Netplay registration (#166)

* Register player

* more

* more

* more

* more

* more
This commit is contained in:
Logan McNaughton 2025-01-20 21:29:46 +01:00 committed by GitHub
parent 004af133eb
commit ada4b7e3b4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 197 additions and 35 deletions

View File

@ -10,6 +10,7 @@ include!(concat!(env!("OUT_DIR"), "/simd_bindings.rs"));
#[cfg(target_arch = "x86_64")]
use std::arch::x86_64::*;
use crate::netplay;
use crate::ui;
use std::collections::HashMap;
use std::fs;
@ -47,17 +48,11 @@ pub mod unmapped;
pub mod vi;
pub fn run_game(
file_path: &std::path::Path,
rom_contents: Vec<u8>,
data_dir: std::path::PathBuf,
device: &mut Device,
fullscreen: bool,
) {
let rom_contents = get_rom_contents(file_path);
if rom_contents.is_empty() {
println!("Could not read rom file");
return;
}
cart_rom::init(device, rom_contents); // cart needs to come before rdram
// rdram pointer is shared with parallel-rdp
@ -166,6 +161,7 @@ pub fn get_rom_contents(file_path: &std::path::Path) -> Vec<u8> {
}
pub struct Device {
pub netplay: Option<netplay::Netplay>,
pub ui: ui::Ui,
byte_swap: usize,
cpu: cpu::Cpu,
@ -195,6 +191,7 @@ impl Device {
byte_swap = 3;
}
Device {
netplay: None,
ui: ui::Ui::new(config_dir),
byte_swap,
cpu: cpu::Cpu {

View File

@ -4,6 +4,7 @@
static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
mod device;
mod netplay;
mod ui;
use clap::Parser;
@ -156,7 +157,12 @@ async fn main() {
if args.game.is_some() {
let file_path = std::path::Path::new(args.game.as_ref().unwrap());
device::run_game(file_path, data_dir, &mut device, args.fullscreen);
let rom_contents = device::get_rom_contents(file_path);
if rom_contents.is_empty() {
println!("Could not read rom file");
return;
}
device::run_game(rom_contents, data_dir, &mut device, args.fullscreen);
}
} else {
let options = eframe::NativeOptions {

89
src/netplay.rs Normal file
View File

@ -0,0 +1,89 @@
use crate::device;
use crate::ui;
use std::io::{Read, Write};
//UDP packet formats
//const UDP_SEND_KEY_INFO: u8 = 0;
//const UDP_RECEIVE_KEY_INFO: u8 = 1;
//const UDP_REQUEST_KEY_INFO: u8 = 2;
//const UDP_RECEIVE_KEY_INFO_GRATUITOUS: u8 = 3;
//const UDP_SYNC_DATA: u8 = 4;
//TCP packet formats
//const TCP_SEND_SAVE: u8 = 1;
//const TCP_RECEIVE_SAVE: u8 = 2;
//const TCP_SEND_SETTINGS: u8 = 3;
//const TCP_RECEIVE_SETTINGS: u8 = 4;
const TCP_REGISTER_PLAYER: u8 = 5;
const TCP_GET_REGISTRATION: u8 = 6;
const TCP_DISCONNECT_NOTICE: u8 = 7;
pub struct Netplay {
// udp_socket: std::net::UdpSocket,
tcp_stream: std::net::TcpStream,
player_number: u8,
}
pub fn init(
mut peer_addr: std::net::SocketAddr,
session: ui::gui::gui_netplay::NetplayRoom,
player_number: u8,
) -> Netplay {
peer_addr.set_port(session.port.unwrap() as u16);
let udp_socket = if peer_addr.is_ipv4() {
std::net::UdpSocket::bind((std::net::Ipv4Addr::UNSPECIFIED, 0))
.expect("couldn't bind to address")
} else {
std::net::UdpSocket::bind((std::net::Ipv6Addr::UNSPECIFIED, 0))
.expect("couldn't bind to address")
};
udp_socket.connect(peer_addr).unwrap();
let mut stream = std::net::TcpStream::connect(peer_addr).unwrap();
let regid = (player_number + 1) as u32;
let mut request: [u8; 8] = [
TCP_REGISTER_PLAYER,
player_number,
0, //plugin/pak
0, //rawdata
0, //regid (u32)
0, //regid (u32)
0, //regid (u32)
0, //regid (u32)
];
request[4..8].copy_from_slice(&regid.to_be_bytes());
stream.write_all(&request).unwrap();
let mut response: [u8; 2] = [0, 0];
stream.read_exact(&mut response).unwrap();
if response[0] != 1 {
panic!("Failed to register player");
}
let _buffer_target = response[1];
let request: [u8; 1] = [TCP_GET_REGISTRATION];
stream.write_all(&request).unwrap();
let mut response: [u8; 24] = [0; 24];
stream.read_exact(&mut response).unwrap();
let mut reg_id: [u32; 4] = [0; 4];
for i in 0..4 {
// reg_id of 0 means no player connected
reg_id[i] = u32::from_be_bytes(response[(i * 6)..(i * 6) + 4].try_into().unwrap());
}
Netplay {
// udp_socket,
tcp_stream: stream,
player_number,
}
}
pub fn close(device: &mut device::Device) {
let netplay = device.netplay.as_mut().unwrap();
let regid = (netplay.player_number + 1) as u32;
let mut request: [u8; 5] = [TCP_DISCONNECT_NOTICE, 0, 0, 0, 0];
request[1..5].copy_from_slice(&regid.to_be_bytes());
netplay.tcp_stream.write_all(&request).unwrap();
}

View File

@ -1,8 +1,8 @@
use crate::device;
use crate::netplay;
use crate::ui;
use eframe::egui;
mod netplay;
pub mod gui_netplay;
pub struct GopherEguiApp {
config_dir: std::path::PathBuf,
@ -24,7 +24,7 @@ pub struct GopherEguiApp {
vru_window_receiver: Option<tokio::sync::mpsc::Receiver<Vec<String>>>,
vru_word_notifier: Option<tokio::sync::mpsc::Sender<String>>,
vru_word_list: Vec<String>,
netplay: netplay::Netplay,
pub netplay: gui_netplay::GuiNetplay,
}
struct SaveConfig {
@ -229,18 +229,38 @@ fn show_vru_dialog(app: &mut GopherEguiApp, ctx: &egui::Context) {
);
}
fn open_rom(app: &mut GopherEguiApp, ctx: &egui::Context) {
let task = rfd::AsyncFileDialog::new().pick_file();
pub fn open_rom(app: &mut GopherEguiApp, ctx: &egui::Context) {
let task;
let netplay;
let selected_controller = app.selected_controller;
let selected_profile = app.selected_profile.clone();
let controller_enabled = app.controller_enabled;
let upscale = app.upscale;
let integer_scaling = app.integer_scaling;
let fullscreen = app.fullscreen;
let emulate_vru = app.emulate_vru;
let mut emulate_vru = app.emulate_vru;
let config_dir = app.config_dir.clone();
let cache_dir = app.cache_dir.clone();
let data_dir = app.data_dir.clone();
let peer_addr;
let session;
let player_number;
if app.netplay.player_name.is_empty() {
task = Some(rfd::AsyncFileDialog::new().pick_file());
netplay = false;
peer_addr = None;
session = None;
player_number = None;
} else {
task = None;
netplay = true;
emulate_vru = false;
peer_addr = app.netplay.peer_addr;
session = app.netplay.waiting_session.clone();
player_number = Some(app.netplay.player_number);
}
let (vru_window_notifier, vru_window_receiver): (
tokio::sync::mpsc::Sender<Vec<String>>,
@ -260,13 +280,15 @@ fn open_rom(app: &mut GopherEguiApp, ctx: &egui::Context) {
app.vru_word_notifier = None;
}
let rom_contents = app.netplay.rom_contents.clone();
let gui_ctx = ctx.clone();
tokio::spawn(async move {
let file = task.await;
let file = if !netplay { task.unwrap().await } else { None };
if let Some(file) = file {
if file.is_some() || netplay {
let running_file = cache_dir.join("game_running");
if running_file.exists() {
println!("Game already running");
return;
}
let result = std::fs::File::create(running_file.clone());
@ -291,12 +313,23 @@ fn open_rom(app: &mut GopherEguiApp, ctx: &egui::Context) {
device.vru.word_receiver = Some(vru_word_receiver);
device.vru.gui_ctx = Some(gui_ctx);
}
device::run_game(
std::path::Path::new(file.path()),
data_dir,
&mut device,
fullscreen,
);
if netplay {
device.netplay = Some(netplay::init(
peer_addr.unwrap(),
session.unwrap(),
player_number.unwrap(),
));
device::run_game(rom_contents, data_dir, &mut device, fullscreen);
netplay::close(&mut device);
} else {
let rom_contents = device::get_rom_contents(file.unwrap().path());
if rom_contents.is_empty() {
println!("Could not read rom file");
} else {
device::run_game(rom_contents, data_dir, &mut device, fullscreen);
}
}
let result = std::fs::remove_file(running_file.clone());
if result.is_err() {
panic!("could not remove running file: {}", result.err().unwrap())
@ -308,19 +341,19 @@ fn open_rom(app: &mut GopherEguiApp, ctx: &egui::Context) {
impl eframe::App for GopherEguiApp {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
if self.netplay.create {
netplay::netplay_create(self, ctx);
gui_netplay::netplay_create(self, ctx);
}
if self.netplay.join {
netplay::netplay_join(self, ctx);
gui_netplay::netplay_join(self, ctx);
}
if self.netplay.wait {
netplay::netplay_wait(self, ctx);
gui_netplay::netplay_wait(self, ctx);
}
if !self.netplay.error.is_empty() {
netplay::netplay_error(self, ctx, self.netplay.error.clone());
gui_netplay::netplay_error(self, ctx, self.netplay.error.clone());
}
if self.configure_profile {

View File

@ -1,4 +1,5 @@
use crate::device;
use crate::ui::gui;
use crate::ui::gui::GopherEguiApp;
use eframe::egui;
use sha2::{Digest, Sha256};
@ -6,8 +7,10 @@ use sha2::{Digest, Sha256};
const NETPLAY_VERSION: i32 = 17;
const EMU_NAME: &str = "gopher64";
type GameInfo = (String, String, String, Vec<u8>);
#[derive(Default)]
pub struct Netplay {
pub struct GuiNetplay {
pub create: bool,
pub join: bool,
pub wait: bool,
@ -24,19 +27,22 @@ pub struct Netplay {
pub chat_message: String,
pub selected_session: Option<NetplayRoom>,
pub pending_begin: bool,
pub peer_addr: Option<std::net::SocketAddr>,
pub motd: String,
pub sessions: Vec<NetplayRoom>,
pub rom_contents: Vec<u8>,
pub player_number: u8,
pub player_names: [String; 4],
pub server: (String, String),
pub socket_waiting: bool,
pub game_info: (String, String, String),
pub game_info: GameInfo,
pub servers: std::collections::HashMap<String, String>,
pub waiting_session: Option<NetplayRoom>,
pub socket:
Option<tungstenite::WebSocket<tungstenite::stream::MaybeTlsStream<std::net::TcpStream>>>,
pub server_receiver:
Option<tokio::sync::mpsc::Receiver<std::collections::HashMap<String, String>>>,
pub game_info_receiver: Option<tokio::sync::mpsc::Receiver<(String, String, String)>>,
pub game_info_receiver: Option<tokio::sync::mpsc::Receiver<GameInfo>>,
pub broadcast_socket: Option<std::net::UdpSocket>,
pub broadcast_timer: Option<std::time::Instant>,
}
@ -49,7 +55,7 @@ pub struct NetplayRoom {
#[serde(rename = "MD5")]
md5: Option<String>,
game_name: Option<String>,
port: Option<i32>,
pub port: Option<i32>,
}
#[derive(serde::Serialize, serde::Deserialize)]
@ -193,15 +199,27 @@ pub fn netplay_create(app: &mut GopherEguiApp, ctx: &egui::Context) {
let game_name = std::str::from_utf8(&rom_contents[0x20..0x20 + 0x14])
.unwrap()
.to_string();
let _ = tx.send((hash, game_name, file.file_name())).await;
let _ = tx
.send((hash, game_name, file.file_name(), rom_contents))
.await;
} else {
let _ = tx
.send(("".to_string(), "".to_string(), "Invalid ROM".to_string()))
.send((
"".to_string(),
"".to_string(),
"Invalid ROM".to_string(),
vec![],
))
.await;
}
} else {
let _ = tx
.send(("".to_string(), "".to_string(), "Open ROM".to_string()))
.send((
"".to_string(),
"".to_string(),
"Open ROM".to_string(),
vec![],
))
.await;
}
gui_ctx.request_repaint();
@ -228,6 +246,7 @@ pub fn netplay_create(app: &mut GopherEguiApp, ctx: &egui::Context) {
let data = result.unwrap();
if !data.0.is_empty() {
app.netplay.game_info = data;
app.netplay.rom_contents = app.netplay.game_info.3.clone();
app.netplay.create_rom_label = app.netplay.game_info.2.clone();
} else {
app.netplay.create_rom_label = data.2;
@ -278,6 +297,7 @@ pub fn netplay_create(app: &mut GopherEguiApp, ctx: &egui::Context) {
} else if app.netplay.game_info.0.is_empty() {
app.netplay.error = "ROM not loaded".to_string();
} else {
app.netplay.rom_contents = app.netplay.game_info.3.clone();
let now_utc = chrono::Utc::now().timestamp_millis().to_string();
let hasher = Sha256::new().chain_update(&now_utc).chain_update(EMU_NAME);
let mut game_name = app.netplay.game_info.1.trim().to_string();
@ -315,6 +335,7 @@ pub fn netplay_create(app: &mut GopherEguiApp, ctx: &egui::Context) {
.unwrap();
match socket.get_mut() {
tungstenite::stream::MaybeTlsStream::Plain(stream) => {
app.netplay.peer_addr = Some(stream.peer_addr().unwrap());
stream.set_nonblocking(true)
}
_ => unimplemented!(),
@ -346,7 +367,10 @@ fn get_sessions(app: &mut GopherEguiApp, ctx: &egui::Context) {
let (mut sock, _response) =
tungstenite::connect(&app.netplay.server.1).expect("Can't connect");
match sock.get_mut() {
tungstenite::stream::MaybeTlsStream::Plain(stream) => stream.set_nonblocking(true),
tungstenite::stream::MaybeTlsStream::Plain(stream) => {
app.netplay.peer_addr = Some(stream.peer_addr().unwrap());
stream.set_nonblocking(true)
}
_ => unimplemented!(),
}
.expect("could not set socket to non-blocking");
@ -555,13 +579,16 @@ pub fn netplay_join(app: &mut GopherEguiApp, ctx: &egui::Context) {
std::str::from_utf8(&rom_contents[0x20..0x20 + 0x14])
.unwrap()
.to_string();
let _ = tx.send((hash, game_name, file.file_name())).await;
let _ = tx
.send((hash, game_name, file.file_name(), rom_contents))
.await;
} else {
let _ = tx
.send((
"".to_string(),
"".to_string(),
"Invalid ROM".to_string(),
vec![],
))
.await;
}
@ -571,6 +598,7 @@ pub fn netplay_join(app: &mut GopherEguiApp, ctx: &egui::Context) {
"".to_string(),
"".to_string(),
"No ROM selected".to_string(),
vec![],
))
.await;
}
@ -712,6 +740,15 @@ pub fn netplay_wait(app: &mut GopherEguiApp, ctx: &egui::Context) {
app.netplay.chat_log.push_str(&message.message.unwrap());
app.netplay.chat_log.push('\n');
} else if message.message_type == "reply_begin_game" {
let mut player = 0;
for (i, name) in app.netplay.player_names.iter().enumerate() {
if *name == app.netplay.player_name {
player = i;
break;
}
}
app.netplay.player_number = player as u8;
gui::open_rom(app, ctx);
app.netplay = Default::default();
return;
}