Netplay registration (#166)
* Register player * more * more * more * more * more
This commit is contained in:
parent
004af133eb
commit
ada4b7e3b4
@ -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 {
|
||||
|
||||
@ -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
89
src/netplay.rs
Normal 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(®id.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(®id.to_be_bytes());
|
||||
|
||||
netplay.tcp_stream.write_all(&request).unwrap();
|
||||
}
|
||||
@ -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 {
|
||||
|
||||
@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user