1
0
mirror of https://git.ngram.ca/OpenJam/rc-servers synced 2026-08-23 23:08:52 +00:00

Implement non-event part of singleplayer for #7

This commit is contained in:
NGnius (Graham)
2025-03-18 16:47:11 -04:00
parent 792585a031
commit 67c14fe80a
35 changed files with 2127 additions and 17 deletions

View File

@@ -0,0 +1,13 @@
[package]
name = "rc_singleplayer"
version = "0.1.0"
edition = "2021"
[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
polariton_auth = { version = "*", path = "../polariton_auth" }
polariton_server.workspace = true

3
rc_singleplayer/build_arm64.sh Executable file
View File

@@ -0,0 +1,3 @@
#!/bin/bash
cargo build --release --target aarch64-unknown-linux-musl

3
rc_singleplayer/run_debug.sh Executable file
View File

@@ -0,0 +1,3 @@
#!/bin/bash
RUST_BACKTRACE=1 RUST_LOG=debug cargo run -- -1

View File

@@ -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 = 4538)]
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:4539".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()
}
}

263
rc_singleplayer/src/main.rs Normal file
View File

@@ -0,0 +1,263 @@
mod cli;
mod state;
use 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 sock_state = state::State::new(enc);
while let Ok(packet) = polariton_server::utils::receive_packet_async(&mut socket, &sock_state.serdes_ctx()).await {
match packet {
Packet::Ping(ping) => {
polariton_server::utils::handle_ping_async(ping, &mut socket, &sock_state.serdes_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 = "SinglePlayerServer";
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 polariton_auth::AuthProvider<AuthError> for AuthImpl {
fn validate(&mut self, params: &std::collections::HashMap<u8, Typed>) -> Result<std::collections::HashMap<u8, Typed>, 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::<u8, Typed>::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<polariton_auth::CryptoImpl> {
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, &crypto) {
Ok(x) => x,
Err(h) => match h.extra {
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::<u8, Typed>::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(crypto)
}

View File

@@ -0,0 +1,17 @@
const SERDES_SERDES_CTX_REF: &'static polariton::serdes::SerdesContext<(), polariton::serdes::NoCustomSerdes> = &polariton::serdes::SerdesContext::default_const();
pub struct State {
pub crypto: polariton_auth::CryptoImpl,
}
impl State {
pub fn new(c: polariton_auth::CryptoImpl) -> Self {
Self {
crypto: c,
}
}
pub fn serdes_ctx(&self) -> polariton::packet::SerdesContext<'_, (), polariton::serdes::NoCustomSerdes> {
polariton::packet::SerdesContext::new(SERDES_SERDES_CTX_REF, &self.crypto)
}
}