From 4baa1ef9119a8b5d650b9ce71a46e76469f467bf Mon Sep 17 00:00:00 2001 From: "NG (Graham)" Date: Tue, 10 Jun 2025 20:52:36 -0400 Subject: [PATCH] Add minimum matchmaking lobby server --- Cargo.lock | 28 ++ Cargo.toml | 1 + assets/robocraft/servenvmulti.config | 4 + assets/templates/rc_register.html.hbs | 15 +- .../src/data/lobby.rs | 0 rc_core/src/data/mod.rs | 1 + rc_core/src/persist/settings.rs | 2 +- rc_lobby/Cargo.toml | 17 ++ rc_lobby/run_debug.sh | 3 + rc_lobby/src/cli.rs | 31 ++ rc_lobby/src/main.rs | 263 ++++++++++++++++ rc_lobby_room/Cargo.toml | 19 ++ rc_lobby_room/run_debug.sh | 3 + rc_lobby_room/src/cli.rs | 31 ++ rc_lobby_room/src/data/mod.rs | 0 rc_lobby_room/src/main.rs | 280 ++++++++++++++++++ rc_lobby_room/src/operations/eac.rs | 22 ++ rc_lobby_room/src/operations/join_queue.rs | 47 +++ rc_lobby_room/src/operations/mod.rs | 16 + rc_lobby_room/src/operations/more_auth.rs | 42 +++ rc_lobby_room/src/operations/no_quit.rs | 26 ++ rc_services_room/src/data/mod.rs | 4 +- .../src/operations/validate_machine.rs | 2 +- 23 files changed, 851 insertions(+), 6 deletions(-) rename {rc_services_room => rc_core}/src/data/lobby.rs (100%) create mode 100644 rc_lobby/Cargo.toml create mode 100755 rc_lobby/run_debug.sh create mode 100644 rc_lobby/src/cli.rs create mode 100644 rc_lobby/src/main.rs create mode 100644 rc_lobby_room/Cargo.toml create mode 100755 rc_lobby_room/run_debug.sh create mode 100644 rc_lobby_room/src/cli.rs create mode 100644 rc_lobby_room/src/data/mod.rs create mode 100644 rc_lobby_room/src/main.rs create mode 100644 rc_lobby_room/src/operations/eac.rs create mode 100644 rc_lobby_room/src/operations/join_queue.rs create mode 100644 rc_lobby_room/src/operations/mod.rs create mode 100644 rc_lobby_room/src/operations/more_auth.rs create mode 100644 rc_lobby_room/src/operations/no_quit.rs diff --git a/Cargo.lock b/Cargo.lock index 9e53221..e7ed0ad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2614,6 +2614,34 @@ dependencies = [ "sea-orm", ] +[[package]] +name = "oj_rc_lobby" +version = "0.2.1" +dependencies = [ + "clap", + "env_logger", + "log", + "oj_polariton_auth", + "polariton", + "polariton_server", + "tokio", +] + +[[package]] +name = "oj_rc_lobby_room" +version = "0.2.1" +dependencies = [ + "async-trait", + "clap", + "env_logger", + "log", + "oj_polariton_auth", + "oj_rc_core", + "polariton", + "polariton_server", + "tokio", +] + [[package]] name = "oj_rc_microtransactions" version = "0.2.1" diff --git a/Cargo.toml b/Cargo.toml index 9611ab6..9efb354 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,7 @@ members = [ "rc_social", "rc_social_room", "rc_chat", "rc_chat_room", "rc_singleplayer", "rc_singleplayer_room", + "rc_lobby", "rc_lobby_room", "rc_core", "rc_database", "rc_factory", ] diff --git a/assets/robocraft/servenvmulti.config b/assets/robocraft/servenvmulti.config index ba7a0d9..44e296d 100644 --- a/assets/robocraft/servenvmulti.config +++ b/assets/robocraft/servenvmulti.config @@ -11,6 +11,8 @@ 127.0.0.1:4537 127.0.0.1:4538 127.0.0.1:4539 + 127.0.0.1:4540 + 127.0.0.1:4541 http://127.0.0.1:8001/ http://127.0.0.1:8010/live/data.json @@ -23,6 +25,8 @@ :4537 sp.services.rc.ngram.ca:4538 :4539 + !!!TODO!!!:4540 + :4541 https://live-auth.rc.ngram.ca/ https://static.rc.ngram.ca/live/data.json https://pay.rc.ngram.ca/ diff --git a/assets/templates/rc_register.html.hbs b/assets/templates/rc_register.html.hbs index b254568..e259469 100644 --- a/assets/templates/rc_register.html.hbs +++ b/assets/templates/rc_register.html.hbs @@ -1,5 +1,7 @@ - + + + Registration - Openjam @@ -87,8 +89,9 @@ border-color: red; } input:required:invalid.form-input-box::after { - content: "*"; + content: "*required"; color: red; + font-size: 12px; } input { width: 100%; @@ -97,6 +100,11 @@ background-color: lightgrey; font-size: 20px; } + .input-required-hint::after { + color: red; + /*content: "*required";*/ + font-size: 12px; + } .form-disclaimer { margin: 0 auto; width: 95%; @@ -138,6 +146,7 @@ +
@@ -145,6 +154,7 @@
+
@@ -152,6 +162,7 @@
+
diff --git a/rc_services_room/src/data/lobby.rs b/rc_core/src/data/lobby.rs similarity index 100% rename from rc_services_room/src/data/lobby.rs rename to rc_core/src/data/lobby.rs diff --git a/rc_core/src/data/mod.rs b/rc_core/src/data/mod.rs index 4e83ee5..ce870c3 100644 --- a/rc_core/src/data/mod.rs +++ b/rc_core/src/data/mod.rs @@ -14,6 +14,7 @@ pub mod crf; pub mod channel; pub mod sanction; pub mod robot_data; +pub mod lobby; pub mod error_codes; diff --git a/rc_core/src/persist/settings.rs b/rc_core/src/persist/settings.rs index 90185f9..8a90660 100644 --- a/rc_core/src/persist/settings.rs +++ b/rc_core/src/persist/settings.rs @@ -22,7 +22,7 @@ fn default_gameplay_settings() -> super::GameplaySettings { shield_hps: 2_000, request_review_level: 10_000, critical_ratio: 5.0, - cross_promo_image: "https://git.ngram.ca/assets/img/logo.png".to_owned(), + cross_promo_image: "https://git.ngram.ca/OpenJam/servers/raw/branch/main/assets/robocraft/favicon.jpg".to_owned(), cross_promo_link: "https://git.ngram.ca/OpenJam/servers".to_owned(), } } diff --git a/rc_lobby/Cargo.toml b/rc_lobby/Cargo.toml new file mode 100644 index 0000000..522556f --- /dev/null +++ b/rc_lobby/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "oj_rc_lobby" +version.workspace = true +edition.workspace = true +readme.workspace = true +license.workspace = true +repository.workspace = true +authors.workspace = true + +[dependencies] +log.workspace = true +env_logger.workspace = true +tokio = { version = "1.43", features = [ "net", "macros", "rt-multi-thread", "io-util" ] } +clap.workspace = true +polariton.workspace = true +oj_polariton_auth = { version = "*", path = "../polariton_auth" } +polariton_server.workspace = true diff --git a/rc_lobby/run_debug.sh b/rc_lobby/run_debug.sh new file mode 100755 index 0000000..cc15e3f --- /dev/null +++ b/rc_lobby/run_debug.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +RUST_BACKTRACE=1 RUST_LOG=debug cargo run -- -1 diff --git a/rc_lobby/src/cli.rs b/rc_lobby/src/cli.rs new file mode 100644 index 0000000..0fad86d --- /dev/null +++ b/rc_lobby/src/cli.rs @@ -0,0 +1,31 @@ +use clap::Parser; + +#[derive(Parser, Debug)] +#[command(version, about, long_about = None)] +pub struct CliArgs { + /// TCP port on which to accept connections + #[arg(short, long, default_value_t = 4540)] + pub port: u16, + + /// IP Address on which to accept connections + #[arg(long, default_value_t = {"127.0.0.1".to_string()})] + pub ip: String, + + /// Domain and port of the game server to send new connections + #[arg(long, default_value_t = {"127.0.0.1:4541".to_string()})] + pub redirect: String, + + /// Name of game server to send new connections + #[arg(long, default_value_t = {"ngram_is_ngnius".to_string()})] + pub room_name: String, + + /// Handle one connection and then exit + #[arg(short = '1', long)] + pub once: bool, +} + +impl CliArgs { + pub fn get() -> Self { + Self::parse() + } +} diff --git a/rc_lobby/src/main.rs b/rc_lobby/src/main.rs new file mode 100644 index 0000000..d8fc8a7 --- /dev/null +++ b/rc_lobby/src/main.rs @@ -0,0 +1,263 @@ +#![forbid(unsafe_code)] +mod cli; + +use oj_polariton_auth::Handshake; +use tokio::net; + +use polariton::packet::{Data, Message, Packet, StandardMessage}; +use polariton::operation::{OperationResponse, Typed}; + +#[tokio::main] +async fn main() -> std::io::Result<()> { + env_logger::init(); + let args = cli::CliArgs::get(); + log::debug!("Got cli args {:?}", args); + + let ip_addr: std::net::IpAddr = args.ip.parse().expect("Invalid IP address"); + + // memory leak, but only once (so not a big deal) + let redirect_static = Box::leak(Box::new(args.redirect.clone())); + let room_name_static = Box::leak(Box::new(args.room_name.clone())); + + let listener = net::TcpListener::bind(std::net::SocketAddr::new(ip_addr, args.port)).await?; + + if args.once { + log::warn!("Handling first connection and then exiting"); + let (socket, address) = listener.accept().await?; + process_socket(socket, address, redirect_static, room_name_static).await; + Ok(()) + } else { + loop { + let (socket, address) = listener.accept().await?; + tokio::spawn(process_socket(socket, address, redirect_static, room_name_static)); + } + } +} + +async fn process_socket(mut socket: net::TcpStream, address: std::net::SocketAddr, redirect_url: &str, lobby_name: &str) { + log::debug!("Accepting connection from address {}", address); + + let enc = match do_connect_handshake(&mut socket, lobby_name, redirect_url).await { + Some(x) => x, + None => { + log::error!("Failed to do connect handshake with {}", address); + return; + } + }; + let ctx = polariton::packet::SerdesContext::from_boxed(Default::default(), enc); + while let Ok(packet) = polariton_server::utils::receive_packet_async(&mut socket, &ctx).await { + match packet { + Packet::Ping(ping) => { + polariton_server::utils::handle_ping_async(ping, &mut socket, &ctx).await.unwrap_or_default(); + }, + Packet::Packet(packet) => log::warn!("Not handling packet {:?}", packet), + } + } + log::debug!("Goodbye connection from address {}", address); +} + +const APP_ID: &str = "LobbyServer"; + +struct AuthImpl; + +const TOKEN_KEY: u8 = 216; // token;refresh_token +//const UNKNOWN_BYTE_KEY: u8 = 217; +const SERVICE_KEY: u8 = 224; +const USERNAME_KEY: u8 = 225; + +//const CCU_KEY: u8 = 245; + +#[derive(Debug)] +enum AuthError { + WrongService { expected: String, actual: String }, + MissingService, + MissingToken, + MissingUsername, +} + +impl AuthError { + fn log_err(&self) { + match self { + Self::WrongService { expected, actual } => log::error!("(auth fail) Got unexpected service {}, expected {}", actual, expected), + Self::MissingService => log::error!("(auth fail) No service name param ({}) received", SERVICE_KEY), + Self::MissingToken => log::error!("(auth fail) No token param ({}) received", TOKEN_KEY), + Self::MissingUsername => log::error!("(auth fail) No username param ({}) received", USERNAME_KEY), + } + } +} + +impl oj_polariton_auth::AuthProvider for AuthImpl { + fn validate(&mut self, params: &std::collections::HashMap) -> Result, AuthError> { + if let Some(Typed::Str(token)) = params.get(&TOKEN_KEY) { + if let Some(Typed::Str(service)) = params.get(&SERVICE_KEY) { + if let Some(Typed::Str(user)) = params.get(&USERNAME_KEY) { + if service.string == APP_ID { + let params_resp = std::collections::HashMap::::new(); + //params_resp.insert(CCU_KEY, Typed::Byte(0)); + log::debug!("Auth success for {} (token: {})", user.string, token.string); + Ok(params_resp) + } else { Err(AuthError::WrongService { expected: APP_ID.to_owned(), actual: service.string.to_owned() }) } + } else { Err(AuthError::MissingUsername) } + } else { Err(AuthError::MissingService) } + } else { Err(AuthError::MissingToken) } + } +} + +async fn do_connect_handshake( + socket: &mut net::TcpStream, + game_server_name: &str, + game_server_url: &str, +) -> Option> { + let handshake = Handshake::new(APP_ID); + // connect + log::debug!("(connect) Handling first packet"); + let packet1 = match polariton_server::utils::receive_packet_async(socket, &Default::default()).await { + Ok(x) => x, + Err(e) => { + log::error!("Failed to read connect packet: {}", e); + return None; + } + }; + let (handshake, to_send) = match handshake.connect(&packet1) { + Ok(x) => (x.handshake, x.extra), + Err(e) => { + log::error!("Failed to handle connect handshake: {:?}", e.extra); + return None; + } + }; + match polariton_server::utils::send_packet_async(&to_send, socket, &Default::default()).await { + Ok(_) => {}, + Err(e) => { + log::error!("Failed to send connect ack packet: {}", e); + return None; + } + } + // encrypt + log::debug!("(connect) Handling second packet"); + let mut packet2 = match polariton_server::utils::receive_packet_async(socket, &Default::default()).await { + Ok(x) => x, + Err(e) => { + log::error!("Failed to read (maybe) public key packet: {}", e); + return None; + } + }; + while let Packet::Ping(ping) = packet2 { + polariton_server::utils::handle_ping_async(ping, socket, &Default::default()).await.unwrap_or_default(); + packet2 = match polariton_server::utils::receive_packet_async(socket, &Default::default()).await { + Ok(x) => x, + Err(e) => { + log::error!("Failed to read (maybe) public key packet: {}", e); + return None; + } + }; + } + let (handshake, to_send, crypto) = match handshake.encrypt(&packet2) { + Ok(x) => (x.handshake, x.extra.0, x.extra.1), + Err(e) => { + log::error!("Failed to handle encryption handshake: {:?}", e.extra); + return None; + } + }; + match polariton_server::utils::send_packet_async(&to_send, socket, &Default::default()).await { + Ok(_) => {}, + Err(e) => { + log::error!("Failed to send encryption ack packet: {}", e); + return None; + } + } + // pre-auth + let handshake = handshake.with_auth(AuthImpl); + let op_ctx = polariton::serdes::SerdesContext::default(); + let ctx = polariton::packet::SerdesContext::new(op_ctx, crypto); + // authenticate + log::debug!("(connect) Handling third packet"); + let mut packet3 = match polariton_server::utils::receive_packet_async(socket, &ctx).await { + Ok(x) => x, + Err(e) => { + log::error!("Failed to read (maybe) auth packet: {}", e); + return None; + } + }; + while let Packet::Ping(ping) = packet3 { + polariton_server::utils::handle_ping_async(ping, socket, &Default::default()).await.unwrap_or_default(); + packet3 = match polariton_server::utils::receive_packet_async(socket, &ctx).await { + Ok(x) => x, + Err(e) => { + log::error!("Failed to read (maybe) auth packet: {}", e); + return None; + } + }; + } + let to_send = match handshake.authenticate(&packet3, &ctx) { + Ok(x) => x, + Err(h) => match h.extra { + oj_polariton_auth::AuthError::Validation(e) => { + e.log_err(); + return None; + }, + e => { + log::error!("Failed to handle auth handshake: {:?}", e); + return None; + }, + }, + }; + match polariton_server::utils::send_packet_async(&to_send, socket, &ctx).await { + Ok(_) => {}, + Err(e) => { + log::error!("Failed to send auth ack packet: {}", e); + return None; + } + } + + // redirect to lobby + log::debug!("(connect) Handling fourth packet"); + let mut packet_j = match polariton_server::utils::receive_packet_async(socket, &ctx).await { + Ok(x) => x, + Err(e) => { + log::error!("Failed to read (maybe) join packet: {}", e); + return None; + } + }; + while let Packet::Ping(ping) = packet_j { + polariton_server::utils::handle_ping_async(ping, socket, &ctx).await.unwrap_or_default(); + packet_j = match polariton_server::utils::receive_packet_async(socket, &ctx).await { + Ok(x) => x, + Err(e) => { + log::error!("Failed to read (maybe) join packet: {}", e); + return None; + } + }; + } + log::debug!("(connect) Got fourth packet {:?}", packet_j); + if let Packet::Packet(msg) = &packet_j { + if let Message::Standard(st) = &msg.message { + if let Data::OpReq(req) = &st.data { + if req.code == 226 { // join lobby + log::debug!("Max players from lobby join request: {:?}", req.params.to_owned().to_dict().get(&255)); + let mut params = std::collections::HashMap::::new(); + params.insert(230 /* game server address */, Typed::Str(game_server_url.into())); + params.insert(255 /* room name */, Typed::Str(game_server_name.into())); + let resp = Packet::from_message( + Message::Standard( + StandardMessage { flags: 0, + data: Data::OpResp(OperationResponse { + code: req.code, + return_code: 0, + message: Typed::Null, + params: params.into(), + }), + }.encrypt(true)), 0, true, &ctx).unwrap(); + match polariton_server::utils::send_packet_async(&resp, socket, &ctx).await { + Ok(_) => {}, + Err(e) => { + log::error!("Failed to send lobby ack packet: {}", e); + return None; + } + } + } + } + } + } + + Some(ctx.into_crypto()) +} diff --git a/rc_lobby_room/Cargo.toml b/rc_lobby_room/Cargo.toml new file mode 100644 index 0000000..f899496 --- /dev/null +++ b/rc_lobby_room/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "oj_rc_lobby_room" +version.workspace = true +edition.workspace = true +readme.workspace = true +license.workspace = true +repository.workspace = true +authors.workspace = true + +[dependencies] +log.workspace = true +env_logger.workspace = true +tokio = { version = "1.43", features = [ "net", "macros", "rt-multi-thread", "io-util", "time"] } +clap.workspace = true +polariton.workspace = true +oj_polariton_auth = { version = "*", path = "../polariton_auth" } +polariton_server.workspace = true +oj_rc_core = { version = "*", path = "../rc_core" } +async-trait.workspace = true diff --git a/rc_lobby_room/run_debug.sh b/rc_lobby_room/run_debug.sh new file mode 100755 index 0000000..cc15e3f --- /dev/null +++ b/rc_lobby_room/run_debug.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +RUST_BACKTRACE=1 RUST_LOG=debug cargo run -- -1 diff --git a/rc_lobby_room/src/cli.rs b/rc_lobby_room/src/cli.rs new file mode 100644 index 0000000..a269e71 --- /dev/null +++ b/rc_lobby_room/src/cli.rs @@ -0,0 +1,31 @@ +use clap::Parser; + +#[derive(Parser, Debug)] +#[command(version, about, long_about = None)] +pub struct CliArgs { + /// TCP port on which to accept connections + #[arg(short, long, default_value_t = 4541)] + pub port: u16, + + /// IP Address on which to accept connections + #[arg(long, default_value_t = {"127.0.0.1".to_string()})] + pub ip: String, + + /// Assets root + #[arg(long, default_value_t = {"../assets/robocraft".to_string()})] + pub assets: String, + + /// User data root + #[arg(long, default_value_t = {"../data/robocraft".to_string()})] + pub data: String, + + /// Handle one connection and then exit + #[arg(short = '1', long)] + pub once: bool, +} + +impl CliArgs { + pub fn get() -> Self { + Self::parse() + } +} diff --git a/rc_lobby_room/src/data/mod.rs b/rc_lobby_room/src/data/mod.rs new file mode 100644 index 0000000..e69de29 diff --git a/rc_lobby_room/src/main.rs b/rc_lobby_room/src/main.rs new file mode 100644 index 0000000..b6f4a47 --- /dev/null +++ b/rc_lobby_room/src/main.rs @@ -0,0 +1,280 @@ +#![forbid(unsafe_code)] +mod cli; + +mod data; +mod operations; + +use oj_polariton_auth::Handshake; +use tokio::net; + +use polariton::packet::{Data, Message, Packet, StandardMessage}; +use polariton::operation::{OperationResponse, Typed}; + +pub struct InitConfig { + pub config: oj_rc_core::persist::config::ConfigImpl, + pub users: std::sync::Arc, + pub factory: std::sync::Arc, + pub parsers: oj_rc_core::cubes::CubeParsers, +} + +pub type UserTy = oj_rc_core::UserState<()>; + +#[tokio::main] +async fn main() -> std::io::Result<()> { + env_logger::init(); + let args = cli::CliArgs::get(); + log::debug!("Got cli args {:?}", args); + + let config = oj_rc_core::persist::config::ConfigImpl::load(&args.assets).expect("Bad config data"); + let users = std::sync::Arc::new(oj_rc_core::persist::user::UserImpl::load(&args.data, &config).await.expect("Bad user data")); + let factory = std::sync::Arc::new(>::factory::<'_, '_>(&config).await.expect("Bad vehicle factory (CRF) config")); + let parsers = oj_rc_core::cubes::CubeParsers::new(&config); + + let init_ctx = InitConfig { + config, + users, + factory, + parsers, + }; + + let server = std::sync::Arc::new(polariton_server::Server::new(operations::handler(&init_ctx), polariton_server::events::EventsHandler::new())); + + let ip_addr: std::net::IpAddr = args.ip.parse().expect("Invalid IP address"); + + let listener = net::TcpListener::bind(std::net::SocketAddr::new(ip_addr, args.port)).await?; + + if args.once { + log::warn!("Handling first connection and then exiting"); + let (socket, address) = listener.accept().await?; + process_socket(socket, address, server.clone(), init_ctx.users.clone()).await; + } else { + loop { + let (socket, address) = listener.accept().await?; + tokio::spawn(process_socket(socket, address, server.clone(), init_ctx.users.clone())); + } + } + server.join(); + server.join_async().await; + Ok(()) +} + +async fn process_socket(mut socket: net::TcpStream, address: std::net::SocketAddr, server: std::sync::Arc>, users: std::sync::Arc) { + log::debug!("Accepting connection from address {}", address); + let enc = match do_connect_handshake(&mut socket).await { + Some(x) => x, + None => { + log::error!("Failed to do connect handshake with {}", address); + return; + } + }; + let (socket_r, socket_w) = socket.into_split(); + let (chann_tx, chann_rx) = tokio::sync::mpsc::unbounded_channel(); + let user_state = oj_rc_core::UserState::<()>::new(users, chann_tx.clone()); + let ctx = polariton::packet::SerdesContext::from_boxed(Default::default(), enc); + server.handle_async_with_channel(socket_r, socket_w, user_state, ctx, chann_tx, chann_rx).await; + log::debug!("Goodbye connection from address {}", address); +} + +const APP_ID: &str = "LobbyServer"; + +struct AuthImpl; + +const TOKEN_KEY: u8 = 216; // token;refresh_token +//const UNKNOWN_BYTE_KEY: u8 = 217; +const SERVICE_KEY: u8 = 224; +const USERNAME_KEY: u8 = 225; + +//const CCU_KEY: u8 = 245; + +#[derive(Debug)] +enum AuthError { + WrongService { expected: String, actual: String }, + MissingService, + MissingToken, + MissingUsername, +} + +impl AuthError { + fn log_err(&self) { + match self { + Self::WrongService { expected, actual } => log::error!("(auth fail) Got unexpected service {}, expected {}", actual, expected), + Self::MissingService => log::error!("(auth fail) No service name param ({}) received", SERVICE_KEY), + Self::MissingToken => log::error!("(auth fail) No token param ({}) received", TOKEN_KEY), + Self::MissingUsername => log::error!("(auth fail) No username param ({}) received", USERNAME_KEY), + } + } +} + +impl oj_polariton_auth::AuthProvider for AuthImpl { + fn validate(&mut self, params: &std::collections::HashMap) -> Result, AuthError> { + if let Some(Typed::Str(token)) = params.get(&TOKEN_KEY) { + if let Some(Typed::Str(service)) = params.get(&SERVICE_KEY) { + if let Some(Typed::Str(user)) = params.get(&USERNAME_KEY) { + if service.string == APP_ID { + let params_resp = std::collections::HashMap::::new(); + //params_resp.insert(CCU_KEY, Typed::Byte(0)); + log::debug!("Auth success for {} (token: {})", user.string, token.string); + Ok(params_resp) + } else { Err(AuthError::WrongService { expected: APP_ID.to_owned(), actual: service.string.to_owned() }) } + } else { Err(AuthError::MissingUsername) } + } else { Err(AuthError::MissingService) } + } else { Err(AuthError::MissingToken) } + } +} + +async fn do_connect_handshake( + socket: &mut net::TcpStream, +) -> Option> { + let handshake = Handshake::new(APP_ID); + // connect + log::debug!("(connect) Handling first packet"); + let packet1 = match polariton_server::utils::receive_packet_async(socket, &Default::default()).await { + Ok(x) => x, + Err(e) => { + log::error!("Failed to read connect packet: {}", e); + return None; + } + }; + let (handshake, to_send) = match handshake.connect(&packet1) { + Ok(x) => (x.handshake, x.extra), + Err(e) => { + log::error!("Failed to handle connect handshake: {:?}", e.extra); + return None; + } + }; + match polariton_server::utils::send_packet_async(&to_send, socket, &Default::default()).await { + Ok(_) => {}, + Err(e) => { + log::error!("Failed to send connect ack packet: {}", e); + return None; + } + } + // encrypt + log::debug!("(connect) Handling second packet"); + let mut packet2 = match polariton_server::utils::receive_packet_async(socket, &Default::default()).await { + Ok(x) => x, + Err(e) => { + log::error!("Failed to read (maybe) public key packet: {}", e); + return None; + } + }; + while let Packet::Ping(ping) = packet2 { + polariton_server::utils::handle_ping_async(ping, socket, &Default::default()).await.unwrap_or_default(); + packet2 = match polariton_server::utils::receive_packet_async(socket, &Default::default()).await { + Ok(x) => x, + Err(e) => { + log::error!("Failed to read (maybe) public key packet: {}", e); + return None; + } + }; + } + let (handshake, to_send, crypto) = match handshake.encrypt(&packet2) { + Ok(x) => (x.handshake, x.extra.0, x.extra.1), + Err(e) => { + log::error!("Failed to handle encryption handshake: {:?}", e.extra); + return None; + } + }; + match polariton_server::utils::send_packet_async(&to_send, socket, &Default::default()).await { + Ok(_) => {}, + Err(e) => { + log::error!("Failed to send encryption ack packet: {}", e); + return None; + } + } + // pre-auth + let handshake = handshake.with_auth(AuthImpl); + let op_ctx = polariton::serdes::SerdesContext::default(); + let ctx = polariton::packet::SerdesContext::new(op_ctx, crypto); + // authenticate + log::debug!("(connect) Handling third packet"); + let mut packet3 = match polariton_server::utils::receive_packet_async(socket, &ctx).await { + Ok(x) => x, + Err(e) => { + log::error!("Failed to read (maybe) auth packet: {}", e); + return None; + } + }; + while let Packet::Ping(ping) = packet3 { + polariton_server::utils::handle_ping_async(ping, socket, &Default::default()).await.unwrap_or_default(); + packet3 = match polariton_server::utils::receive_packet_async(socket, &ctx).await { + Ok(x) => x, + Err(e) => { + log::error!("Failed to read (maybe) auth packet: {}", e); + return None; + } + }; + } + let to_send = match handshake.authenticate(&packet3, &ctx) { + Ok(x) => x, + Err(h) => match h.extra { + oj_polariton_auth::AuthError::Validation(e) => { + e.log_err(); + return None; + }, + e => { + log::error!("Failed to handle auth handshake: {:?}", e); + return None; + }, + }, + }; + match polariton_server::utils::send_packet_async(&to_send, socket, &ctx).await { + Ok(_) => {}, + Err(e) => { + log::error!("Failed to send auth ack packet: {}", e); + return None; + } + } + + // join lobby + log::debug!("(join lobby) Handling fourth packet"); + let mut packet_j = match polariton_server::utils::receive_packet_async(socket, &ctx).await { + Ok(x) => x, + Err(e) => { + log::error!("Failed to read (maybe) join packet: {}", e); + return None; + } + }; + while let Packet::Ping(ping) = packet_j { + polariton_server::utils::handle_ping_async(ping, socket, &Default::default()).await.unwrap_or_default(); + packet_j = match polariton_server::utils::receive_packet_async(socket, &ctx).await { + Ok(x) => x, + Err(e) => { + log::error!("Failed to read (maybe) join packet: {}", e); + return None; + } + }; + } + if let Packet::Packet(msg) = &packet_j { + if let Message::Standard(st) = &msg.message { + if let Data::OpReq(req) = &st.data { + if req.code == 226 { // join lobby (but for real this time) + let mut params = std::collections::HashMap::::new(); + //params.insert(252 /* actors in game */, Typed::Str(game_server_url.into())); + params.insert(254 /* game server address */, Typed::Int(42)); + params.insert(249 /* actor properties */, Typed::HashMap(Vec::new().into())); + params.insert(248 /* game properties */, Typed::HashMap(Vec::new().into())); + let resp = Packet::from_message( + Message::Standard( + StandardMessage { flags: 0, + data: Data::OpResp(OperationResponse { + code: req.code, + return_code: 0, + message: Typed::Null, + params: params.into(), + }), + }.encrypt(true)), 0, true, &ctx).unwrap(); + match polariton_server::utils::send_packet_async(&resp, socket, &ctx).await { + Ok(_) => {}, + Err(e) => { + log::error!("Failed to send lobby ack packet: {}", e); + return None; + } + } + } + } + } + } + + Some(ctx.into_crypto()) +} diff --git a/rc_lobby_room/src/operations/eac.rs b/rc_lobby_room/src/operations/eac.rs new file mode 100644 index 0000000..e9bbe6b --- /dev/null +++ b/rc_lobby_room/src/operations/eac.rs @@ -0,0 +1,22 @@ +use polariton_server::operations::{Operation, OperationCode}; + +pub struct EacChallengeIgnorer; + +impl Operation for EacChallengeIgnorer { + type User = crate::UserTy; + + fn handle(&self, params: polariton::operation::ParameterTable, _: &Self::User) -> polariton::operation::OperationResponse { + polariton::operation::OperationResponse { + code: 5, // skip the challenge (hopefully) + return_code: 0, + message: polariton::operation::Typed::Null, + params, + } + } +} + +impl OperationCode for EacChallengeIgnorer { + fn op_code() -> u8 { + 4 + } +} diff --git a/rc_lobby_room/src/operations/join_queue.rs b/rc_lobby_room/src/operations/join_queue.rs new file mode 100644 index 0000000..cdf3887 --- /dev/null +++ b/rc_lobby_room/src/operations/join_queue.rs @@ -0,0 +1,47 @@ +use polariton_server::operations::{SimpleOpError, SimpleOperation, SimpleOpImpl}; +use polariton::operation::{ParameterTable, Typed}; + +const CODE: u8 = 0; + +const GROUP_ID_PARAM_KEY: u8 = 2; // str; in +const GARAGE_SLOT_PARAM_KEY: u8 = 3; // int; in +const GROUP_SIZE_PARAM_KEY: u8 = 4; // int; in +const IS_GROUP_LEADER_PARAM_KEY: u8 = 14; // bool; in +const LOBBY_TY_PARAM_KEY: u8 = 30; // int; in +const EVENT_TO_JOIN_PARAM_KEY: u8 = 41; // str; in + +const ESTIMATED_QUEUE_TIME_PARAM_KEY: u8 = 13; // int (seconds); out +const PERSONAL_RANKING_PARAM_KEY: u8 = 17; // double; out + +pub(super) struct QueueJoinProvider; + +#[async_trait::async_trait] +impl SimpleOperation for QueueJoinProvider { + type User = crate::UserTy; + const CODE: u8 = CODE; + + async fn handle(&self, params: ParameterTable, _user: &Self::User) -> Result, SimpleOpError> { + let mut params = params.to_dict(); + if let Some(Typed::Str(group_id)) = params.remove(&GROUP_ID_PARAM_KEY) { + if let Some(Typed::Int(slot_id)) = params.remove(&GARAGE_SLOT_PARAM_KEY) { + if let Some(Typed::Int(group_size)) = params.remove(&GROUP_SIZE_PARAM_KEY) { + if let Some(Typed::Bool(is_leader)) = params.remove(&IS_GROUP_LEADER_PARAM_KEY) { + if let Some(Typed::Int(lobby_ty)) = params.remove(&LOBBY_TY_PARAM_KEY) { + let lobby_ty = oj_rc_core::data::lobby::LobbyType::from_int(lobby_ty)?; + if let Some(Typed::Str(event_to_join)) = params.remove(&EVENT_TO_JOIN_PARAM_KEY) { + log::debug!("Got lobby join queue request of platoon {} ({} players, is_leader:{}) slot {} lobby {:?} event {}", group_id.string, group_size, is_leader, slot_id, lobby_ty, event_to_join.string); + params.insert(ESTIMATED_QUEUE_TIME_PARAM_KEY, Typed::Int(42)); + params.insert(PERSONAL_RANKING_PARAM_KEY, Typed::Double(42.0)); + } + } + } + } + } + } + Ok(params.into()) + } +} + +pub(super) fn join_queue_provider() -> SimpleOpImpl { + SimpleOpImpl::new(QueueJoinProvider) +} diff --git a/rc_lobby_room/src/operations/mod.rs b/rc_lobby_room/src/operations/mod.rs new file mode 100644 index 0000000..ffe10cf --- /dev/null +++ b/rc_lobby_room/src/operations/mod.rs @@ -0,0 +1,16 @@ +mod more_auth; +mod eac; +mod no_quit; +mod join_queue; + +use polariton_server::operations::OperationsHandler; + +pub fn handler(_init_ctx: &crate::InitConfig) -> OperationsHandler { + OperationsHandler::::new() + .modify(oj_rc_core::polariton::OpIdCopy) + .add(more_auth::MoreLobbyAuth) + //.add(eac::EacChallengeIgnorer) + //.add(polariton_server::operations::Ack::<2, _>::default()) + .add(no_quit::quit_blocker_provider()) + .add(join_queue::join_queue_provider()) +} diff --git a/rc_lobby_room/src/operations/more_auth.rs b/rc_lobby_room/src/operations/more_auth.rs new file mode 100644 index 0000000..6b0f69f --- /dev/null +++ b/rc_lobby_room/src/operations/more_auth.rs @@ -0,0 +1,42 @@ +use polariton::operation::Typed; +use polariton_server::operations::{Operation, OperationCode}; + +pub struct MoreLobbyAuth; + +impl MoreLobbyAuth { + const AUTH_PAYLOAD_KEY: u8 = 245; +} + +#[async_trait::async_trait] +impl Operation for MoreLobbyAuth { + type User = crate::UserTy; + + async fn handle_async(&self, params: polariton::operation::ParameterTable, user: &Self::User) -> polariton::operation::OperationResponse { + let params_dict = params.to_dict(); + if let Some(Typed::Str(auth_payload)) = params_dict.get(&Self::AUTH_PAYLOAD_KEY) { + //let mut write_lock = user.write().unwrap(); + if user.update_with_auth(&auth_payload.string).await { + let mut resp_params = std::collections::HashMap::new(); + resp_params.insert(Self::AUTH_PAYLOAD_KEY, polariton::operation::Typed::Byte(0)); + return polariton::operation::OperationResponse { + code: Self::op_code(), + return_code: 0, + message: polariton::operation::Typed::Null, + params: resp_params.into(), + } + } + } + polariton::operation::OperationResponse { + code: Self::op_code(), + return_code: 120, + message: polariton::operation::Typed::Null, + params: std::collections::HashMap::new().into(), + } + } +} + +impl OperationCode for MoreLobbyAuth { + fn op_code() -> u8 { + 230 + } +} diff --git a/rc_lobby_room/src/operations/no_quit.rs b/rc_lobby_room/src/operations/no_quit.rs new file mode 100644 index 0000000..61ef0b7 --- /dev/null +++ b/rc_lobby_room/src/operations/no_quit.rs @@ -0,0 +1,26 @@ +use polariton_server::operations::{SimpleOpError, SimpleOperation, SimpleOpImpl}; +use polariton::operation::{ParameterTable, Typed}; + +const CODE: u8 = 3; + +const DID_QUIT_LAST_GAME_PARAM_KEY: u8 = 19; // bool +const BLOCK_TIME_PARAM_KEY: u8 = 15; // int + +pub(super) struct QuitterBlockProvider; + +#[async_trait::async_trait] +impl SimpleOperation for QuitterBlockProvider { + type User = crate::UserTy; + const CODE: u8 = CODE; + + async fn handle(&self, params: ParameterTable, _user: &Self::User) -> Result, SimpleOpError> { + let mut params = params.to_dict(); + params.insert(DID_QUIT_LAST_GAME_PARAM_KEY, Typed::Bool(false)); + params.insert(BLOCK_TIME_PARAM_KEY, Typed::Int(0)); + Ok(params.into()) + } +} + +pub(super) fn quit_blocker_provider() -> SimpleOpImpl { + SimpleOpImpl::new(QuitterBlockProvider) +} diff --git a/rc_services_room/src/data/mod.rs b/rc_services_room/src/data/mod.rs index 5f7568d..fc299dc 100644 --- a/rc_services_room/src/data/mod.rs +++ b/rc_services_room/src/data/mod.rs @@ -23,8 +23,8 @@ pub mod player_rank; pub mod quest; //pub use oj_rc_core::data::auto_regen; //pub use oj_rc_core::data::voting; -pub mod lobby; -pub use oj_rc_core::data::error_codes; +//pub use oj_rc_core::data::lobby; +//pub use oj_rc_core::data::error_codes; //pub use oj_rc_core::data::game_mode; pub mod score_multipliers; //pub use oj_rc_core::data::campaign; diff --git a/rc_services_room/src/operations/validate_machine.rs b/rc_services_room/src/operations/validate_machine.rs index 25cd588..48319d3 100644 --- a/rc_services_room/src/operations/validate_machine.rs +++ b/rc_services_room/src/operations/validate_machine.rs @@ -19,7 +19,7 @@ pub(super) fn validate_robot_provider() -> SimpleFunc<102, crate::UserTy, impl ( SimpleFunc::new(|params, _user: &crate::UserTy| { let mut params = params.to_dict(); if let Some(Typed::Int(lobby_ty)) = params.get(&LOBBY_PARAM_KEY) { - log::info!("Got lobby type {} ({:?})", lobby_ty, crate::data::lobby::LobbyType::from_int(*lobby_ty)); + log::info!("Got lobby type {} ({:?})", lobby_ty, oj_rc_core::data::lobby::LobbyType::from_int(*lobby_ty)); } // let lock = user.read().unwrap(); // let user_info = lock.user()?;