This commit is contained in:
Maple 2025-09-21 15:59:27 +02:00
commit aab4414904
71 changed files with 293 additions and 4316 deletions

37
rnex-core/src/common.rs Normal file
View file

@ -0,0 +1,37 @@
use std::fs;
use std::fs::File;
use chrono::{Local, SecondsFormat};
use log::LevelFilter;
use simplelog::{ColorChoice, CombinedLogger, Config, TermLogger, TerminalMode, WriteLogger};
pub fn setup(){
CombinedLogger::init(vec![
TermLogger::new(
LevelFilter::Info,
Config::default(),
TerminalMode::Mixed,
ColorChoice::Auto,
),
WriteLogger::new(LevelFilter::max(), Config::default(), {
fs::create_dir_all("log").unwrap();
let date = Local::now().to_rfc3339_opts(SecondsFormat::Secs, false);
// this fixes windows being windows
let date = date.replace(":", "-");
let filename = format!("{}.log", date);
if cfg!(windows) {
File::create(format!("log\\{}", filename)).unwrap()
} else {
File::create(format!("log/{}", filename)).unwrap()
}
}),
])
.unwrap();
/*ctrlc::set_handler(||{
FORCE_EXIT.call_once_force(|_|{
println!("attempting exit");
});
}).unwrap();*/
dotenv::dotenv().ok();
}

View file

@ -0,0 +1,91 @@
use rnex_core::reggie::{RemoteEdgeNodeHolder, UnitPacketRead};
use log::{error, info};
use once_cell::sync::Lazy;
use rustls::client::danger::HandshakeSignatureValid;
use rustls::pki_types::{CertificateDer, TrustAnchor, UnixTime};
use rustls::server::danger::{ClientCertVerified, ClientCertVerifier};
use rustls::server::{ClientCertVerifierBuilder, WebPkiClientVerifier};
use rustls::{
DigitallySignedStruct, DistinguishedName, Error, RootCertStore, ServerConfig, ServerConnection,
SignatureScheme,
};
use rustls_pki_types::PrivateKeyDer;
use rnex_core::common::setup;
use std::borrow::ToOwned;
use std::{env, fs};
use std::io::Cursor;
use std::net::{IpAddr, Ipv4Addr, SocketAddr, SocketAddrV4};
use std::sync::Arc;
use macros::{method_id, rmc_proto, rmc_struct};
use tokio::io::AsyncReadExt;
use tokio::net::{TcpListener, TcpSocket, TcpStream};
use tokio::task;
use tokio_rustls::TlsAcceptor;
use rnex_core::define_rmc_proto;
use rnex_core::executables::common::{OWN_IP_PRIVATE, SECURE_SERVER_ACCOUNT, SERVER_PORT};
use rnex_core::nex::auth_handler::AuthHandler;
use rnex_core::reggie::EdgeNodeHolderConnectOption::DontRegister;
use rnex_core::rmc::protocols::{new_rmc_gateway_connection, OnlyRemote};
use rnex_core::rmc::response::ErrorCode;
use rnex_core::rmc::structures::RmcSerialize;
use rnex_core::rnex_proxy_common::ConnectionInitData;
use rnex_core::util::SplittableBufferConnection;
pub static FORWARD_EDGE_NODE_HOLDER: Lazy<SocketAddrV4> = Lazy::new(||{
env::var("FORWARD_EDGE_NODE_HOLDER")
.ok()
.and_then(|s| s.parse().ok())
.expect("SECURE_EDGE_NODE_HOLDER not set")
});
#[tokio::main]
async fn main() {
setup();
let conn = TcpStream::connect(&*FORWARD_EDGE_NODE_HOLDER).await.unwrap();
let conn: SplittableBufferConnection = conn.into();
conn.send(DontRegister.to_data()).await;
let conn = new_rmc_gateway_connection(conn, |r| Arc::new(OnlyRemote::<RemoteEdgeNodeHolder>::new(r)));
let listen = TcpListener::bind(SocketAddrV4::new(*OWN_IP_PRIVATE, *SERVER_PORT)).await.unwrap();
while let Ok((mut stream, addr)) = listen.accept().await {
let buffer = match stream.read_buffer().await{
Ok(v) => v,
Err(e) => {
error!("an error ocurred whilest reading connection data buffer: {:?}", e);
continue;
}
};
let user_connection_data = ConnectionInitData::deserialize(&mut Cursor::new(buffer));
let user_connection_data = match user_connection_data{
Ok(v) => v,
Err(e) => {
error!("an error ocurred whilest reading connection data: {:?}", e);
continue;
}
};
let controller = conn.clone();
task::spawn(async move {
info!("connection to secure backend established");
new_rmc_gateway_connection(stream.into(), |_| {
Arc::new(AuthHandler {
destination_server_acct: &SECURE_SERVER_ACCOUNT,
build_name: "branch:origin/project/wup-agmj build:3_8_15_2004_0",
control_server: controller
})
});
});
}
}

View file

@ -0,0 +1,74 @@
use std::io::Cursor;
use rnex_core::rmc::structures::RmcSerialize;
use rnex_core::reggie::{RemoteEdgeNodeHolder, UnitPacketRead};
use std::net::SocketAddrV4;
use std::sync::Arc;
use std::sync::atomic::AtomicU32;
use log::{error, info};
use tokio::net::{TcpListener, TcpStream};
use tokio::task;
use rnex_core::common::setup;
use rnex_core::executables::common::{OWN_IP_PRIVATE, SERVER_PORT};
use rnex_core::nex::matchmake::MatchmakeManager;
use rnex_core::nex::remote_console::RemoteConsole;
use rnex_core::nex::user::User;
use rnex_core::reggie::EdgeNodeHolderConnectOption::DontRegister;
use rnex_core::rmc::protocols::{new_rmc_gateway_connection, OnlyRemote};
use rnex_core::rnex_proxy_common::ConnectionInitData;
use rnex_core::rmc::protocols::RemoteInstantiatable;
use rnex_core::util::SplittableBufferConnection;
#[tokio::main]
async fn main() {
setup();
let listen = TcpListener::bind(SocketAddrV4::new(*OWN_IP_PRIVATE, *SERVER_PORT)).await.unwrap();
let mmm = Arc::new(MatchmakeManager{
gid_counter: AtomicU32::new(1),
sessions: Default::default(),
users: Default::default(),
rv_cid_counter: AtomicU32::new(1),
});
let weak_mmm = Arc::downgrade(&mmm);
MatchmakeManager::initialize_garbage_collect_thread(weak_mmm).await;
while let Ok((mut stream, addr)) = listen.accept().await {
let buffer = match stream.read_buffer().await{
Ok(v) => v,
Err(e) => {
error!("an error ocurred whilest reading connection data buffer: {:?}", e);
continue;
}
};
let user_connection_data = ConnectionInitData::deserialize(&mut Cursor::new(buffer));
let user_connection_data = match user_connection_data{
Ok(v) => v,
Err(e) => {
error!("an error ocurred whilest reading connection data: {:?}", e);
continue;
}
};
let mmm = mmm.clone();
task::spawn(async move {
info!("connection to secure backend established");
new_rmc_gateway_connection(stream.into(), |r| {
Arc::new_cyclic(|this| User{
this: this.clone(),
ip: user_connection_data.prudpsock_addr,
pid: user_connection_data.pid,
remote: RemoteConsole::new(r),
matchmake_manager: mmm,
station_url: Default::default()
})
});
});
}
}

View file

@ -0,0 +1,44 @@
use std::env;
use std::net::{Ipv4Addr, SocketAddrV4};
use macros::{method_id, rmc_proto, RmcSerialize};
use once_cell::sync::Lazy;
use tonic::transport::Server;
use crate::define_rmc_proto;
use crate::prudp::station_url::StationUrl;
use crate::nex::account::Account;
use crate::rmc::response::ErrorCode;
pub static OWN_IP_PRIVATE: Lazy<Ipv4Addr> = Lazy::new(|| {
env::var("SERVER_IP")
.ok()
.and_then(|s| s.parse().ok())
.expect("SERVER_IP not specified")
});
pub static OWN_IP_PUBLIC: Lazy<Ipv4Addr> = Lazy::new(|| {
env::var("SERVER_IP_PUBLIC")
.ok()
.and_then(|s| s.parse().ok())
.expect("SERVER_IP_PUBLIC not specified")
});
pub static SERVER_PORT: Lazy<u16> = Lazy::new(|| {
env::var("SERVER_PORT")
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(10000)
});
pub static KERBEROS_SERVER_PASSWORD: Lazy<String> = Lazy::new(|| {
env::var("AUTH_SERVER_PASSWORD")
.ok()
.unwrap_or("password".to_owned())
});
pub static AUTH_SERVER_ACCOUNT: Lazy<Account> =
Lazy::new(|| Account::new(1, "Quazal Authentication", &KERBEROS_SERVER_PASSWORD));
pub static SECURE_SERVER_ACCOUNT: Lazy<Account> =
Lazy::new(|| Account::new(2, "Quazal Rendez-Vous", &KERBEROS_SERVER_PASSWORD));

View file

@ -0,0 +1,92 @@
use std::io::Cursor;
use std::net::SocketAddrV4;
use std::sync::{Arc, Weak};
use log::error;
use macros::rmc_struct;
use tokio::net::TcpListener;
use tokio::sync::RwLock;
use rnex_core::common::setup;
use rnex_core::executables::common::{OWN_IP_PRIVATE, SERVER_PORT};
use rnex_core::reggie::{EdgeNodeHolderConnectOption, EdgeNodeManagement, LocalEdgeNodeHolder};
use rnex_core::rmc::protocols::new_rmc_gateway_connection;
use rnex_core::rmc::response::ErrorCode;
use rnex_core::util::SplittableBufferConnection;
use rnex_core::rmc::structures::RmcSerialize;
#[rmc_struct(EdgeNodeHolder)]
struct EdgeNode{
data_holder: Arc<DataHolder>,
address: SocketAddrV4
}
impl EdgeNodeManagement for EdgeNode{
async fn get_url(&self, seed: u64) -> Result<SocketAddrV4, ErrorCode> {
self.data_holder.get_url(seed).await
}
}
#[rmc_struct(EdgeNodeHolder)]
#[derive(Default)]
struct DataHolder{
edge_nodes: RwLock<Vec<Weak<EdgeNode>>>
}
impl EdgeNodeManagement for DataHolder{
async fn get_url(&self, seed: u64) -> Result<SocketAddrV4, ErrorCode> {
let nodes = self.edge_nodes.read().await;
let nodes: Vec<_> = nodes.iter().filter_map(|n| n.upgrade()).collect();
// avoid a devide by zero
if nodes.len() == 0{
return Err(ErrorCode::Core_InvalidIndex);
};
let node = &nodes[seed as usize % nodes.len()];
Ok(node.address)
}
}
#[tokio::main]
async fn main() {
setup();
let listen = TcpListener::bind(SocketAddrV4::new(*OWN_IP_PRIVATE, *SERVER_PORT)).await.unwrap();
let holder: Arc<DataHolder> = Default::default();
while let Ok((mut stream, addr)) = listen.accept().await {
let mut conn: SplittableBufferConnection = stream.into();
let Some(data) = conn.recv().await else {
continue;
};
let Ok(data) = EdgeNodeHolderConnectOption::deserialize(&mut Cursor::new(data)) else {
continue;
};
let holder = holder.clone();
match data{
EdgeNodeHolderConnectOption::DontRegister => {
new_rmc_gateway_connection(conn, |_| holder);
},
EdgeNodeHolderConnectOption::Register(address) => {
let edge_node = EdgeNode{
address,
data_holder: holder.clone()
};
let node = new_rmc_gateway_connection(conn, move |_| Arc::new(edge_node));
let mut nodes = holder.edge_nodes.write().await;
nodes.push(Arc::downgrade(&node));
}
}
}
}

View file

@ -0,0 +1 @@
pub mod common;

View file

@ -0,0 +1,156 @@
use std::{env, result};
use std::array::TryFromSliceError;
use std::str::FromStr;
use json::{object, JsonValue};
use once_cell::sync::Lazy;
use reqwest::{Body, Method, Url};
use reqwest::header::HeaderValue;
use thiserror::Error;
use crate::grpc::account::Error::SomethingHappened;
static API_KEY: Lazy<String> = Lazy::new(||{
let key = env::var("ACCOUNT_GQL_API_KEY")
.expect("no graphql ip specified");
key
});
static CLIENT_URI: Lazy<String> = Lazy::new(||{
env::var("ACCOUNT_GQL_URL")
.ok()
.and_then(|s| s.parse().ok())
.expect("no graphql ip specified")
});
#[derive(Error, Debug)]
pub enum Error{
#[error(transparent)]
Creation(#[from] reqwest::Error),
#[error(transparent)]
Json(#[from] json::Error),
#[error(transparent)]
Status(#[from] tonic::Status),
#[error("invalid password size: {0}")]
PasswordConversion(#[from] TryFromSliceError),
#[error("something happened")]
SomethingHappened
}
pub type Result<T> = result::Result<T, Error>;
pub struct Client(reqwest::Client);
impl Client{
pub async fn new() -> Result<Self> {
Ok(Self(reqwest::ClientBuilder::new().build()?))
}
async fn do_request(&self, request_data: JsonValue) -> Result<JsonValue>{
let mut request = reqwest::Request::new(Method::POST, Url::from_str(CLIENT_URI.as_str()).unwrap());
*(request.body_mut()) = Some(Body::from(request_data.to_string()));
request.headers_mut().insert("X-API-Key", HeaderValue::from_str(&API_KEY).unwrap());
request.headers_mut().insert("Content-Type", HeaderValue::from_str("application/json").unwrap());
let response = self.0.execute(request).await?;
Ok(json::parse(&response.text().await?)?)
}
pub async fn get_nex_password(&mut self , pid: u32) -> Result<[u8; 16]>{
let req = self.do_request(object!{
"query": r"query($pid: Int!){
userByPid(pid: $pid){
nexPassword
}
}",
"variables": {
"pid": pid
}
}).await?;
let Some(val) = req.entries()
.find(|v| v.0 == "data")
.ok_or(SomethingHappened)?.1
.entries()
.find(|v| v.0 == "userByPid")
.ok_or(SomethingHappened)?.1
.entries()
.find(|v| v.0 == "nexPassword")
.ok_or(SomethingHappened)?.1
.as_str() else {
return Err(SomethingHappened);
};
Ok(val.as_bytes().try_into().map_err(|_| SomethingHappened)?)
}
/*pub async fn get_user_data(&mut self , pid: u32) -> Result<GetUserDataResponse>{
let req = Request::new(GetUserDataRequest{
pid
});
let response = self.0.get_user_data(req).await?.into_inner();
Ok(response)
}*/
}
/*
pub struct Client(AccountClient<InterceptedService<Channel, InterceptorFunc>>);
impl Client{
pub async fn new() -> Result<Self>{
let channel = Channel::from_static(&*CLIENT_URI).connect().await?;
let func = Box::new(&|mut req: Request<()>|{
req.metadata_mut().insert("x-api-key", API_KEY.clone());
Ok(req)
}) as InterceptorFunc;
let client = AccountClient::with_interceptor(channel, func);
Ok(Self(client))
}
pub async fn get_nex_password(&mut self , pid: u32) -> Result<[u8; 16]>{
let req = Request::new(GetNexPasswordRequest{
pid
});
let response = self.0.get_nex_password(req).await?.into_inner();
Ok(response.password.as_bytes().try_into()?)
}
pub async fn get_user_data(&mut self , pid: u32) -> Result<GetUserDataResponse>{
let req = Request::new(GetUserDataRequest{
pid
});
let response = self.0.get_user_data(req).await?.into_inner();
Ok(response)
}
}
*/
#[cfg(test)]
mod test{
use crate::grpc::account::Client;
#[tokio::test]
async fn test(){
dotenv::dotenv().ok();
let mut client = Client::new().await.unwrap();
let cli = client.get_nex_password(1699562916).await.unwrap();
println!("{:?}", cli);
}
}

View file

@ -0,0 +1,8 @@
//! Legacy grpc communication server for being able to use this with pretendos infrastructure
//! before account rs is finished.
//!
//! This WILL be deprecated as soon as account rs is in a stable state.
use tonic::{Request, Status};
type InterceptorFunc = Box<(dyn Fn(Request<()>) -> Result<Request<()>, Status> + Send)>;
pub mod account;

View file

@ -0,0 +1,170 @@
use std::io::{Read, Write};
use bytemuck::{bytes_of, Pod, Zeroable};
use chrono::{Datelike, NaiveDate, NaiveDateTime, NaiveTime, Timelike, Utc};
use hmac::Hmac;
use md5::{Digest, Md5};
use rc4::{Rc4, Rc4Core, StreamCipher};
use rc4::cipher::StreamCipherCoreWrapper;
use rc4::consts::U16;
use hmac::Mac;
use rc4::KeyInit;
use crate::rmc::structures::RmcSerialize;
type Md5Hmac = Hmac<md5::Md5>;
pub fn derive_key(pid: u32, password: [u8; 16]) -> [u8; 16]{
let iteration_count = 65000 + pid%1024;
let mut key = password;
for _ in 0..iteration_count {
let mut md5 = Md5::new();
md5.update(key);
key = md5.finalize().try_into().unwrap();
}
key
}
#[derive(Pod, Zeroable, Copy, Clone, Debug, Eq, PartialEq, Default)]
#[repr(transparent)]
pub struct KerberosDateTime(pub u64);
impl KerberosDateTime{
pub fn new(second: u64, minute: u64, hour: u64, day: u64, month: u64, year:u64 ) -> Self {
Self(second | (minute << 6) | (hour << 12) | (day << 17) | (month << 22) | (year << 26))
}
pub fn now() -> Self{
let now = chrono::Utc::now();
Self::new(
now.second() as u64,
now.minute() as u64,
now.hour() as u64,
now.day() as u64,
now.month() as u64,
now.year() as u64,
)
}
#[inline]
pub fn get_seconds(&self) -> u8{
(self.0 & 0b111111) as u8
}
#[inline]
pub fn get_minutes(&self) -> u8{
((self.0 >> 6) & 0b111111) as u8
}
#[inline]
pub fn get_hours(&self) -> u8{
((self.0 >> 12) & 0b111111) as u8
}
#[inline]
pub fn get_days(&self) -> u8{
((self.0 >> 17) & 0b111111) as u8
}
#[inline]
pub fn get_month(&self) -> u8{
((self.0 >> 22) & 0b1111) as u8
}
#[inline]
pub fn get_year(&self) -> u64{
(self.0 >> 26) & 0xFFFFFFFF
}
pub fn to_regular_time(&self) -> chrono::DateTime<Utc>{
NaiveDateTime::new(
NaiveDate::from_ymd_opt(self.get_year() as i32, self.get_month() as u32, self.get_days() as u32).unwrap(),
NaiveTime::from_hms_opt(self.get_hours() as u32, self.get_minutes() as u32, self.get_seconds() as u32).unwrap()
).and_utc()
}
}
impl RmcSerialize for KerberosDateTime{
fn serialize(&self, writer: &mut dyn Write) -> crate::rmc::structures::Result<()> {
Ok(self.0.serialize(writer)?)
}
fn deserialize(reader: &mut dyn Read) -> crate::rmc::structures::Result<Self> {
Ok(Self(u64::deserialize(reader)?))
}
}
#[derive(Pod, Zeroable, Copy, Clone)]
#[repr(C, packed)]
pub struct TicketInternalData{
pub issued_time: KerberosDateTime,
pub pid: u32,
pub session_key: [u8; 32],
}
impl TicketInternalData{
pub(crate) fn new(pid: u32) -> Self{
Self{
issued_time: KerberosDateTime::now(),
pid,
session_key: rand::random()
}
}
pub(crate) fn encrypt(&self, key: [u8; 16]) -> Box<[u8]>{
let mut data = bytes_of(self).to_vec();
let mut rc4: StreamCipherCoreWrapper<Rc4Core<U16>> = Rc4::new_from_slice(&key).unwrap();
rc4.apply_keystream(&mut data);
let mut hmac = <Md5Hmac as KeyInit>::new_from_slice(&key).unwrap();
hmac.write_all(&data[..]).expect("failed to write data to hmac");
let hmac_result = &hmac.finalize().into_bytes()[..];
data.write_all(&hmac_result).expect("failed to write data to vec");
data.into_boxed_slice()
}
}
#[derive(Pod, Zeroable, Copy, Clone)]
#[repr(C, packed)]
pub struct Ticket{
pub session_key: [u8; 32],
pub pid: u32,
}
impl Ticket{
pub fn encrypt(&self, key: [u8; 16], internal_data: &[u8]) -> Box<[u8]>{
let mut data = bytes_of(self).to_vec();
internal_data.serialize(&mut data).expect("unable to write to vec");
let mut rc4: StreamCipherCoreWrapper<Rc4Core<U16>> = Rc4::new_from_slice(&key).unwrap();
rc4.apply_keystream(&mut data);
let mut hmac = <Md5Hmac as KeyInit>::new_from_slice(&key).unwrap();
hmac.write_all(&data[..]).expect("failed to write data to hmac");
let hmac_result = &hmac.finalize().into_bytes()[..];
data.write_all(&hmac_result).expect("failed to write data to vec");
data.into_boxed_slice()
}
}
#[cfg(test)]
mod test{
use chrono::{Datelike, Utc};
use crate::kerberos::KerberosDateTime;
#[test]
fn kerberos_time_convert_test(){
let time = KerberosDateTime(135904948834);
println!("{}", time.to_regular_time().to_rfc2822());
}
}

26
rnex-core/src/lib.rs Normal file
View file

@ -0,0 +1,26 @@
#![allow(dead_code)]
// rnex makes extensive use of async functions in public traits
// this is however fine because these traits should never(and i mean NEVER) be used dynamically
#![allow(async_fn_in_trait)]
//#![warn(missing_docs)]
extern crate self as rnex_core;
pub mod prudp;
pub mod rmc;
//mod protocols;
pub mod grpc;
pub mod kerberos;
pub mod nex;
pub mod result;
pub mod versions;
pub mod web;
pub mod common;
pub mod reggie;
pub mod rnex_proxy_common;
pub mod util;
pub mod executables;
pub use macros::*;

419
rnex-core/src/main.rs Normal file
View file

@ -0,0 +1,419 @@
#![allow(dead_code)]
#![allow(async_fn_in_trait)]
//#![warn(missing_docs)]
//! # Splatoon RNEX server
//!
//! This server still includes the code for rnex itself as this is the first rnex server and thus
//! also the first and only current usage of rnex, expect this and rnex to be split into seperate
//! repos soon.
extern crate self as rust_nex;
use crate::nex::account::Account;
use crate::nex::auth_handler::{AuthHandler, RemoteAuthClientProtocol};
use crate::nex::remote_console::RemoteConsole;
use crate::nex::user::{RemoteUserProtocol, User};
use crate::rmc::protocols::auth::Auth;
use crate::rmc::protocols::auth::RawAuth;
use crate::rmc::protocols::auth::RawAuthInfo;
use crate::rmc::protocols::auth::RemoteAuth;
use crate::rmc::protocols::matchmake_extension::RemoteMatchmakeExtension;
use crate::rmc::protocols::{new_rmc_gateway_connection, OnlyRemote, RemoteInstantiatable};
use crate::rmc::response::ErrorCode;
use crate::rmc::structures::any::Any;
use crate::rmc::structures::connection_data::ConnectionData;
use crate::rmc::structures::matchmake::{CreateMatchmakeSessionParam, Gathering, MatchmakeParam, MatchmakeSession};
use crate::rmc::structures::qresult::QResult;
use chrono::{Local, SecondsFormat};
use log::{error, info};
use macros::rmc_struct;
use once_cell::sync::Lazy;
use simplelog::{
ColorChoice, CombinedLogger, Config, LevelFilter, TermLogger, TerminalMode, WriteLogger,
};
use std::fs::File;
use std::marker::PhantomData;
use std::net::{Ipv4Addr, SocketAddrV4};
use std::ops::{BitAnd, BitOr};
use std::str::FromStr;
use std::sync::{Arc, Once, Weak};
use std::time::Duration;
use std::{env, fs};
use std::sync::atomic::AtomicU32;
use tokio::task::JoinHandle;
use crate::kerberos::KerberosDateTime;
use crate::nex::matchmake::MatchmakeManager;
use crate::rmc::protocols::secure::RemoteSecure;
mod prudp;
pub mod rmc;
//mod protocols;
mod grpc;
mod kerberos;
mod nex;
mod result;
mod versions;
mod web;
pub mod reggie;
pub mod util;
pub mod common;
static KERBEROS_SERVER_PASSWORD: Lazy<String> = Lazy::new(|| {
env::var("AUTH_SERVER_PASSWORD")
.ok()
.unwrap_or("password".to_owned())
});
static AUTH_SERVER_ACCOUNT: Lazy<Account> =
Lazy::new(|| Account::new(1, "Quazal Authentication", &KERBEROS_SERVER_PASSWORD));
static SECURE_SERVER_ACCOUNT: Lazy<Account> =
Lazy::new(|| Account::new(2, "Quazal Rendez-Vous", &KERBEROS_SERVER_PASSWORD));
static AUTH_SERVER_PORT: Lazy<u16> = Lazy::new(|| {
env::var("AUTH_SERVER_PORT")
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(10000)
});
static SECURE_SERVER_PORT: Lazy<u16> = Lazy::new(|| {
env::var("SECURE_SERVER_PORT")
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(10001)
});
static OWN_IP_PRIVATE: Lazy<Ipv4Addr> = Lazy::new(|| {
env::var("SERVER_IP")
.ok()
.and_then(|s| s.parse().ok())
.expect("no private ip specified")
});
static OWN_IP_PUBLIC: Lazy<String> =
Lazy::new(|| env::var("SERVER_IP_PUBLIC").unwrap_or(OWN_IP_PRIVATE.to_string()));
static SECURE_STATION_URL: Lazy<String> = Lazy::new(|| {
format!(
"prudps:/PID=2;sid=1;stream=10;type=2;address={};port={};CID=1",
*OWN_IP_PUBLIC, *SECURE_SERVER_PORT
)
});
static FORCE_EXIT: Once = Once::new();
#[tokio::main]
async fn main() {
CombinedLogger::init(vec![
TermLogger::new(
LevelFilter::Info,
Config::default(),
TerminalMode::Mixed,
ColorChoice::Auto,
),
WriteLogger::new(LevelFilter::max(), Config::default(), {
fs::create_dir_all("log").unwrap();
let date = Local::now().to_rfc3339_opts(SecondsFormat::Secs, false);
// this fixes windows being windows
let date = date.replace(":", "-");
let filename = format!("{}.log", date);
if cfg!(windows) {
File::create(format!("log\\{}", filename)).unwrap()
} else {
File::create(format!("log/{}", filename)).unwrap()
}
}),
])
.unwrap();
ctrlc::set_handler(||{
FORCE_EXIT.call_once_force(|_|{
println!("attempting exit");
});
}).unwrap();
dotenv::dotenv().ok();
//start_servers().await;
}
/*
struct AuthServer{
router: Arc<Router>,
join_handle: JoinHandle<()>,
socket: Socket
}
async fn start_auth_server() -> AuthServer{
info!("starting auth server on {}:{}", *OWN_IP_PRIVATE, *AUTH_SERVER_PORT);
let (router, join_handle) =
Router::new(SocketAddrV4::new(*OWN_IP_PRIVATE, *AUTH_SERVER_PORT)).await
.expect("unable to startauth server");
info!("setting up endpoints");
// dont assign it to the name _ as that will make it drop right here and now
let auth_protocol_config = AuthProtocolConfig{
secure_server_account: &SECURE_SERVER_ACCOUNT,
build_name: "branch:origin/project/wup-agmj build:3_8_15_2004_0",
station_url: &SECURE_STATION_URL
};
let rmcserver = RMCProtocolServer::new(Box::new([
Box::new(auth::bound_protocol(auth_protocol_config))
]));
let socket =
Socket::new(
router.clone(),
VirtualPort::new(1,10),
"6f599f81",
Box::new(|_, count|{
Box::pin(
async move {
let encryption_pairs = Vec::from_iter((0..=count).map(|_v| {
let rc4: Rc4<U5> = Rc4::new_from_slice( "CD&ML".as_bytes()).unwrap();
let cypher = Box::new(rc4);
let server_cypher: Box<dyn StreamCipher + Send> = cypher;
let rc4: Rc4<U5> = Rc4::new_from_slice( "CD&ML".as_bytes()).unwrap();
let cypher = Box::new(rc4);
let client_cypher: Box<dyn StreamCipher + Send> = cypher;
EncryptionPair{
recv: client_cypher,
send: server_cypher
}
}));
Some((Vec::new(), encryption_pairs, None))
}
)
}),
Box::new(move |packet, socket, connection|{
let rmcserver = rmcserver.clone();
Box::pin(async move { rmcserver.process_message(packet, socket, connection).await; })
})
).await.expect("unable to create socket");
AuthServer{
join_handle,
router,
socket,
}
}
struct SecureServer{
router: Arc<Router>,
join_handle: JoinHandle<()>,
socket: Socket
}
async fn start_secure_server() -> SecureServer{
info!("starting secure server on {}:{}", *OWN_IP_PRIVATE, *SECURE_SERVER_PORT);
let (router, join_handle) =
Router::new(SocketAddrV4::new(*OWN_IP_PRIVATE, *SECURE_SERVER_PORT)).await
.expect("unable to startauth server");
info!("setting up endpoints");
let matchmake_data = Arc::new(RwLock::new(
MatchmakeData{
matchmake_sessions: BTreeMap::new()
}
));
let rmcserver = RMCProtocolServer::new(Box::new([
Box::new(block_if_maintenance),
Box::new(protocols::secure::bound_protocol()),
Box::new(protocols::matchmake::bound_protocol(matchmake_data.clone())),
Box::new(protocols::matchmake_extension::bound_protocol(matchmake_data)),
Box::new(protocols::nat_traversal::bound_protocol())
]));
let socket =
Socket::new(
router.clone(),
VirtualPort::new(1,10),
"6f599f81",
Box::new(|p, count|{
Box::pin(
async move {
let (session_key, pid, check_value) = read_secure_connection_data(&p.payload, &SECURE_SERVER_ACCOUNT)?;
let check_value_response = check_value + 1;
let data = bytemuck::bytes_of(&check_value_response);
let mut response = Vec::new();
data.serialize(&mut response).ok()?;
let encryption_pairs = generate_secure_encryption_pairs(session_key, count);
Some((response, encryption_pairs, Some(
ActiveSecureConnectionData{
pid,
session_key
}
)))
}
)
}),
Box::new(move |packet, socket, connection|{
let rmcserver = rmcserver.clone();
Box::pin(async move { rmcserver.process_message(packet, socket, connection).await; })
})
).await.expect("unable to create socket");
SecureServer{
join_handle,
router,
socket,
}
}*/
/*
async fn start_auth() -> JoinHandle<()> {
tokio::spawn(async {
let (router_secure, _) = Router::new(SocketAddrV4::new(*OWN_IP_PRIVATE, *AUTH_SERVER_PORT))
.await
.expect("unable to start router");
let mut socket_secure = router_secure
.add_socket(VirtualPort::new(1, 10), Unsecure(
"6f599f81"
))
.await
.expect("unable to add socket");
// let conn = socket_secure.connect(auth_sockaddr).await.unwrap();
while !FORCE_EXIT.is_completed() {
let Some(conn) = socket_secure.accept().await else {
error!("server crashed");
return;
};
info!("new connected user!");
let _ = new_rmc_gateway_connection(conn, |_| {
Arc::new(AuthHandler {
destination_server_acct: &SECURE_SERVER_ACCOUNT,
build_name: "branch:origin/project/wup-agmj build:3_8_15_2004_0",
station_url: &SECURE_STATION_URL,
})
});
}
})
}
async fn start_secure() -> JoinHandle<()> {
tokio::spawn(async {
let mmm = Arc::new(MatchmakeManager{
gid_counter: AtomicU32::new(1),
sessions: Default::default(),
users: Default::default(),
rv_cid_counter: AtomicU32::new(1),
});
let weak_mmm = Arc::downgrade(&mmm);
MatchmakeManager::initialize_garbage_collect_thread(weak_mmm).await;
let web_server = web::start_web(mmm.clone()).await;
let (router_secure, _) =
Router::new(SocketAddrV4::new(*OWN_IP_PRIVATE, *SECURE_SERVER_PORT))
.await
.expect("unable to start router");
let mut socket_secure = router_secure
.add_socket(
VirtualPort::new(1, 10),
Secure(
"6f599f81",
&SECURE_SERVER_ACCOUNT
),
)
.await
.expect("unable to add socket");
// let conn = socket_secure.connect(auth_sockaddr).await.unwrap();
while !FORCE_EXIT.is_completed() {
let Some(conn) = socket_secure.accept().await else {
error!("server crashed");
return;
};
info!("new connected user on secure :D!");
let ip = conn.socket_addr;
let pid = conn.user_id;
let _ = new_rmc_gateway_connection(conn, |r| {
Arc::new_cyclic(|w| User {
ip,
pid,
this: w.clone(),
remote: RemoteConsole::new(r),
station_url: Default::default(),
matchmake_manager: mmm.clone()
})
});
}
})
}
async fn start_test() {
let addr = SocketAddrV4::new(*OWN_IP_PRIVATE, *SECURE_SERVER_PORT);
let virt_addr = VirtualPort::new(1, 10);
let prudp_addr = PRUDPSockAddr::new(addr, virt_addr);
let (router_test, _) = Router::new(SocketAddrV4::new(*OWN_IP_PRIVATE, 26969))
.await
.expect("unable to start router");
let mut socket_secure = router_test
.add_socket(VirtualPort::new(1, 10), Unsecure("6f599f81"))
.await
.expect("unable to add socket");
let conn = socket_secure.connect(prudp_addr).await.unwrap();
let remote = new_rmc_gateway_connection(conn, |r| {
Arc::new(OnlyRemote::<RemoteUserProtocol>::new(r))
});
tokio::time::sleep(Duration::from_secs(1)).await;
let urls = vec!["prudp:/address=192.168.178.45;port=60146;Pl=2;natf=0;natm=0;pmp=0;sid=15;upnp=0".to_owned()];
}
async fn start_servers() {
#[cfg(feature = "auth")]
let auth_server = start_auth().await;
#[cfg(feature = "secure")]
let secure_server = start_secure().await;
tokio::time::sleep(Duration::from_secs(1)).await;
//start_test().await;
#[cfg(feature = "auth")]
auth_server.await.expect("auth server crashed");
#[cfg(feature = "secure")]
secure_server.await.expect("auth server crashed");
}
*/

View file

@ -0,0 +1,39 @@
use macros::RmcSerialize;
#[derive(RmcSerialize)]
#[derive(Clone)]
pub struct Account{
pub pid: u32,
pub username: String,
pub kerbros_password: [u8; 16],
}
impl Account{
pub fn new(pid: u32, username: &str, passwd: &str) -> Self{
let passwd_data = passwd.as_bytes();
let mut passwd = [0u8; 16];
for (idx, byte) in passwd_data.iter().enumerate(){
passwd[idx] = *byte;
}
Self{
kerbros_password: passwd,
username: username.into(),
pid
}
}
pub fn new_raw_password(pid: u32, username: &str, passwd: [u8; 16]) -> Self{
Self{
kerbros_password: passwd,
username: username.into(),
pid
}
}
pub fn get_login_data(&self) -> (u32, [u8; 16]){
(self.pid, self.kerbros_password)
}
}

View file

@ -0,0 +1,187 @@
use std::hash::{DefaultHasher, Hasher};
use std::net::SocketAddrV4;
use std::sync::Arc;
use crate::grpc::account;
use rnex_core::kerberos::{derive_key, KerberosDateTime, Ticket};
use rnex_core::nex::account::Account;
use rnex_core::rmc::protocols::auth::{Auth, RawAuth, RawAuthInfo, RemoteAuth};
use rnex_core::rmc::response::ErrorCode;
use rnex_core::rmc::response::ErrorCode::Core_Unknown;
use rnex_core::rmc::structures::any::Any;
use rnex_core::rmc::structures::connection_data::ConnectionData;
use rnex_core::rmc::structures::qresult::QResult;
use crate::{define_rmc_proto, kerberos};
use macros::rmc_struct;
use crate::reggie::{RemoteEdgeNodeHolder, RemoteEdgeNodeManagement};
use rnex_core::rmc::protocols::OnlyRemote;
define_rmc_proto!(
proto AuthClientProtocol{
Auth
}
);
#[rmc_struct(AuthClientProtocol)]
pub struct AuthHandler {
pub destination_server_acct: &'static Account,
pub build_name: &'static str,
//pub station_url: &'static str,
pub control_server: Arc<OnlyRemote<RemoteEdgeNodeHolder>>,
}
pub fn generate_ticket(
source_act_login_data: (u32, [u8; 16]),
dest_act_login_data: (u32, [u8; 16]),
) -> Box<[u8]> {
let source_key = derive_key(source_act_login_data.0, source_act_login_data.1);
let dest_key = derive_key(dest_act_login_data.0, dest_act_login_data.1);
let internal_data = kerberos::TicketInternalData::new(source_act_login_data.0);
let encrypted_inner = internal_data.encrypt(dest_key);
let encrypted_session_ticket = Ticket {
pid: dest_act_login_data.0,
session_key: internal_data.session_key,
}
.encrypt(source_key, &encrypted_inner);
encrypted_session_ticket
}
async fn get_login_data_by_pid(pid: u32) -> Option<(u32, [u8; 16])> {
let Ok(mut client) = account::Client::new().await else {
return None;
};
let Ok(passwd) = client.get_nex_password(pid).await else {
return None;
};
Some((pid, passwd))
}
fn station_url_from_sock_addr(sock_addr: SocketAddrV4) -> String{
format!(
"prudps:/PID=2;sid=1;stream=10;type=2;address={};port={};CID=1",
sock_addr.ip(), sock_addr.port()
)
}
impl Auth for AuthHandler {
async fn login(&self, _name: String) -> Result<(), ErrorCode> {
todo!()
}
async fn login_ex(
&self,
name: String,
_extra_data: Any,
) -> Result<(QResult, u32, Vec<u8>, ConnectionData, String), ErrorCode> {
let Ok(pid) = name.parse() else {
return Err(ErrorCode::Core_InvalidArgument);
};
let Ok(mut client) = account::Client::new().await else {
return Err(ErrorCode::Core_Exception);
};
let Ok(passwd) = client.get_nex_password(pid).await else {
return Err(ErrorCode::Core_Exception);
};
let source_login_data = (pid, passwd);
let destination_login_data = self.destination_server_acct.get_login_data();
let ticket = generate_ticket(source_login_data, destination_login_data);
let result = QResult::success(Core_Unknown);
let mut hasher = DefaultHasher::new();
hasher.write(name.as_bytes());
let Ok(addr) = self.control_server.get_url(hasher.finish()).await else {
return Err(ErrorCode::Core_Exception);
};
let connection_data = ConnectionData {
station_url: station_url_from_sock_addr(addr),
special_station_url: "".to_string(),
//date_time: KerberosDateTime::new(1,1,1,1,1,1),
date_time: KerberosDateTime::now(),
special_protocols: Vec::new(),
};
Ok((
result,
source_login_data.0,
ticket.into(),
connection_data,
self.build_name.to_string() //format!("{}; Rust NEX Version {} by DJMrTV", self.build_name, env!("CARGO_PKG_VERSION")),
))
}
async fn request_ticket(
&self,
source_pid: u32,
destination_pid: u32,
) -> Result<(QResult, Vec<u8>), ErrorCode> {
let Some(source_login_data) = get_login_data_by_pid(source_pid).await else {
return Err(ErrorCode::Core_Exception);
};
let desgination_login_data = if destination_pid == self.destination_server_acct.pid {
self.destination_server_acct.get_login_data()
} else {
let Some(login) = get_login_data_by_pid(destination_pid).await else {
return Err(ErrorCode::Core_Exception);
};
login
};
let result = QResult::success(Core_Unknown);
let ticket = generate_ticket(source_login_data, desgination_login_data);
Ok((result, ticket.into()))
}
async fn get_pid(&self, _username: String) -> Result<u32, ErrorCode> {
Err(ErrorCode::Core_Exception)
}
async fn get_name(&self, _pid: u32) -> Result<String, ErrorCode> {
Err(ErrorCode::Core_Exception)
}
}
#[cfg(test)]
mod test {
use rnex_core::rmc::structures::connection_data::ConnectionData;
use rnex_core::rmc::structures::qresult::QResult;
use rnex_core::rmc::structures::RmcSerialize;
use rnex_core::rmc::response::RMCResponse;
use std::io::Cursor;
#[test]
fn test() {
let stuff = hex::decode("200100000a0106000000028000000100010051b3995774000000a6321c7f78847c1c5e9fb825eb26bd91841f1a40d92fc694159666119cb13527f1463ac48ad42a63e6613ede67041554b1770978112e6f1f3e177a2bfc75933216dbe38f70133a1eb28e2ae32a4b5c4b0c3e3efd4c02907992e259b257270b57a9dbe7792f4721b07f8fafb9e32d50f2555c616a015c0000004b007072756470733a2f5049443d323b7369643d313b73747265616d3d31303b747970653d323b616464726573733d322e3234332e39352e3131333b706f72743d31303030313b4349443d3100000000000100002c153ba51f00000033006272616e63683a6f726967696e2f70726f6a6563742f7775702d61676d6a206275696c643a335f385f31355f323030345f3000").unwrap();
let stuff = RMCResponse::new(&mut Cursor::new(stuff)).unwrap();
let rnex_core::rmc::response::RMCResponseResult::Success { call_id, method_id, data: stuff} = stuff.response_result else {
panic!()
};
// let stuff = hex::decode("0100010051B399577400000085F1736FCFBE93660275A3FE36FED6C2EFC57222AC99A9219CF54170A415B02DF1463AC48AD42A6307813FDE67041554B177097832ED000F892D9551A09F88E9CB0388DC1BC9527CC7384556A3287B2A349ABBF7E34A5A3EC14C2287CC7F78DA616BC3B03A035347FBD2E9A505C8EF42447CD809015F0000004E007072756470733A2F73747265616D3D31303B747970653D323B616464726573733D3139322E3136382E3137382E3132303B706F72743D31303030313B4349443D313B5049443D323B7369643D310000000000010000CDF53AA51F00000033006272616E63683A6F726967696E2F70726F6A6563742F7775702D61676D6A206275696C643A335F385F31355F323030345F3000").unwrap();
// let stuff = hex::decode("0100010051b399577400000037d3d4814d2b16dd546c94a75d32637b45f856b5abe73cf26cfaa235c5f2c1cef1463ac48ad42a637d873fde67041554b177097880cfa7e10bb810eaf686bfb0a0cf3d65b1f476ebc046d0855327986f557dca14fbb8594883c186b863f2206f22baa0309dbcc81da2f883cb2cdc12628ec7fced015c0000004b007072756470733a2f5049443d323b7369643d313b73747265616d3d31303b747970653d323b616464726573733d322e3234332e39352e3131333b706f72743d31303030313b4349443d310000000000010000b7f33aa51f00000033006272616e63683a6f726967696e2f70726f6a6563742f7775702d61676d6a206275696c643a335f385f31355f323030345f3000").unwrap();
let data = <(QResult, u32, Vec<u8>, ConnectionData, String) as RmcSerialize>::deserialize(
&mut Cursor::new(stuff),
).unwrap();
println!("data: {:?}", data);
}
}

View file

@ -0,0 +1,388 @@
use std::collections::HashMap;
use std::str::FromStr;
use std::sync::{Arc, Weak};
use std::sync::atomic::AtomicU32;
use std::sync::atomic::Ordering::Relaxed;
use std::time::Duration;
use log::info;
use rand::random;
use tokio::sync::{Mutex, RwLock};
use tokio::time::sleep;
use rnex_core::kerberos::KerberosDateTime;
use crate::nex::user::User;
use crate::rmc::protocols::notifications::{NotificationEvent, RemoteNotification};
use rnex_core::rmc::protocols::notifications::notification_types::{HOST_CHANGED, OWNERSHIP_CHANGED};
use rnex_core::rmc::response::ErrorCode;
use rnex_core::rmc::response::ErrorCode::{Core_InvalidArgument, RendezVous_SessionVoid};
use rnex_core::rmc::structures::matchmake::{Gathering, MatchmakeParam, MatchmakeSession, MatchmakeSessionSearchCriteria};
use rnex_core::rmc::structures::matchmake::gathering_flags::PERSISTENT_GATHERING;
use rnex_core::rmc::structures::variant::Variant;
pub struct MatchmakeManager{
pub gid_counter: AtomicU32,
pub sessions: RwLock<HashMap<u32, Arc<Mutex<ExtendedMatchmakeSession>>>>,
pub rv_cid_counter: AtomicU32,
pub users: RwLock<HashMap<u32, Weak<User>>>
}
impl MatchmakeManager{
pub fn next_gid(&self) -> u32{
self.gid_counter.fetch_add(1, Relaxed)
}
pub fn next_cid(&self) -> u32{
self.rv_cid_counter.fetch_add(1, Relaxed)
}
pub async fn get_session(&self, gid: u32) -> Result<Arc<Mutex<ExtendedMatchmakeSession>>, ErrorCode>{
let sessions = self.sessions.read().await;
let Some(session) = sessions.get(&gid) else {
return Err(RendezVous_SessionVoid);
};
let session = session.clone();
drop(sessions);
Ok(session)
}
async fn garbage_collect(&self){
info!("running rnex garbage collector over all sessions and users");
let mut idx = 0;
let mut to_be_deleted_gids = Vec::new();
// i am very well aware of how inefficient doing it like this is but this is the only
// way which i could think of to do this without potentially causing a deadlock of
// the entire server
while let Some((gid, session)) = {
let sessions = self.sessions.read().await;
let session_pair = sessions.iter().nth(idx).map(|s| (*s.0, s.1.clone()));
drop(sessions);
session_pair
}{
let session = session.lock().await;
if !session.is_reachable(){
to_be_deleted_gids.push(gid);
}
idx += 1;
}
let mut sessions = self.sessions.write().await;
for gid in to_be_deleted_gids{
sessions.remove(&gid);
}
}
pub async fn initialize_garbage_collect_thread(this: Weak<Self>){
tokio::spawn(async move {
while let Some(this) = this.upgrade(){
this.garbage_collect().await;
// every 30 minutes
sleep(Duration::from_secs(60 * 30)).await;
}
});
}
}
#[derive(Default, Debug)]
pub struct ExtendedMatchmakeSession{
pub session: MatchmakeSession,
pub connected_players: Vec<Weak<User>>,
}
fn read_bounds_string<T: FromStr>(str: &str) -> Option<(T,T)>{
let bounds = str.split_once(",")?;
Some((T::from_str(bounds.0).ok()?, T::from_str(bounds.1).ok()?))
}
fn check_bounds_str<T: FromStr + PartialOrd>(compare: T, str: &str) -> Option<bool>{
let bounds: (T, T) = read_bounds_string(str)?;
Some(bounds.0 <= compare && compare <= bounds.1)
}
pub async fn broadcast_notification<T: AsRef<User>>(players: &[T], notification_event: &NotificationEvent){
for player in players{
let player = player.as_ref();
player.remote.process_notification_event(notification_event.clone()).await;
}
}
impl ExtendedMatchmakeSession{
#[inline(always)]
pub fn get_active_players(&self) -> Vec<Arc<User>>{
self.connected_players.iter().filter_map(|u| u.upgrade()).collect()
}
#[inline(always)]
pub async fn broadcast_notification(&self, notification_event: &NotificationEvent){
broadcast_notification(&self.get_active_players(), notification_event).await;
}
pub async fn from_matchmake_session(gid: u32, session: MatchmakeSession, host: &Weak<User>) -> Self{
let Some(host) = host.upgrade() else{
return Default::default();
};
let mm_session = MatchmakeSession{
gathering: Gathering{
self_gid: gid,
owner_pid: host.pid,
host_pid: host.pid,
..session.gathering.clone()
},
datetime: KerberosDateTime::now(),
session_key: (0..32).map(|_| random()).collect(),
matchmake_param: MatchmakeParam{
params: vec![
("@SR".to_owned(), Variant::Bool(true)),
("@GIR".to_owned(), Variant::SInt64(3))
]
},
system_password_enabled: false,
..session
};
Self{
session: mm_session,
connected_players: Default::default()
}
}
pub async fn add_players(&mut self, conns: &[Weak<User>], join_msg: String) {
let Some(initiating_user) = conns[0].upgrade() else {
return
};
let initiating_pid = initiating_user.pid;
let old_particip = self.connected_players.clone();
for conn in conns {
self.connected_players.push(conn.clone());
}
self.session.participation_count = self.connected_players.len() as u32;
for other_connection in &conns[1..]{
let Some(other_conn) = other_connection.upgrade() else {
continue;
};
let other_pid = other_conn.pid;
/*if other_pid == self.session.gathering.owner_pid &&
joining_pid == self.session.gathering.owner_pid{
continue;
}*/
other_conn.remote.process_notification_event(NotificationEvent{
pid_source: initiating_pid,
notif_type: 122000,
param_1: self.session.gathering.self_gid,
param_2: other_pid,
str_param: "".into(),
param_3: 0
}).await;
}
let list_of_connected_pids: Vec<_> = self.connected_players.iter().filter_map(|p| p.upgrade()).map(|p| p.pid).collect();
for other_connection in conns{
let Some(other_conn) = other_connection.upgrade() else {
continue;
};
let other_pid = other_conn.pid;
/*if other_pid == self.session.gathering.owner_pid &&
joining_pid == self.session.gathering.owner_pid{
continue;
}*/
for pid in &list_of_connected_pids {
other_conn.remote.process_notification_event(NotificationEvent {
pid_source: initiating_pid,
notif_type: 3001,
param_1: self.session.gathering.self_gid,
param_2: *pid,
str_param: join_msg.clone(),
param_3: self.connected_players.len() as _
}).await;
}
}
for old_conns in &old_particip{
let Some(old_conns) = old_conns.upgrade() else {
continue;
};
let older_pid = old_conns.pid;
initiating_user.remote.process_notification_event(NotificationEvent{
pid_source: initiating_pid,
notif_type: 3001,
param_1: self.session.gathering.self_gid,
param_2: older_pid,
str_param: join_msg.clone(),
param_3: self.connected_players.len() as _
}).await;
}
}
#[inline]
pub fn is_reachable(&self) -> bool{
(if self.session.gathering.flags & PERSISTENT_GATHERING != 0{
if !self.connected_players.is_empty(){
true
} else {
self.session.open_participation
}
} else {
!self.connected_players.is_empty()
}) & !self.connected_players.is_empty()
}
#[inline]
pub fn is_joinable(&self) -> bool{
self.is_reachable() && self.session.open_participation
}
pub fn matches_criteria(&self, search_criteria: &MatchmakeSessionSearchCriteria) -> Result<bool, ErrorCode>{
// todo: implement the rest of the search criteria
if search_criteria.vacant_only {
if (self.connected_players.len() as u16 + search_criteria.vacant_participants) > self.session.gathering.maximum_participants{
return Ok(false);
}
}
if search_criteria.exclude_locked{
if !self.session.open_participation{
return Ok(false);
}
}
if search_criteria.exclude_system_password_set{
if self.session.system_password_enabled{
return Ok(false);
}
}
if search_criteria.exclude_user_password_set{
if self.session.user_password_enabled{
return Ok(false);
}
}
if !check_bounds_str(self.session.gathering.minimum_participants, &search_criteria.minimum_participants).ok_or(Core_InvalidArgument)? {
return Ok(false);
}
if !check_bounds_str(self.session.gathering.maximum_participants, &search_criteria.maximum_participants).ok_or(Core_InvalidArgument)? {
return Ok(false);
}
let game_mode: u32 = search_criteria.game_mode.parse().map_err(|_| Core_InvalidArgument)?;
if self.session.gamemode != game_mode{
return Ok(false);
}
let mm_sys_type: u32 = search_criteria.matchmake_system_type.parse().map_err(|_| Core_InvalidArgument)?;
if self.session.matchmake_system_type != mm_sys_type{
return Ok(false);
}
if search_criteria.attribs.get(0).map(|str| str.parse().ok()).flatten() != self.session.attributes.get(0).map(|v| *v){
return Ok(false);
}
if search_criteria.attribs.get(2).map(|str| str.parse().ok()).flatten() != self.session.attributes.get(2).map(|v| *v){
return Ok(false);
}
if search_criteria.attribs.get(3).map(|str| str.parse().ok()).flatten() != self.session.attributes.get(3).map(|v| *v){
return Ok(false);
}
Ok(true)
}
pub async fn migrate_ownership(&mut self, initiator_pid: u32) -> Result<(), ErrorCode>{
let players: Vec<_> = self.connected_players.iter().filter_map(|p| p.upgrade()).collect();
let Some(new_owner) = players.iter().find(|p| p.pid != self.session.gathering.owner_pid) else {
self.session.gathering.owner_pid = 0;
return Ok(());
};
self.session.gathering.owner_pid = new_owner.pid;
self.broadcast_notification(&NotificationEvent{
pid_source: initiator_pid,
notif_type: OWNERSHIP_CHANGED,
param_1: self.session.gathering.self_gid,
param_2: new_owner.pid,
..Default::default()
}).await;
Ok(())
}
pub async fn migrate_host(&mut self, initiator_pid: u32) -> Result<(), ErrorCode>{
let players: Vec<_> = self.connected_players.iter().filter_map(|p| p.upgrade()).collect();
self.session.gathering.host_pid = self.session.gathering.owner_pid;
self.broadcast_notification(&NotificationEvent{
pid_source: initiator_pid,
notif_type: HOST_CHANGED,
param_1: self.session.gathering.self_gid,
..Default::default()
}).await;
Ok(())
}
pub async fn remove_player_from_session(&mut self, pid: u32, message: &str) -> Result<(), ErrorCode>{
self.connected_players.retain(|u| u.upgrade().is_some_and(|u| u.pid != pid));
self.session.participation_count = (self.connected_players.len() & u32::MAX as usize) as u32;
if pid == self.session.gathering.owner_pid {
self.migrate_ownership(pid).await?;
}
if pid == self.session.gathering.host_pid {
self.migrate_host(pid).await?;
}
// todo: support DisconnectChangeOwner
// todo: finish the rest of this
for player in self.connected_players.iter().filter_map(|p| p.upgrade()){
player.remote.process_notification_event(NotificationEvent{
notif_type: 3008,
pid_source: pid,
param_1: self.session.gathering.self_gid,
param_2: pid,
str_param: message.to_owned(),
.. Default::default()
}).await;
}
Ok(())
}
}

5
rnex-core/src/nex/mod.rs Normal file
View file

@ -0,0 +1,5 @@
pub mod account;
pub mod auth_handler;
pub mod user;
pub mod remote_console;
pub mod matchmake;

View file

@ -0,0 +1,21 @@
use crate::rmc::protocols::notifications::{Notification, RawNotification, RawNotificationInfo, RemoteNotification};
use crate::rmc::protocols::nat_traversal::{NatTraversalConsole, RemoteNatTraversalConsole, RawNatTraversalConsoleInfo, RawNatTraversalConsole};
use crate::define_rmc_proto;
define_rmc_proto!(
proto Console{
Notification,
NatTraversalConsole
}
);
/*
#[rmc_struct(Console)]
pub struct TestRemoteConsole{
pub remote: RemoteUserProtocol,
}
impl Notification for TestRemoteConsole{
async fn process_notification_event(&self, event: NotificationEvent) {
println!("NOTIF RECIEVED: {:?}", event);
}
}*/

568
rnex-core/src/nex/user.rs Normal file
View file

@ -0,0 +1,568 @@
use crate::define_rmc_proto;
use crate::nex::matchmake::{ExtendedMatchmakeSession, MatchmakeManager};
use crate::nex::remote_console::RemoteConsole;
use rnex_core::prudp::station_url::StationUrl;
use rnex_core::prudp::station_url::UrlOptions::{
Address, NatFiltering, NatMapping, NatType, Port, PrincipalID, RVConnectionID,
};
use crate::rmc::protocols::matchmake::{
Matchmake, RawMatchmake, RawMatchmakeInfo, RemoteMatchmake,
};
use rnex_core::rmc::protocols::ranking::{Ranking, RawRanking, RawRankingInfo, RemoteRanking};
use rnex_core::rmc::protocols::matchmake_extension::{
MatchmakeExtension, RawMatchmakeExtension, RawMatchmakeExtensionInfo, RemoteMatchmakeExtension,
};
use crate::rmc::protocols::nat_traversal::{NatTraversal, RawNatTraversal, RawNatTraversalInfo, RemoteNatTraversal, RemoteNatTraversalConsole};
use rnex_core::rmc::protocols::secure::{RawSecure, RawSecureInfo, RemoteSecure, Secure};
use rnex_core::rmc::protocols::matchmake_ext::{MatchmakeExt, RawMatchmakeExt, RawMatchmakeExtInfo, RemoteMatchmakeExt};
use rnex_core::rmc::response::ErrorCode;
use rnex_core::rmc::structures::matchmake::{AutoMatchmakeParam, CreateMatchmakeSessionParam, JoinMatchmakeSessionParam, MatchmakeSession};
use rnex_core::rmc::structures::qresult::QResult;
use macros::rmc_struct;
use std::sync::{Arc, Weak};
use log::info;
use tokio::sync::{Mutex, RwLock};
use rnex_core::prudp::socket_addr::PRUDPSockAddr;
use rnex_core::prudp::station_url::nat_types::PUBLIC;
use crate::rmc::protocols::notifications::{NotificationEvent, RemoteNotification};
use rnex_core::rmc::response::ErrorCode::{Core_Exception, Core_InvalidArgument, RendezVous_AccountExpired};
define_rmc_proto!(
proto UserProtocol{
Secure,
MatchmakeExtension,
MatchmakeExt,
Matchmake,
NatTraversal,
Ranking
}
);
#[rmc_struct(UserProtocol)]
pub struct User {
pub pid: u32,
pub ip: PRUDPSockAddr,
pub this: Weak<User>,
pub remote: RemoteConsole,
pub station_url: RwLock<Vec<StationUrl>>,
pub matchmake_manager: Arc<MatchmakeManager>,
}
impl Secure for User {
async fn register(
&self,
station_urls: Vec<StationUrl>,
) -> Result<(QResult, u32, StationUrl), ErrorCode> {
let cid = self.matchmake_manager.next_cid();
println!("{:?}", station_urls);
let mut users = self.matchmake_manager.users.write().await;
users.insert(cid, self.this.clone());
drop(users);
let mut public_station: Option<StationUrl> = None;
let mut private_station: Option<StationUrl> = None;
for station in station_urls {
let is_public = station.options.iter().any(|v| {
if let NatType(v) = v {
if *v & PUBLIC != 0 {
return true;
}
}
false
});
let Some(nat_filtering) = station.options.iter().find_map(|v| match v {
NatFiltering(v) => Some(v),
_ => None
}) else {
return Err(Core_Exception);
};
let Some(nat_mapping) = station.options.iter().find_map(|v| match v {
NatMapping(v) => Some(v),
_ => None
}) else {
return Err(Core_Exception);
};
if !is_public || (*nat_filtering == 0 && *nat_mapping == 0) {
private_station = Some(station.clone());
}
if is_public {
public_station = Some(station);
}
}
let Some(mut private_station) = private_station else {
return Err(Core_Exception);
};
let mut public_station = if let Some(public_station) = public_station {
public_station
} else {
let mut public_station = private_station.clone();
public_station.options.retain(|v| {
match v {
Address(_) | Port(_) | NatFiltering(_) | NatMapping(_) | NatType(_) => false,
_ => true
}
});
public_station.options.push(Address(*self.ip.regular_socket_addr.ip()));
public_station.options.push(Port(self.ip.regular_socket_addr.port()));
public_station.options.push(NatFiltering(0));
public_station.options.push(NatMapping(0));
public_station.options.push(NatType(3));
public_station
};
let both = [&mut public_station, &mut private_station];
for station in both {
station.options.retain(|v| {
match v {
PrincipalID(_) | RVConnectionID(_) => false,
_ => true
}
});
station.options.push(PrincipalID(self.pid));
station.options.push(RVConnectionID(cid));
}
let mut lock = self.station_url.write().await;
*lock = vec![
public_station.clone(),
// private_station.clone()
];
drop(lock);
let result = QResult::success(ErrorCode::Core_Unknown);
let out = public_station.to_string();
println!("out: {}", out);
Ok((result, cid, public_station))
}
async fn replace_url(&self, target_url: StationUrl, dest: StationUrl) -> Result<(), ErrorCode> {
let mut lock = self.station_url.write().await;
let Some(target_addr) = target_url.options.iter().find(|v| matches!(v, Address(_))) else {
return Err(ErrorCode::Core_InvalidArgument);
};
let Some(target_port) = target_url.options.iter().find(|v| matches!(v, Port(_))) else {
return Err(ErrorCode::Core_InvalidArgument);
};
let Some(replacement_target) = lock.iter_mut().find(|url| {
url.options.iter().any(|o| o == target_addr) &&
url.options.iter().any(|o| o == target_port)
}) else {
return Err(ErrorCode::Core_InvalidArgument);
};
*replacement_target = dest;
drop(lock);
Ok(())
}
}
impl MatchmakeExtension for User {
async fn close_participation(&self, gid: u32) -> Result<(), ErrorCode> {
let session = self.matchmake_manager.get_session(gid).await?;
let mut session = session.lock().await;
session.session.open_participation = false;
Ok(())
}
async fn open_participation(&self, gid: u32) -> Result<(), ErrorCode> {
let session = self.matchmake_manager.get_session(gid).await?;
let mut session = session.lock().await;
session.session.open_participation = true;
Ok(())
}
async fn get_playing_session(&self, _pids: Vec<u32>) -> Result<Vec<()>, ErrorCode> {
Ok(Vec::new())
}
async fn update_progress_score(&self, gid: u32, progress: u8) -> Result<(), ErrorCode> {
let session = self.matchmake_manager.get_session(gid).await?;
let mut session = session.lock().await;
session.session.progress_score = progress;
Ok(())
}
async fn create_matchmake_session_with_param(
&self,
create_session_param: CreateMatchmakeSessionParam,
) -> Result<MatchmakeSession, ErrorCode> {
println!("{:?}", create_session_param);
let gid = self.matchmake_manager.next_gid();
let mut new_session = ExtendedMatchmakeSession::from_matchmake_session(
gid,
create_session_param.matchmake_session,
&self.this.clone(),
)
.await;
let mut joining_players = vec![self.this.clone()];
let users = self.matchmake_manager.users.read().await;
if let Ok(old_gathering) = self.matchmake_manager.get_session(create_session_param.gid_for_participation_check).await {
let old_gathering = old_gathering.lock().await;
let players = old_gathering.connected_players.iter().filter_map(|v| v.upgrade()).filter(|u| create_session_param.additional_participants.iter().any(|p| *p == u.pid));
for player in players {
joining_players.push(Arc::downgrade(&player));
}
}
drop(users);
new_session.session.participation_count = create_session_param.participation_count as u32;
new_session
.add_players(&joining_players, create_session_param.join_message)
.await;
let session = new_session.session.clone();
let mut sessions = self.matchmake_manager.sessions.write().await;
sessions.insert(gid, Arc::new(Mutex::new(new_session)));
drop(sessions);
Ok(session)
}
async fn join_matchmake_session_with_param(
&self,
join_session_param: JoinMatchmakeSessionParam,
) -> Result<MatchmakeSession, ErrorCode> {
let session = self.matchmake_manager.get_session(join_session_param.gid).await?;
let mut session = session.lock().await;
if session.session.user_password_enabled{
if join_session_param.user_password != session.session.user_password{
return Err(ErrorCode::RendezVous_InvalidPassword)
}
}
session.connected_players.retain(|v| v.upgrade().is_some_and(|v| v.pid != self.pid));
let mut joining_players = vec![self.this.clone()];
let users = self.matchmake_manager.users.read().await;
if let Ok(old_gathering) = self.matchmake_manager.get_session(join_session_param.gid_for_participation_check).await {
let old_gathering = old_gathering.lock().await;
let players = old_gathering.connected_players.iter().filter_map(|v| v.upgrade()).filter(|u| join_session_param.additional_participants.iter().any(|p| *p == u.pid));
for player in players {
joining_players.push(Arc::downgrade(&player));
}
}
drop(users);
session
.add_players(&joining_players, join_session_param.join_message)
.await;
let mm_session = session.session.clone();
Ok(mm_session)
}
async fn auto_matchmake_with_param_postpone(&self, param: AutoMatchmakeParam) -> Result<MatchmakeSession, ErrorCode> {
println!("{:?}", param);
let mut joining_players = vec![self.this.clone()];
let users = self.matchmake_manager.users.read().await;
if let Ok(old_gathering) = self.matchmake_manager.get_session(param.gid_for_participation_check).await {
let old_gathering = old_gathering.lock().await;
let players = old_gathering.connected_players.iter().filter_map(|v| v.upgrade()).filter(|u| param.additional_participants.iter().any(|p| *p == u.pid));
for player in players {
joining_players.push(Arc::downgrade(&player));
}
}
drop(users);
let sessions = self.matchmake_manager.sessions.read().await;
for session in sessions.values() {
let mut session = session.lock().await;
println!("checking session!");
if !session.is_joinable() {
continue;
}
let mut bool_matched_criteria = false;
for criteria in &param.search_criteria {
if session.matches_criteria(criteria)? {
bool_matched_criteria = true;
}
}
if bool_matched_criteria {
session.add_players(&joining_players, param.join_message).await;
return Ok(session.session.clone());
}
}
drop(sessions);
println!("making new session!");
let AutoMatchmakeParam {
join_message,
participation_count,
gid_for_participation_check,
matchmake_session,
additional_participants,
..
} = param;
self.create_matchmake_session_with_param(CreateMatchmakeSessionParam {
join_message,
participation_count,
gid_for_participation_check,
create_matchmake_session_option: 0,
matchmake_session,
additional_participants,
}).await
}
async fn find_matchmake_session_by_gathering_id_detail(&self, gid: u32) -> Result<MatchmakeSession, ErrorCode> {
let session = self.matchmake_manager.get_session(gid).await?;
let session = session.lock().await;
Ok(session.session.clone())
}
async fn modify_current_game_attribute(&self, gid: u32, attrib_index: u32, attrib_val: u32) -> Result<(), ErrorCode> {
let session = self.matchmake_manager.get_session(gid).await?;
let mut session = session.lock().await;
session.session.attributes[attrib_index as usize] = attrib_val;
Ok(())
}
}
impl Matchmake for User {
async fn unregister_gathering(&self, _gid: u32) -> Result<bool, ErrorCode> {
Ok(true)
}
async fn get_session_urls(&self, gid: u32) -> Result<Vec<StationUrl>, ErrorCode> {
let session = self.matchmake_manager.get_session(gid).await?;
let session = session.lock().await;
let urls: Vec<_> =
session
.connected_players
.iter()
.filter_map(|v| v.upgrade())
.filter(|u| u.pid == session.session.gathering.host_pid)
.map(|u| async move {
u.station_url.read().await.clone()
})
.next()
.ok_or(ErrorCode::RendezVous_SessionClosed)?
.await;
println!("{:?}", urls);
if urls.is_empty(){
return Err(ErrorCode::RendezVous_NotParticipatedGathering)
}
Ok(urls)
}
async fn update_session_host(&self, gid: u32, change_session_owner: bool) -> Result<(), ErrorCode> {
let session = self.matchmake_manager.get_session(gid).await?;
let mut session = session.lock().await;
session.session.gathering.host_pid = self.pid;
for player in &session.connected_players {
let Some(player) = player.upgrade() else {
continue;
};
player.remote.process_notification_event(NotificationEvent {
notif_type: 110000,
pid_source: self.pid,
param_1: gid,
param_2: self.pid,
param_3: 0,
str_param: "".to_string(),
}).await;
}
if change_session_owner {
session.session.gathering.owner_pid = self.pid;
for player in &session.connected_players {
let Some(player) = player.upgrade() else {
continue;
};
player.remote.process_notification_event(NotificationEvent {
notif_type: 4000,
pid_source: self.pid,
param_1: gid,
param_2: self.pid,
param_3: 0,
str_param: "".to_string(),
}).await;
}
}
Ok(())
}
async fn migrate_gathering_ownership(&self, gid: u32, candidates: Vec<u32>, _participants_only: bool) -> Result<(), ErrorCode> {
let session = self.matchmake_manager.get_session(gid).await?;
let mut session = session.lock().await;
let candidate = candidates.get(0).ok_or(Core_InvalidArgument)?;
session.session.gathering.owner_pid = *candidate;
for player in &session.connected_players {
let Some(player) = player.upgrade() else {
continue;
};
player.remote.process_notification_event(NotificationEvent {
notif_type: 4000,
pid_source: self.pid,
param_1: gid,
param_2: *candidate,
param_3: 0,
str_param: "".to_string(),
}).await;
}
Ok(())
}
}
impl MatchmakeExt for User {
async fn end_participation(&self, gid: u32, message: String) -> Result<bool, ErrorCode> {
let session = self.matchmake_manager.get_session(gid).await?;
let mut session = session.lock().await;
session.remove_player_from_session(self.pid, &message).await?;
Ok(true)
}
}
impl NatTraversal for User {
async fn report_nat_properties(
&self,
nat_mapping: u32,
nat_filtering: u32,
_rtt: u32,
) -> Result<(), ErrorCode> {
let mut urls = self.station_url.write().await;
for station_url in urls.iter_mut() {
station_url.options.retain(|o| match o {
NatMapping(_) | NatFiltering(_) => false,
_ => true
});
station_url.options.push(NatMapping(nat_mapping as u8));
station_url.options.push(NatFiltering(nat_filtering as u8));
}
Ok(())
}
async fn report_nat_traversal_result(&self, _cid: u32, _result: bool, _rtt: u32) -> Result<(), ErrorCode> {
Ok(())
}
async fn request_probe_initiation(&self, _station_to_probe: String) -> Result<(), ErrorCode> {
info!("NO!");
Err(RendezVous_AccountExpired)
}
async fn request_probe_initialization_ext(&self, target_list: Vec<String>, station_to_probe: String) -> Result<(), ErrorCode> {
let users = self.matchmake_manager.users.read().await;
println!("requesting station probe for {:?} to {:?}", target_list, station_to_probe);
for target in target_list {
let Ok(url) = StationUrl::try_from(target.as_ref()) else {
continue;
};
let Some(RVConnectionID(v)) = url.options.into_iter().find(|o| { matches!(o, &RVConnectionID(_)) }) else {
continue;
};
let Some(v) = users.get(&v) else {
continue;
};
let Some(user) = v.upgrade() else {
continue;
};
user.remote.request_probe_initiation(station_to_probe.clone()).await;
}
info!("finished probing");
Ok(())
}
}
impl Ranking for User{
}

View file

@ -0,0 +1,3 @@
pub mod virtual_port;
pub mod station_url;
pub mod socket_addr;

View file

@ -0,0 +1,38 @@
use std::io::Write;
use std::net::SocketAddrV4;
use hmac::{Hmac};
use md5::digest::Mac;
use macros::RmcSerialize;
use rnex_core::prudp::virtual_port::VirtualPort;
type Md5Hmac = Hmac<md5::Md5>;
#[derive(Eq, PartialEq, Hash, Debug, Copy, Clone, Ord, PartialOrd, RmcSerialize)]
#[rmc_struct(0)]
pub struct PRUDPSockAddr{
pub regular_socket_addr: SocketAddrV4,
pub virtual_port: VirtualPort
}
impl PRUDPSockAddr{
pub fn new(regular_socket_addr: SocketAddrV4, virtual_port: VirtualPort) -> Self{
Self{
regular_socket_addr,
virtual_port
}
}
pub fn calculate_connection_signature(&self) -> [u8; 16] {
let mut hmac = Md5Hmac::new_from_slice(&[0; 16]).expect("fuck");
let mut data = self.regular_socket_addr.ip().octets().to_vec();
//data.extend_from_slice(&self.regular_socket_addr.port().to_be_bytes());
hmac.write_all(&data).expect("figuring this out was complete ass");
let result: [u8; 16] = hmac.finalize().into_bytes()[0..16].try_into().expect("fuck");
result
}
}

View file

@ -0,0 +1,198 @@
use std::net::Ipv4Addr;
use log::error;
use std::fmt::{Debug, Display, Formatter, Write};
use std::io::Read;
use crate::prudp::station_url::Type::{PRUDP, PRUDPS, UDP};
use crate::prudp::station_url::UrlOptions::{Address, ConnectionID, NatFiltering, NatMapping, NatType, Platform, PMP, Port, PrincipalID, RVConnectionID, StreamID, StreamType, UPNP, PID};
use crate::rmc::structures::Error::StationUrlInvalid;
use crate::rmc::structures::RmcSerialize;
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum Type{
UDP,
PRUDP,
PRUDPS
}
pub mod nat_types{
pub const BEHIND_NAT: u8 = 1;
pub const PUBLIC: u8 = 2;
}
#[derive(Clone, Eq, PartialEq)]
pub enum UrlOptions {
Address(Ipv4Addr),
Port(u16),
StreamType(u8),
StreamID(u8),
ConnectionID(u8),
PrincipalID(u32),
NatType(u8),
NatMapping(u8),
NatFiltering(u8),
UPNP(u8),
RVConnectionID(u32),
Platform(u8),
PMP(u8),
PID(u32),
}
#[derive(Clone, PartialEq, Eq)]
pub struct StationUrl{
pub url_type: Type,
pub options: Vec<UrlOptions>
}
impl StationUrl{
pub fn read_options(options: &str) -> Option<Vec<UrlOptions>>{
let mut options_out = Vec::new();
for option in options.split(';'){
if option == "" { continue; }
let mut option_parts = option.split('=');
let option_name= option_parts.next()?.to_ascii_lowercase();
let option_value = option_parts.next()?;
match option_name.as_ref(){
"address" => {
options_out.push(Address(option_value.parse().ok()?))
},
"port" => {
options_out.push(Port(option_value.parse().ok()?))
}
"natf" => {
options_out.push(NatFiltering(option_value.parse().ok()?))
}
"natm" => {
options_out.push(NatMapping(option_value.parse().ok()?))
}
"sid" => {
options_out.push(StreamID(option_value.parse().ok()?))
}
"upnp" => {
options_out.push(UPNP(option_value.parse().ok()?))
}
"type" => {
options_out.push(NatType(option_value.parse().ok()?))
}
"stream" => {
options_out.push(StreamType(option_value.parse().ok()?))
}
"RVCID" => {
options_out.push(RVConnectionID(option_value.parse().ok()?))
}
"rvcid" => {
options_out.push(RVConnectionID(option_value.parse().ok()?))
}
"pl" => {
options_out.push(Platform(option_value.parse().ok()?))
}
"pmp" => {
options_out.push(PMP(option_value.parse().ok()?))
},
"pid" => {
options_out.push(PID(option_value.parse().ok()?))
},
"PID" => {
options_out.push(PID(option_value.parse().ok()?))
},
_ => {
error!("unimplemented option type, skipping: {}", option_name);
}
}
}
Some(options_out)
}
}
impl TryFrom<&str> for StationUrl{
type Error = ();
fn try_from(value: &str) -> Result<Self, ()> {
let (url_type, options) = value.split_at(value.find(":/").ok_or(())?);
let options = &options[2..];
let url_type = match url_type{
"udp" => UDP,
"prudp" => PRUDP,
"prudps" => PRUDPS,
_ => return Err(())
};
let options = Self::read_options(options).ok_or(())?;
Ok(
Self{
url_type,
options
}
)
}
}
impl<'a> Into<String> for &'a StationUrl{
fn into(self) -> String {
let mut url = match self.url_type{
UDP => "udp:/",
PRUDP => "prudp:/",
PRUDPS => "prudps:/"
}.to_owned();
for option in &self.options{
match option{
Address(v) => write!(url, "address={}", v).expect("failed to write"),
Port(v) => write!(url, "port={}", v).expect("failed to write"),
StreamType(v) => write!(url, "stream={}", v).expect("failed to write"),
StreamID(v) => write!(url, "sid={}", v).expect("failed to write"),
ConnectionID(v) => write!(url, "CID={}", v).expect("failed to write"),
PrincipalID(v) => write!(url, "PID={}", v).expect("failed to write"),
NatType(v) => write!(url, "type={}", v).expect("failed to write"),
NatMapping(v) => write!(url, "natm={}", v).expect("failed to write"),
NatFiltering(v) => write!(url, "natf={}", v).expect("failed to write"),
UPNP(v) => write!(url, "upnp={}", v).expect("failed to write"),
RVConnectionID(v) => write!(url, "RVCID={}", v).expect("failed to write"),
Platform(v) => write!(url, "pl={}", v).expect("failed to write"),
PMP(v) => write!(url, "pmp={}", v).expect("failed to write"),
PID(v) => write!(url, "PID={}", v).expect("failed to write"),
}
write!(url, ";").expect("failed to write");
}
url[0..url.len()-1].into()
}
}
impl Display for StationUrl{
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let str: String = self.into();
write!(f, "{}", str)
}
}
impl RmcSerialize for StationUrl{
fn deserialize(reader: &mut dyn Read) -> crate::rmc::structures::Result<Self> {
let str = String::deserialize(reader)?;
Self::try_from(str.as_str()).map_err(|_| StationUrlInvalid)
}
fn serialize(&self, writer: &mut dyn std::io::Write) -> crate::rmc::structures::Result<()> {
let str: String = self.into();
str.serialize(writer)
}
}
impl Debug for StationUrl{
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let str: String = self.into();
f.write_str(&str)
}
}

View file

@ -0,0 +1,49 @@
use std::fmt::{Debug, Formatter};
use bytemuck::{Pod, Zeroable};
use v_byte_helpers::SwapEndian;
#[repr(transparent)]
#[derive(PartialEq, Eq, Ord, PartialOrd, Copy, Clone, Pod, Zeroable, SwapEndian, Hash, Default)]
pub struct VirtualPort(pub u8);
impl VirtualPort {
#[inline]
pub const fn get_stream_type(self) -> u8 {
(self.0 & 0xF0) >> 4
}
#[inline]
pub const fn get_port_number(self) -> u8 {
self.0 & 0x0F
}
#[inline]
pub fn stream_type(self, val: u8) -> Self {
let masked_val = val & 0x0F;
assert_eq!(masked_val, val);
Self((self.0 & 0x0F) | (masked_val << 4))
}
#[inline]
pub fn port_number(self, val: u8) -> Self {
let masked_val = val & 0x0F;
assert_eq!(masked_val, val);
Self((self.0 & 0xF0) | masked_val)
}
#[inline]
pub fn new(port: u8, stream_type: u8) -> Self {
Self(0).stream_type(stream_type).port_number(port)
}
}
impl Debug for VirtualPort {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let stream_type = self.get_stream_type();
let port_number = self.get_port_number();
write!(f, "VirtualPort{{ stream_type: {}, port_number: {} }}", stream_type, port_number)
}
}

82
rnex-core/src/reggie.rs Normal file
View file

@ -0,0 +1,82 @@
use std::{env, fs, io};
use std::hash::Hash;
use std::io::{Error, ErrorKind};
use std::net::{SocketAddrV4, ToSocketAddrs};
use std::pin::Pin;
use std::sync::Arc;
use std::task::{Context, Poll};
use futures::{SinkExt, StreamExt};
use macros::{method_id, rmc_proto, rmc_struct, RmcSerialize};
use once_cell::sync::Lazy;
use rustls::{ClientConfig, RootCertStore, ServerConfig};
use rustls::client::WebPkiServerVerifier;
use rustls::server::WebPkiClientVerifier;
use rustls_pki_types::{CertificateDer, PrivateKeyDer, ServerName, TrustAnchor};
use thiserror::Error;
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt, ReadBuf};
use tokio::net::{TcpListener, TcpStream};
use tokio_rustls::{TlsAcceptor, TlsConnector};
use tokio_rustls::client::TlsStream;
use tokio_tungstenite::{connect_async, MaybeTlsStream, WebSocketStream};
use tokio_tungstenite::tungstenite::Message;
use webpki::anchor_from_trusted_cert;
use crate::common::setup;
use crate::define_rmc_proto;
use crate::nex::account::Account;
use crate::rmc::protocols::{new_rmc_gateway_connection, OnlyRemote, RmcCallable, RmcConnection};
use rnex_core::rmc::response::ErrorCode;
use crate::rmc::structures::RmcSerialize;
pub trait UnitPacketRead: AsyncRead + Unpin{
async fn read_buffer(&mut self) -> Result<Vec<u8>, io::Error>{
let mut len_raw: [u8; 4] = [0; 4];
self.read_exact(&mut len_raw).await?;
let len = u32::from_le_bytes(len_raw);
let mut vec = vec![0u8; len as _];
self.read_exact(&mut vec).await?;
Ok(vec)
}
}
impl<T: AsyncRead + Unpin> UnitPacketRead for T{}
pub trait UnitPacketWrite: AsyncWrite + Unpin{
async fn send_buffer(&mut self, data: &[u8]) -> Result<(), io::Error> {
let mut dest_data = Vec::new();
data.serialize(&mut dest_data).expect("ran out of memory or something");
self.write_all(&dest_data[..]).await?;
self.flush().await?;
Ok(())
}
}
impl<T: AsyncWrite + Unpin> UnitPacketWrite for T{}
#[rmc_proto(1)]
pub trait EdgeNodeManagement {
#[method_id(1)]
async fn get_url(&self, seed: u64) -> Result<SocketAddrV4, ErrorCode>;
}
define_rmc_proto!(
proto EdgeNodeHolder{
EdgeNodeManagement
}
);
#[derive(RmcSerialize, Debug)]
#[repr(u32)]
pub enum EdgeNodeHolderConnectOption{
DontRegister = 0,
Register(SocketAddrV4) = 1
}

23
rnex-core/src/result.rs Normal file
View file

@ -0,0 +1,23 @@
use std::error::Error;
use log::error;
pub trait ResultExtension{
type Output;
fn display_err_or_some(self) -> Option<Self::Output>;
}
impl<T, U: Error> ResultExtension for Result<T, U>{
type Output = T;
fn display_err_or_some(self) -> Option<Self::Output> {
match self{
Ok(v) => Some(v),
Err(e) => {
error!("{}", e);
None
}
}
}
}

View file

@ -0,0 +1,89 @@
use std::io;
use std::io::{Read, Seek, Write};
use bytemuck::bytes_of;
use log::error;
use v_byte_helpers::{IS_BIG_ENDIAN, ReadExtensions};
use crate::rmc::response::{ErrorCode, RMCResponseResult};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RMCMessage{
pub protocol_id: u16,
pub call_id: u32,
pub method_id: u32,
pub rest_of_data: Vec<u8>
}
impl RMCMessage{
pub fn new(stream: &mut (impl Seek + Read)) -> io::Result<Self>{
let size: u32 = stream.read_struct(IS_BIG_ENDIAN)?;
let mut header_size = 1 + 4 + 4;
let protocol_id: u8 = stream.read_struct(IS_BIG_ENDIAN)?;
let protocol_id= protocol_id & (!0x80);
let protocol_id: u16 = match protocol_id{
0x7F => {
header_size += 2;
stream.read_struct(IS_BIG_ENDIAN)?
},
_ => protocol_id as u16
};
let call_id = stream.read_struct(IS_BIG_ENDIAN)?;
let method_id = stream.read_struct(IS_BIG_ENDIAN)?;
let mut rest_of_data = Vec::new();
stream.read_to_end(&mut rest_of_data)?;
if header_size + rest_of_data.len() != size as usize {
error!("received incorrect rmc packet: expected size {} but found {}", size, header_size + rest_of_data.len());
}
//stream.
Ok(Self{
protocol_id,
method_id,
call_id,
rest_of_data
})
}
pub fn to_data(&self) -> Vec<u8>{
let size = (1 + 4 + 4 + self.rest_of_data.len()) as u32;
let mut output = Vec::new();
output.write_all(bytes_of(&size)).expect("unable to write size");
let proto_id = self.protocol_id as u8 | 0x80;
output.write_all(bytes_of(&proto_id)).expect("unable to write size");
output.write_all(bytes_of(&self.call_id)).expect("unable to write size");
output.write_all(bytes_of(&self.method_id)).expect("unable to write size");
output.write_all(&self.rest_of_data).expect("unable to write data");
output
}
pub fn error_result_with_code(&self, error_code: ErrorCode) -> RMCResponseResult{
RMCResponseResult::Error {
call_id: self.call_id,
error_code
}
}
pub fn success_with_data(&self, data: Vec<u8>) -> RMCResponseResult{
RMCResponseResult::Success {
call_id: self.call_id,
method_id: self.method_id,
data
}
}
}

7
rnex-core/src/rmc/mod.rs Normal file
View file

@ -0,0 +1,7 @@
pub mod message;
pub mod structures;
pub mod response;
pub mod protocols;

View file

@ -0,0 +1,47 @@
use rnex_core::rmc::response::ErrorCode;
use rnex_core::rmc::structures::any::Any;
use crate::rmc::structures::connection_data::ConnectionData;
use rnex_core::rmc::structures::qresult::QResult;
use macros::{method_id, rmc_proto};
/// This is the representation for `Ticket Granting`(for details see the
/// [kinnay wiki entry](https://github.com/kinnay/NintendoClients/wiki/Authentication-Protocol))
#[rmc_proto(10)]
pub trait Auth {
/// representation of the `Login` method(for details see the
/// [kinnay wiki entry](https://github.com/kinnay/NintendoClients/wiki/Authentication-Protocol))
#[method_id(1)]
async fn login(&self, name: String) -> Result<(), ErrorCode>;
/// representation of the `LoginEx` method(for details see the
/// [kinnay wiki entry](https://github.com/kinnay/NintendoClients/wiki/Authentication-Protocol))
#[method_id(2)]
async fn login_ex(
&self,
name: String,
extra_data: Any,
) -> Result<(QResult, u32, Vec<u8>, ConnectionData, String), ErrorCode>;
/// representation of the `RequestTicket` method(for details see the
/// [kinnay wiki entry](https://github.com/kinnay/NintendoClients/wiki/Authentication-Protocol))
#[method_id(3)]
async fn request_ticket(
&self,
source_pid: u32,
destination_pid: u32,
) -> Result<(QResult, Vec<u8>), ErrorCode>;
/// representation of the `GetPID` method(for details see the
/// [kinnay wiki entry](https://github.com/kinnay/NintendoClients/wiki/Authentication-Protocol))
#[method_id(4)]
async fn get_pid(&self, username: String) -> Result<u32, ErrorCode>;
/// representation of the `LoginWithContext` method(for details see the
/// [kinnay wiki entry](https://github.com/kinnay/NintendoClients/wiki/Authentication-Protocol))
#[method_id(5)]
async fn get_name(&self, pid: u32) -> Result<String, ErrorCode>;
// `LoginWithContext` is left out here because we don't need it right now and versioning still
// needs to be figured out
}

View file

@ -0,0 +1,17 @@
use macros::{method_id, rmc_proto};
use rnex_core::prudp::station_url::StationUrl;
use rnex_core::rmc::response::ErrorCode;
#[rmc_proto(21)]
pub trait Matchmake{
#[method_id(2)]
async fn unregister_gathering(&self, gid: u32) -> Result<bool, ErrorCode>;
#[method_id(41)]
async fn get_session_urls(&self, gid: u32) -> Result<Vec<StationUrl>, ErrorCode>;
#[method_id(42)]
async fn update_session_host(&self, gid: u32, change_owner: bool) -> Result<(), ErrorCode>;
#[method_id(44)]
async fn migrate_gathering_ownership(&self, gid: u32, candidates: Vec<u32>, participants_only: bool) -> Result<(), ErrorCode>;
}

View file

@ -0,0 +1,8 @@
use macros::{method_id, rmc_proto};
use rnex_core::rmc::response::ErrorCode;
#[rmc_proto(50)]
pub trait MatchmakeExt{
#[method_id(1)]
async fn end_participation(&self, gid: u32, message: String) -> Result<bool, ErrorCode>;
}

View file

@ -0,0 +1,32 @@
use macros::{method_id, rmc_proto};
use rnex_core::rmc::response::ErrorCode;
use rnex_core::rmc::structures::matchmake::{AutoMatchmakeParam, CreateMatchmakeSessionParam, JoinMatchmakeSessionParam, MatchmakeSession};
#[rmc_proto(109)]
pub trait MatchmakeExtension{
#[method_id(1)]
async fn close_participation(&self, gid: u32) -> Result<(), ErrorCode>;
#[method_id(2)]
async fn open_participation(&self, gid: u32) -> Result<(), ErrorCode>;
#[method_id(8)]
async fn modify_current_game_attribute(&self, gid: u32, attrib_index: u32, attrib_val: u32) -> Result<(), ErrorCode>;
#[method_id(16)]
async fn get_playing_session(&self, pids: Vec<u32>) -> Result<Vec<()>, ErrorCode>;
#[method_id(34)]
async fn update_progress_score(&self, gid: u32, progress: u8) -> Result<(), ErrorCode>;
#[method_id(38)]
async fn create_matchmake_session_with_param(&self, session: CreateMatchmakeSessionParam) -> Result<MatchmakeSession, ErrorCode>;
#[method_id(39)]
async fn join_matchmake_session_with_param(&self, session: JoinMatchmakeSessionParam) -> Result<MatchmakeSession, ErrorCode>;
#[method_id(40)]
async fn auto_matchmake_with_param_postpone(&self, session: AutoMatchmakeParam) -> Result<MatchmakeSession, ErrorCode>;
#[method_id(41)]
async fn find_matchmake_session_by_gathering_id_detail(&self, gid: u32) -> Result<MatchmakeSession, ErrorCode>;
}

View file

@ -0,0 +1,342 @@
#![allow(async_fn_in_trait)]
pub mod auth;
pub mod secure;
pub mod notifications;
pub mod matchmake;
pub mod matchmake_extension;
pub mod nat_traversal;
pub mod matchmake_ext;
pub mod ranking;
use crate::util::{SendingBufferConnection, SplittableBufferConnection};
use crate::rmc::message::RMCMessage;
use crate::rmc::protocols::RemoteCallError::ConnectionBroke;
use crate::rmc::response::{ErrorCode, RMCResponse, RMCResponseResult};
use crate::rmc::structures;
use crate::rmc::structures::RmcSerialize;
use log::{error, info};
use std::collections::HashMap;
use std::future::Future;
use std::io::Cursor;
use std::ops::Deref;
use std::sync::Arc;
use std::time::Duration;
use thiserror::Error;
use tokio::sync::{Mutex, Notify};
use tokio::time::{sleep, sleep_until, Instant};
use crate::result::ResultExtension;
#[derive(Error, Debug)]
pub enum RemoteCallError {
#[error("Call to remote timed out whilest waiting on response.")]
Timeout,
#[error("A server side rmc error occurred: {0:?}")]
ServerError(ErrorCode),
#[error("Connection broke")]
ConnectionBroke,
#[error("Error reading response data: {0}")]
InvalidResponse(#[from] structures::Error),
}
pub struct RmcConnection(pub SendingBufferConnection, pub RmcResponseReceiver);
pub struct RmcResponseReceiver(Arc<Notify>, Arc<Mutex<HashMap<u32, RMCResponse>>>);
impl RmcConnection {
pub async fn make_raw_call<T: RmcSerialize>(
&self,
message: &RMCMessage,
) -> Result<T, RemoteCallError> {
self.make_raw_call_no_response(message).await?;
let data = self.1.get_response_data(message.call_id).await?;
let out = <T as RmcSerialize>::deserialize(&mut Cursor::new(data))?;
Ok(out)
}
pub async fn make_raw_call_no_response(
&self,
message: &RMCMessage,
) -> Result<(), RemoteCallError> {
let message_data = message.to_data();
self.0.send(message_data).await.ok_or(ConnectionBroke)?;
Ok(())
}
pub async fn disconnect(&self){
self.0.disconnect().await;
}
}
impl RmcResponseReceiver {
// returns none if timed out
pub async fn get_response_data(&self, call_id: u32) -> Result<Vec<u8>, RemoteCallError> {
let mut end_wait_time = Instant::now();
end_wait_time += Duration::from_secs(5);
let sleep_fut = sleep_until(end_wait_time);
tokio::pin!(sleep_fut);
let mut sleep_manual_unlock_fut = Instant::now();
sleep_manual_unlock_fut += Duration::from_secs(4);
let sleep_manual_unlock_fut = sleep_until(sleep_manual_unlock_fut);
tokio::pin!(sleep_manual_unlock_fut);
loop {
let mut locked = self.1.lock().await;
if let Some(v) = locked.remove(&call_id) {
match v.response_result{
RMCResponseResult::Success {
data,
..
} => return Ok(data),
RMCResponseResult::Error {
error_code,
..
} => return Err(RemoteCallError::ServerError(error_code))
}
}
drop(locked);
let notif_fut = self.0.notified();
tokio::select! {
_ = &mut sleep_manual_unlock_fut => {
continue;
}
_ = &mut sleep_fut => {
return Err(RemoteCallError::Timeout);
}
_ = notif_fut => {
continue;
}
}
}
}
}
pub trait HasRmcConnection {
fn get_connection(&self) -> &RmcConnection;
}
pub trait RemoteObject {
fn new(conn: RmcConnection) -> Self;
}
impl RemoteObject for () {
fn new(_: RmcConnection) -> Self {}
}
pub trait RmcCallable {
//type Remote: RemoteObject;
fn rmc_call(
&self,
responder: &SendingBufferConnection,
protocol_id: u16,
method_id: u32,
call_id: u32,
rest: Vec<u8>,
) -> impl std::future::Future<Output = ()> + Send;
}
#[macro_export]
macro_rules! define_rmc_proto {
(proto $name:ident{
$($protocol:path),*
}) => {
paste::paste!{
pub trait [<Local $name>]: std::any::Any $( + [<Raw $protocol>] + $protocol)* {
async fn rmc_call(&self, remote_response_connection: &rnex_core::util::SendingBufferConnection, protocol_id: u16, method_id: u32, call_id: u32, rest: Vec<u8>){
match protocol_id{
$(
[<Raw $protocol Info>]::PROTOCOL_ID => <Self as [<Raw $protocol>]>::rmc_call_proto(self, remote_response_connection, method_id, call_id, rest).await,
)*
v => log::error!("invalid protocol called on rmc object {}", v)
}
}
}
pub struct [<Remote $name>](rnex_core::rmc::protocols::RmcConnection);
impl rnex_core::rmc::protocols::RemoteInstantiatable for [<Remote $name>]{
fn new(conn: rnex_core::rmc::protocols::RmcConnection) -> Self{
Self(conn)
}
async fn disconnect(&self){
self.0.disconnect().await;
}
}
impl rnex_core::rmc::protocols::HasRmcConnection for [<Remote $name>]{
fn get_connection(&self) -> &rnex_core::rmc::protocols::RmcConnection{
&self.0
}
}
$(
impl [<Remote $protocol>] for [<Remote $name>]{}
)*
}
};
}
/// This is a special case to allow unit to represent the fact that no object is represented.
impl RmcCallable for () {
async fn rmc_call(
&self,
_remote_response_connection: &SendingBufferConnection,
_protocol_id: u16,
_method_id: u32,
_call_id: u32,
_rest: Vec<u8>,
) {
//todo: maybe reply with not implemented(?)
}
}
pub trait RemoteInstantiatable{
fn new(conn: RmcConnection) -> Self;
async fn disconnect(&self);
}
pub struct OnlyRemote<T: RemoteInstantiatable>(T);
impl<T: RemoteInstantiatable> Deref for OnlyRemote<T>{
type Target = T;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T: RemoteInstantiatable> OnlyRemote<T>{
pub fn new(conn: RmcConnection) -> Self{
Self(T::new(conn))
}
pub async fn disconnect(&self) {
self.0.disconnect().await;
}
}
impl<T: RemoteInstantiatable> RmcCallable for OnlyRemote<T>{
fn rmc_call(&self, _responder: &SendingBufferConnection, _protocol_id: u16, _method_id: u32, _call_id: u32, _rest: Vec<u8>) -> impl Future<Output = ()> + Send {
// maybe respond with not implemented or something
async{}
}
}
async fn handle_incoming<T: RmcCallable + Send + Sync + 'static>(
mut connection: SplittableBufferConnection,
remote: Arc<T>,
notify: Arc<Notify>,
incoming: Arc<Mutex<HashMap<u32, RMCResponse>>>,
) {
let sending_conn = connection.duplicate_sender();
while let Some(v) = connection.recv().await{
let Some(proto_id) = v.get(4) else {
error!("received too small rmc message.");
error!("ending rmc gateway.");
return
};
// protocol 0 is hardcoded to be the no protocol protocol aka keepalive protocol
if *proto_id == 0{
println!("got keepalive");
continue;
}
if (proto_id & 0x80) == 0{
let Some(response) = RMCResponse::new(&mut Cursor::new(v)).display_err_or_some() else {
error!("ending rmc gateway.");
return
};
info!("got rmc response");
let mut locked = incoming.lock().await;
locked.insert(response.get_call_id(), response);
notify.notify_waiters();
} else {
let Some(message) = RMCMessage::new(&mut Cursor::new(v)).display_err_or_some() else {
error!("ending rmc gateway.");
return
};
let RMCMessage{
protocol_id,
method_id,
call_id,
rest_of_data
} = message;
info!("RMC REQUEST: Proto: {}; Method: {};", protocol_id, method_id);
remote.rmc_call(&sending_conn, protocol_id, method_id, call_id, rest_of_data).await;
}
}
info!("rmc disconnected")
}
pub fn new_rmc_gateway_connection<T: RmcCallable + Sync + Send + 'static,F>(conn: SplittableBufferConnection, create_internal: F) -> Arc<T>
where
F: FnOnce(RmcConnection) -> Arc<T>,
{
let notify = Arc::new(Notify::new());
let incoming: Arc<Mutex<HashMap<u32, RMCResponse>>> = Default::default();
let response_recv = RmcResponseReceiver(notify.clone(), incoming.clone());
let sending_conn = conn.duplicate_sender();
let rmc_conn = RmcConnection(sending_conn, response_recv);
let sending_conn = conn.duplicate_sender();
let exposed_object = (create_internal)(rmc_conn);
{
let exposed_object = exposed_object.clone();
tokio::spawn(async move {
handle_incoming(
conn,
exposed_object,
notify,
incoming
).await;
});
tokio::spawn(async move {
while sending_conn.is_alive(){
sending_conn.send([0,0,0,0,0].to_vec()).await;
sleep(Duration::from_secs(10)).await;
}
});
}
exposed_object
}
impl<T: RmcCallable> RmcCallable for Arc<T>{
fn rmc_call(&self, responder: &SendingBufferConnection, protocol_id: u16, method_id: u32, call_id: u32, rest: Vec<u8>) -> impl Future<Output=()> + Send {
self.as_ref().rmc_call(responder, protocol_id, method_id, call_id, rest)
}
}
define_rmc_proto! {
proto NoProto{}
}

View file

@ -0,0 +1,23 @@
use macros::{method_id, rmc_proto};
use rnex_core::rmc::response::ErrorCode;
#[rmc_proto(3)]
pub trait NatTraversal{
#[method_id(2)]
async fn request_probe_initiation(&self, station_to_probe: String) -> Result<(),ErrorCode>;
#[method_id(3)]
async fn request_probe_initialization_ext(&self, target_list: Vec<String>, station_to_probe: String) -> Result<(),ErrorCode>;
#[method_id(4)]
async fn report_nat_traversal_result(&self, cid: u32, result: bool, rtt: u32) -> Result<(),ErrorCode>;
#[method_id(5)]
async fn report_nat_properties(&self, nat_mapping: u32, nat_filtering: u32, rtt: u32) -> Result<(),ErrorCode>;
}
#[rmc_proto(3, NoReturn)]
pub trait NatTraversalConsole{
#[method_id(2)]
async fn request_probe_initiation(&self, station_to_probe: String);
}

View file

@ -0,0 +1,24 @@
use macros::{method_id, rmc_proto, rmc_struct, RmcSerialize};
pub mod notification_types{
pub const OWNERSHIP_CHANGED: u32 = 4000;
pub const HOST_CHANGED: u32 = 110000;
}
#[derive(RmcSerialize, Debug, Default, Clone)]
#[rmc_struct(0)]
pub struct NotificationEvent{
pub pid_source: u32,
pub notif_type: u32,
pub param_1: u32,
pub param_2: u32,
pub str_param: String,
pub param_3: u32,
}
#[rmc_proto(14, NoReturn)]
pub trait Notification {
#[method_id(1)]
async fn process_notification_event(&self, event: NotificationEvent);
}

View file

@ -0,0 +1,5 @@
use macros::rmc_proto;
#[rmc_proto(112)]
pub trait Ranking{
}

View file

@ -0,0 +1,12 @@
use macros::{method_id, rmc_proto};
use rnex_core::prudp::station_url::StationUrl;
use rnex_core::rmc::response::ErrorCode;
use rnex_core::rmc::structures::qresult::QResult;
#[rmc_proto(11)]
pub trait Secure {
#[method_id(1)]
async fn register(&self, station_urls: Vec<StationUrl>) -> Result<(QResult, u32, StationUrl), ErrorCode>;
#[method_id(7)]
async fn replace_url(&self, target: StationUrl, dest: StationUrl) -> Result<(), ErrorCode>;
}

View file

@ -0,0 +1,497 @@
// i seriously dont know why the compiler is complaining about unused parentheses in the repr
// attributes but this gets it to not complain anymore
#![allow(unused_parens)]
use std::io;
use std::io::{Read, Seek, Write};
use std::mem::transmute;
use bytemuck::bytes_of;
use log::error;
use v_byte_helpers::EnumTryInto;
use v_byte_helpers::{ReadExtensions, IS_BIG_ENDIAN};
use crate::rmc::response::ErrorCode::Core_Exception;
use crate::rmc::structures::qresult::ERROR_MASK;
use crate::util::SendingBufferConnection;
pub enum RMCResponseResult {
Success {
call_id: u32,
method_id: u32,
data: Vec<u8>,
},
Error {
error_code: ErrorCode,
call_id: u32,
},
}
pub struct RMCResponse {
pub protocol_id: u8,
pub response_result: RMCResponseResult,
}
impl RMCResponse {
pub fn new(stream: &mut (impl Seek + Read)) -> io::Result<Self>{
// ignore the size for now this will only be used for checking
let size: u32 = stream.read_struct(IS_BIG_ENDIAN)?;
let protocol_id: u8 = stream.read_struct(IS_BIG_ENDIAN)?;
/*let protocol_id: u16 = match protocol_id{
0x7F => {
stream.read_struct(IS_BIG_ENDIAN)?
},
_ => protocol_id as u16
};*/
let is_success: u8 = stream.read_struct(IS_BIG_ENDIAN)?;
let response_result = if is_success == 0x01{
let call_id: u32 = stream.read_struct(IS_BIG_ENDIAN)?;
let method_id: u32 = stream.read_struct(IS_BIG_ENDIAN)?;
let method_id = method_id & (!0x8000);
let mut data: Vec<u8> = vec![0u8; (size - 2 - 4 - 4) as _];
stream.read(&mut data)?;
RMCResponseResult::Success {
call_id,
method_id,
data
}
} else {
let error_code: u32 = stream.read_struct(IS_BIG_ENDIAN)?;
let error_code = error_code & (!0x80000000);
let call_id: u32 = stream.read_struct(IS_BIG_ENDIAN)?;
RMCResponseResult::Error {
error_code: {
match ErrorCode::try_from(error_code){
Ok(v) => v,
Err(()) => {
error!("invalid error code {:#010x}", error_code);
Core_Exception
}
}
},
call_id,
}
};
Ok(Self{
protocol_id,
response_result
})
}
pub fn get_call_id(&self) -> u32{
match &self.response_result{
RMCResponseResult::Success { call_id, ..} => *call_id,
RMCResponseResult::Error { call_id, .. } => *call_id
}
}
pub fn to_data(self) -> Vec<u8> {
generate_response(self.protocol_id, self.response_result).expect("failed to generate response")
}
}
pub fn generate_response(protocol_id: u8, response: RMCResponseResult) -> io::Result<Vec<u8>> {
let size = 1 + 1 + match &response {
RMCResponseResult::Success {
data,
..
} => 4 + 4 + data.len(),
RMCResponseResult::Error { .. } => 4 + 4,
};
let mut data_out = Vec::with_capacity(size + 4);
let u32_size: u32 = size as _;
data_out.write_all(bytes_of(&u32_size))?;
data_out.push(protocol_id);
match response {
RMCResponseResult::Success {
call_id,
method_id,
data
} => {
data_out.push(1);
data_out.write_all(bytes_of(&call_id))?;
let ored_method_id = method_id | 0x8000;
data_out.write_all(bytes_of(&ored_method_id))?;
data_out.write_all(&data)?;
}
RMCResponseResult::Error {
call_id,
error_code
} => {
data_out.push(0);
let error_code_val: u32 = error_code.into();
let error_code_val = error_code_val | ERROR_MASK;
data_out.write_all(bytes_of(&error_code_val))?;
data_out.write_all(bytes_of(&call_id))?;
}
}
assert_eq!(data_out.len(), size + 4);
Ok(data_out)
}
pub async fn send_result(
connection: &SendingBufferConnection,
result: Result<Vec<u8>, ErrorCode>,
protocol_id: u8,
method_id: u32,
call_id: u32,
) {
let response_result = match result {
Ok(v) => RMCResponseResult::Success {
call_id,
method_id,
data: v
},
Err(e) =>
RMCResponseResult::Error {
call_id,
error_code: e.into()
}
};
let response = RMCResponse{
response_result,
protocol_id
};
send_response(connection, response).await
}
pub async fn send_response(connection: &SendingBufferConnection, rmcresponse: RMCResponse) {
connection.send(rmcresponse.to_data()).await;
}
//taken from kinnays error list directly
#[allow(nonstandard_style)]
#[repr(u32)]
#[derive(Debug, EnumTryInto, Clone, Copy)]
pub enum ErrorCode {
Core_Unknown = 0x00010001,
Core_NotImplemented = 0x00010002,
Core_InvalidPointer = 0x00010003,
Core_OperationAborted = 0x00010004,
Core_Exception = 0x00010005,
Core_AccessDenied = 0x00010006,
Core_InvalidHandle = 0x00010007,
Core_InvalidIndex = 0x00010008,
Core_OutOfMemory = 0x00010009,
Core_InvalidArgument = 0x0001000A,
Core_Timeout = 0x0001000B,
Core_InitializationFailure = 0x0001000C,
Core_CallInitiationFailure = 0x0001000D,
Core_RegistrationError = 0x0001000E,
Core_BufferOverflow = 0x0001000F,
Core_InvalidLockState = 0x00010010,
Core_InvalidSequence = 0x00010011,
Core_SystemError = 0x00010012,
Core_Cancelled = 0x00010013,
DDL_InvalidSignature = 0x00020001,
DDL_IncorrectVersion = 0x00020002,
RendezVous_ConnectionFailure = 0x00030001,
RendezVous_NotAuthenticated = 0x00030002,
RendezVous_InvalidUsername = 0x00030064,
RendezVous_InvalidPassword = 0x00030065,
RendezVous_UsernameAlreadyExists = 0x00030066,
RendezVous_AccountDisabled = 0x00030067,
RendezVous_AccountExpired = 0x00030068,
RendezVous_ConcurrentLoginDenied = 0x00030069,
RendezVous_EncryptionFailure = 0x0003006A,
RendezVous_InvalidPID = 0x0003006B,
RendezVous_MaxConnectionsReached = 0x0003006C,
RendezVous_InvalidGID = 0x0003006D,
RendezVous_InvalidControlScriptID = 0x0003006E,
RendezVous_InvalidOperationInLiveEnvironment = 0x0003006F,
RendezVous_DuplicateEntry = 0x00030070,
RendezVous_ControlScriptFailure = 0x00030071,
RendezVous_ClassNotFound = 0x00030072,
RendezVous_SessionVoid = 0x00030073,
RendezVous_DDLMismatch = 0x00030075,
RendezVous_InvalidConfiguration = 0x00030076,
RendezVous_SessionFull = 0x000300C8,
RendezVous_InvalidGatheringPassword = 0x000300C9,
RendezVous_WithoutParticipationPeriod = 0x000300CA,
RendezVous_PersistentGatheringCreationMax = 0x000300CB,
RendezVous_PersistentGatheringParticipationMax = 0x000300CC,
RendezVous_DeniedByParticipants = 0x000300CD,
RendezVous_ParticipantInBlackList = 0x000300CE,
RendezVous_GameServerMaintenance = 0x000300CF,
RendezVous_OperationPostpone = 0x000300D0,
RendezVous_OutOfRatingRange = 0x000300D1,
RendezVous_ConnectionDisconnected = 0x000300D2,
RendezVous_InvalidOperation = 0x000300D3,
RendezVous_NotParticipatedGathering = 0x000300D4,
RendezVous_MatchmakeSessionUserPasswordUnmatch = 0x000300D5,
RendezVous_MatchmakeSessionSystemPasswordUnmatch = 0x000300D6,
RendezVous_UserIsOffline = 0x000300D7,
RendezVous_AlreadyParticipatedGathering = 0x000300D8,
RendezVous_PermissionDenied = 0x000300D9,
RendezVous_NotFriend = 0x000300DA,
RendezVous_SessionClosed = 0x000300DB,
RendezVous_DatabaseTemporarilyUnavailable = 0x000300DC,
RendezVous_InvalidUniqueId = 0x000300DD,
RendezVous_MatchmakingWithdrawn = 0x000300DE,
RendezVous_LimitExceeded = 0x000300DF,
RendezVous_AccountTemporarilyDisabled = 0x000300E0,
RendezVous_PartiallyServiceClosed = 0x000300E1,
RendezVous_ConnectionDisconnectedForConcurrentLogin = 0x000300E2,
PythonCore_Exception = 0x00040001,
PythonCore_TypeError = 0x00040002,
PythonCore_IndexError = 0x00040003,
PythonCore_InvalidReference = 0x00040004,
PythonCore_CallFailure = 0x00040005,
PythonCore_MemoryError = 0x00040006,
PythonCore_KeyError = 0x00040007,
PythonCore_OperationError = 0x00040008,
PythonCore_ConversionError = 0x00040009,
PythonCore_ValidationError = 0x0004000A,
Transport_Unknown = 0x00050001,
Transport_ConnectionFailure = 0x00050002,
Transport_InvalidUrl = 0x00050003,
Transport_InvalidKey = 0x00050004,
Transport_InvalidURLType = 0x00050005,
Transport_DuplicateEndpoint = 0x00050006,
Transport_IOError = 0x00050007,
Transport_Timeout = 0x00050008,
Transport_ConnectionReset = 0x00050009,
Transport_IncorrectRemoteAuthentication = 0x0005000A,
Transport_ServerRequestError = 0x0005000B,
Transport_DecompressionFailure = 0x0005000C,
Transport_ReliableSendBufferFullFatal = 0x0005000D,
Transport_UPnPCannotInit = 0x0005000E,
Transport_UPnPCannotAddMapping = 0x0005000F,
Transport_NatPMPCannotInit = 0x00050010,
Transport_NatPMPCannotAddMapping = 0x00050011,
Transport_UnsupportedNAT = 0x00050013,
Transport_DnsError = 0x00050014,
Transport_ProxyError = 0x00050015,
Transport_DataRemaining = 0x00050016,
Transport_NoBuffer = 0x00050017,
Transport_NotFound = 0x00050018,
Transport_TemporaryServerError = 0x00050019,
Transport_PermanentServerError = 0x0005001A,
Transport_ServiceUnavailable = 0x0005001B,
Transport_ReliableSendBufferFull = 0x0005001C,
Transport_InvalidStation = 0x0005001D,
Transport_InvalidSubStreamID = 0x0005001E,
Transport_PacketBufferFull = 0x0005001F,
Transport_NatTraversalError = 0x00050020,
Transport_NatCheckError = 0x00050021,
DOCore_StationNotReached = 0x00060001,
DOCore_TargetStationDisconnect = 0x00060002,
DOCore_LocalStationLeaving = 0x00060003,
DOCore_ObjectNotFound = 0x00060004,
DOCore_InvalidRole = 0x00060005,
DOCore_CallTimeout = 0x00060006,
DOCore_RMCDispatchFailed = 0x00060007,
DOCore_MigrationInProgress = 0x00060008,
DOCore_NoAuthority = 0x00060009,
DOCore_NoTargetStationSpecified = 0x0006000A,
DOCore_JoinFailed = 0x0006000B,
DOCore_JoinDenied = 0x0006000C,
DOCore_ConnectivityTestFailed = 0x0006000D,
DOCore_Unknown = 0x0006000E,
DOCore_UnfreedReferences = 0x0006000F,
DOCore_JobTerminationFailed = 0x00060010,
DOCore_InvalidState = 0x00060011,
DOCore_FaultRecoveryFatal = 0x00060012,
DOCore_FaultRecoveryJobProcessFailed = 0x00060013,
DOCore_StationInconsitency = 0x00060014,
DOCore_AbnormalMasterState = 0x00060015,
DOCore_VersionMismatch = 0x00060016,
FPD_NotInitialized = 0x00650000,
FPD_AlreadyInitialized = 0x00650001,
FPD_NotConnected = 0x00650002,
FPD_Connected = 0x00650003,
FPD_InitializationFailure = 0x00650004,
FPD_OutOfMemory = 0x00650005,
FPD_RmcFailed = 0x00650006,
FPD_InvalidArgument = 0x00650007,
FPD_InvalidLocalAccountID = 0x00650008,
FPD_InvalidPrincipalID = 0x00650009,
FPD_InvalidLocalFriendCode = 0x0065000A,
FPD_LocalAccountNotExists = 0x0065000B,
FPD_LocalAccountNotLoaded = 0x0065000C,
FPD_LocalAccountAlreadyLoaded = 0x0065000D,
FPD_FriendAlreadyExists = 0x0065000E,
FPD_FriendNotExists = 0x0065000F,
FPD_FriendNumMax = 0x00650010,
FPD_NotFriend = 0x00650011,
FPD_FileIO = 0x00650012,
FPD_P2PInternetProhibited = 0x00650013,
FPD_Unknown = 0x00650014,
FPD_InvalidState = 0x00650015,
FPD_AddFriendProhibited = 0x00650017,
FPD_InvalidAccount = 0x00650019,
FPD_BlacklistedByMe = 0x0065001A,
FPD_FriendAlreadyAdded = 0x0065001C,
FPD_MyFriendListLimitExceed = 0x0065001D,
FPD_RequestLimitExceed = 0x0065001E,
FPD_InvalidMessageID = 0x0065001F,
FPD_MessageIsNotMine = 0x00650020,
FPD_MessageIsNotForMe = 0x00650021,
FPD_FriendRequestBlocked = 0x00650022,
FPD_NotInMyFriendList = 0x00650023,
FPD_FriendListedByMe = 0x00650024,
FPD_NotInMyBlacklist = 0x00650025,
FPD_IncompatibleAccount = 0x00650026,
FPD_BlockSettingChangeNotAllowed = 0x00650027,
FPD_SizeLimitExceeded = 0x00650028,
FPD_OperationNotAllowed = 0x00650029,
FPD_NotNetworkAccount = 0x0065002A,
FPD_NotificationNotFound = 0x0065002B,
FPD_PreferenceNotInitialized = 0x0065002C,
FPD_FriendRequestNotAllowed = 0x0065002D,
Ranking_NotInitialized = 0x00670001,
Ranking_InvalidArgument = 0x00670002,
Ranking_RegistrationError = 0x00670003,
Ranking_NotFound = 0x00670005,
Ranking_InvalidScore = 0x00670006,
Ranking_InvalidDataSize = 0x00670007,
Ranking_PermissionDenied = 0x00670009,
Ranking_Unknown = 0x0067000A,
Ranking_NotImplemented = 0x0067000B,
Authentication_NASAuthenticateError = 0x00680001,
Authentication_TokenParseError = 0x00680002,
Authentication_HttpConnectionError = 0x00680003,
Authentication_HttpDNSError = 0x00680004,
Authentication_HttpGetProxySetting = 0x00680005,
Authentication_TokenExpired = 0x00680006,
Authentication_ValidationFailed = 0x00680007,
Authentication_InvalidParam = 0x00680008,
Authentication_PrincipalIdUnmatched = 0x00680009,
Authentication_MoveCountUnmatch = 0x0068000A,
Authentication_UnderMaintenance = 0x0068000B,
Authentication_UnsupportedVersion = 0x0068000C,
Authentication_ServerVersionIsOld = 0x0068000D,
Authentication_Unknown = 0x0068000E,
Authentication_ClientVersionIsOld = 0x0068000F,
Authentication_AccountLibraryError = 0x00680010,
Authentication_ServiceNoLongerAvailable = 0x00680011,
Authentication_UnknownApplication = 0x00680012,
Authentication_ApplicationVersionIsOld = 0x00680013,
Authentication_OutOfService = 0x00680014,
Authentication_NetworkServiceLicenseRequired = 0x00680015,
Authentication_NetworkServiceLicenseSystemError = 0x00680016,
Authentication_NetworkServiceLicenseError3 = 0x00680017,
Authentication_NetworkServiceLicenseError4 = 0x00680018,
DataStore_Unknown = 0x00690001,
DataStore_InvalidArgument = 0x00690002,
DataStore_PermissionDenied = 0x00690003,
DataStore_NotFound = 0x00690004,
DataStore_AlreadyLocked = 0x00690005,
DataStore_UnderReviewing = 0x00690006,
DataStore_Expired = 0x00690007,
DataStore_InvalidCheckToken = 0x00690008,
DataStore_SystemFileError = 0x00690009,
DataStore_OverCapacity = 0x0069000A,
DataStore_OperationNotAllowed = 0x0069000B,
DataStore_InvalidPassword = 0x0069000C,
DataStore_ValueNotEqual = 0x0069000D,
ServiceItem_Unknown = 0x006C0001,
ServiceItem_InvalidArgument = 0x006C0002,
ServiceItem_EShopUnknownHttpError = 0x006C0003,
ServiceItem_EShopResponseParseError = 0x006C0004,
ServiceItem_NotOwned = 0x006C0005,
ServiceItem_InvalidLimitationType = 0x006C0006,
ServiceItem_ConsumptionRightShortage = 0x006C0007,
MatchmakeReferee_Unknown = 0x006F0001,
MatchmakeReferee_InvalidArgument = 0x006F0002,
MatchmakeReferee_AlreadyExists = 0x006F0003,
MatchmakeReferee_NotParticipatedGathering = 0x006F0004,
MatchmakeReferee_NotParticipatedRound = 0x006F0005,
MatchmakeReferee_StatsNotFound = 0x006F0006,
MatchmakeReferee_RoundNotFound = 0x006F0007,
MatchmakeReferee_RoundArbitrated = 0x006F0008,
MatchmakeReferee_RoundNotArbitrated = 0x006F0009,
Subscriber_Unknown = 0x00700001,
Subscriber_InvalidArgument = 0x00700002,
Subscriber_OverLimit = 0x00700003,
Subscriber_PermissionDenied = 0x00700004,
Ranking2_Unknown = 0x00710001,
Ranking2_InvalidArgument = 0x00710002,
Ranking2_InvalidScore = 0x00710003,
SmartDeviceVoiceChat_Unknown = 0x00720001,
SmartDeviceVoiceChat_InvalidArgument = 0x00720002,
SmartDeviceVoiceChat_InvalidResponse = 0x00720003,
SmartDeviceVoiceChat_InvalidAccessToken = 0x00720004,
SmartDeviceVoiceChat_Unauthorized = 0x00720005,
SmartDeviceVoiceChat_AccessError = 0x00720006,
SmartDeviceVoiceChat_UserNotFound = 0x00720007,
SmartDeviceVoiceChat_RoomNotFound = 0x00720008,
SmartDeviceVoiceChat_RoomNotActivated = 0x00720009,
SmartDeviceVoiceChat_ApplicationNotSupported = 0x0072000A,
SmartDeviceVoiceChat_InternalServerError = 0x0072000B,
SmartDeviceVoiceChat_ServiceUnavailable = 0x0072000C,
SmartDeviceVoiceChat_UnexpectedError = 0x0072000D,
SmartDeviceVoiceChat_UnderMaintenance = 0x0072000E,
SmartDeviceVoiceChat_ServiceNoLongerAvailable = 0x0072000F,
SmartDeviceVoiceChat_AccountTemporarilyDisabled = 0x00720010,
SmartDeviceVoiceChat_PermissionDenied = 0x00720011,
SmartDeviceVoiceChat_NetworkServiceLicenseRequired = 0x00720012,
SmartDeviceVoiceChat_AccountLibraryError = 0x00720013,
SmartDeviceVoiceChat_GameModeNotFound = 0x00720014,
Screening_Unknown = 0x00730001,
Screening_InvalidArgument = 0x00730002,
Screening_NotFound = 0x00730003,
Custom_Unknown = 0x00740001,
Ess_Unknown = 0x00750001,
Ess_GameSessionError = 0x00750002,
Ess_GameSessionMaintenance = 0x00750003,
}
impl Into<u32> for ErrorCode {
fn into(self) -> u32 {
unsafe { transmute(self) }
}
}
#[cfg(test)]
mod test {
use hmac::digest::consts::U5;
use hmac::digest::KeyInit;
use rc4::{Rc4, StreamCipher};
use crate::rmc::response::ErrorCode;
#[test]
fn test() {
let mut data_orig = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 69, 4, 20];
let mut data = data_orig;
let mut rc4: Rc4<U5> =
Rc4::new_from_slice("FUCKE".as_bytes().into()).expect("invalid key");
rc4.apply_keystream(&mut data);
assert_ne!(data_orig, data);
let mut rc4: Rc4<U5> =
Rc4::new_from_slice("FUCKE".as_bytes().into()).expect("invalid key");
rc4.apply_keystream(&mut data);
assert_eq!(data_orig, data);
}
#[test]
fn test_enum_equivilance() {
let val: u32 = ErrorCode::Core_Unknown.into();
assert_eq!(val, 0x00010001)
}
}

View file

@ -0,0 +1,42 @@
use std::io::{Read, Write};
use v_byte_helpers::{IS_BIG_ENDIAN, ReadExtensions};
use super::{Result, RmcSerialize};
#[derive(Debug, Default)]
pub struct Any{
pub name: String,
pub data: Vec<u8>
}
impl RmcSerialize for Any{
fn serialize(&self, writer: &mut dyn Write) -> Result<()> {
self.name.serialize(writer)?;
let u32_len = self.data.len() as u32;
u32_len.serialize(writer)?;
u32_len.serialize(writer)?;
self.data.serialize(writer)?;
Ok(())
}
fn deserialize(mut reader: &mut dyn Read) -> Result<Self> {
let name = String::deserialize(reader)?;
// also length ?
let _len2: u32 = reader.read_struct(IS_BIG_ENDIAN)?;
let length: u32 = reader.read_struct(IS_BIG_ENDIAN)?;
let mut data = vec![0; length as usize];
reader.read_exact(&mut data)?;
Ok(
Any{
name,
data
}
)
}
}

View file

@ -0,0 +1,29 @@
use std::io::{Read, Write};
use crate::rmc::structures::RmcSerialize;
impl<'a> RmcSerialize for &'a [u8]{
fn serialize(&self, writer: &mut dyn Write) -> crate::rmc::structures::Result<()> {
let u32_size = self.len() as u32;
writer.write(bytemuck::bytes_of(&u32_size))?;
writer.write(self)?;
Ok(())
}
/// DO NOT USE (also maybe split off the serialize and deserialize functions at some point)
fn deserialize(_reader: &mut dyn Read) -> crate::rmc::structures::Result<Self> {
panic!("cannot deserialize to a u8 slice reference (use this ONLY for writing)")
}
}
impl RmcSerialize for Box<[u8]>{
fn serialize(&self, writer: &mut dyn Write) -> crate::rmc::structures::Result<()> {
(&self[..]).serialize(writer)
}
fn deserialize(reader: &mut dyn Read) -> crate::rmc::structures::Result<Self> {
Vec::deserialize(reader).map(|v| v.into_boxed_slice())
}
}

View file

@ -0,0 +1,13 @@
use macros::RmcSerialize;
use rnex_core::kerberos::KerberosDateTime;
#[derive(Debug, RmcSerialize)]
#[rmc_struct(1)]
pub struct ConnectionData{
pub station_url: String,
pub special_protocols: Vec<u8>,
pub special_station_url: String,
pub date_time: KerberosDateTime
}

View file

@ -0,0 +1,60 @@
use std::array::from_fn;
use std::io::{Read, Write};
use std::mem::MaybeUninit;
use bytemuck::bytes_of;
use serde::Serialize;
use v_byte_helpers::{IS_BIG_ENDIAN, ReadExtensions};
use crate::rmc::structures::RmcSerialize;
// this is also for implementing `Buffer` this is tecnically not the same as its handled internaly
// probably but as it has the same mapping it doesn't matter and simplifies things
impl<T: RmcSerialize> RmcSerialize for Vec<T>{
fn serialize(&self, writer: &mut dyn Write) -> crate::rmc::structures::Result<()> {
let u32_len = self.len() as u32;
writer.write_all(bytes_of(&u32_len))?;
for e in self{
e.serialize(writer)?;
}
Ok(())
}
fn deserialize(mut reader: &mut dyn Read) -> crate::rmc::structures::Result<Self> {
let len: u32 = reader.read_struct(IS_BIG_ENDIAN)?;
let mut vec = Vec::with_capacity(len as usize);
for _ in 0..len{
vec.push(T::deserialize(reader)?);
}
Ok(vec)
}
}
impl<const LEN: usize, T: RmcSerialize> RmcSerialize for [T; LEN]{
fn serialize(&self, writer: &mut dyn Write) -> crate::rmc::structures::Result<()> {
for i in 0..LEN{
self[i].serialize(writer)?;
}
Ok(())
}
fn deserialize(reader: &mut dyn Read) -> crate::rmc::structures::Result<Self> {
let mut arr = [const { MaybeUninit::<T>::uninit() }; LEN];
for i in 0..LEN{
arr[i] = MaybeUninit::new(T::deserialize(reader)?);
}
// all of the elements are now initialized so it is safe to assume they are initialized
let arr = arr.map(|v| unsafe{ v.assume_init() });
Ok(arr)
}
}

View file

@ -0,0 +1,126 @@
use rnex_core::kerberos::KerberosDateTime;
use rnex_core::rmc::structures::variant::Variant;
use macros::RmcSerialize;
// rmc structure
#[derive(RmcSerialize, Debug, Clone, Default)]
#[rmc_struct(0)]
pub struct Gathering {
pub self_gid: u32,
pub owner_pid: u32,
pub host_pid: u32,
pub minimum_participants: u16,
pub maximum_participants: u16,
pub participant_policy: u32,
pub policy_argument: u32,
pub flags: u32,
pub state: u32,
pub description: String,
}
// rmc structure
#[derive(RmcSerialize, Debug, Clone, Default)]
#[rmc_struct(0)]
pub struct MatchmakeParam {
pub params: Vec<(String, Variant)>,
}
// rmc structure
#[derive(RmcSerialize, Debug, Clone, Default)]
#[rmc_struct(3)]
pub struct MatchmakeSession {
//inherits from
#[extends]
pub gathering: Gathering,
pub gamemode: u32,
pub attributes: Vec<u32>,
pub open_participation: bool,
pub matchmake_system_type: u32,
pub application_buffer: Vec<u8>,
pub participation_count: u32,
pub progress_score: u8,
pub session_key: Vec<u8>,
pub option0: u32,
pub matchmake_param: MatchmakeParam,
pub datetime: KerberosDateTime,
pub user_password: String,
pub refer_gid: u32,
pub user_password_enabled: bool,
pub system_password_enabled: bool,
}
#[derive(RmcSerialize, Debug, Clone)]
#[rmc_struct(3)]
pub struct MatchmakeSessionSearchCriteria {
pub attribs: Vec<String>,
pub game_mode: String,
pub minimum_participants: String,
pub maximum_participants: String,
pub matchmake_system_type: String,
pub vacant_only: bool,
pub exclude_locked: bool,
pub exclude_non_host_pid: bool,
pub selection_method: u32,
pub vacant_participants: u16,
pub matchmake_param: MatchmakeParam,
pub exclude_user_password_set: bool,
pub exclude_system_password_set: bool,
pub refer_gid: u32,
}
#[derive(RmcSerialize, Debug, Clone)]
#[rmc_struct(0)]
pub struct AutoMatchmakeParam {
pub matchmake_session: MatchmakeSession,
pub additional_participants: Vec<u32>,
pub gid_for_participation_check: u32,
pub auto_matchmake_option: u32,
pub join_message: String,
pub participation_count: u16,
pub search_criteria: Vec<MatchmakeSessionSearchCriteria>,
pub target_gids: Vec<u32>,
}
#[derive(RmcSerialize, Debug, Clone)]
#[rmc_struct(0)]
pub struct CreateMatchmakeSessionParam {
pub matchmake_session: MatchmakeSession,
pub additional_participants: Vec<u32>,
pub gid_for_participation_check: u32,
pub create_matchmake_session_option: u32,
pub join_message: String,
pub participation_count: u16,
}
#[derive(RmcSerialize, Debug, Clone)]
#[rmc_struct(0)]
pub struct MatchmakeBlockListParam {
option_flag: u32,
}
#[derive(RmcSerialize, Debug, Clone)]
#[rmc_struct(0)]
pub struct JoinMatchmakeSessionParam {
pub gid: u32,
pub additional_participants: Vec<u32>,
pub gid_for_participation_check: u32,
pub join_matchmake_session_open: u32,
pub join_matchmake_session_behavior: u8,
pub user_password: String,
pub system_password: String,
pub join_message: String,
pub participation_count: u16,
//pub extra_participant: u16,
//pub block_list_param: MatchmakeBlockListParam
}
pub mod gathering_flags {
pub const PERSISTENT_GATHERING: u32 = 0x1;
pub const DISCONNECT_CHANGE_OWNER: u32 = 0x10;
pub const PERSISTENT_GATHERING_LEAVE_PARTICIPATION: u32 = 0x40;
pub const PERSISTENT_GATHERING_ALLOW_ZERO_USERS: u32 = 0x80;
pub const PARTICIPANTS_CHANGE_OWNER: u32 = 0x200;
pub const VERBOSE_PARTICIPANTS: u32 = 0x400;
pub const VERBOSE_PARTICIPANTS_EX: u32 = 0x800;
}

View file

@ -0,0 +1,60 @@
use std::io;
use std::io::{Read, Write};
use std::string::FromUtf8Error;
use thiserror::Error;
//ideas for the future: make a proc macro library which allows generation of struct reads
#[derive(Error, Debug)]
pub enum Error{
#[error("Io Error: {0}")]
Io(#[from] io::Error),
#[error("UTF8 conversion Error: {0}")]
Utf8(#[from] FromUtf8Error),
#[error("unexpected value: {0}")]
UnexpectedValue(u64),
#[error("version mismatch: {0}")]
VersionMismatch(u8),
#[error("an error occurred reading the station url")]
StationUrlInvalid
}
pub type Result<T> = std::result::Result<T, Error>;
pub mod string;
pub mod any;
pub mod qresult;
pub mod buffer;
pub mod connection_data;
pub mod rmc_struct;
pub mod list;
pub mod qbuffer;
pub mod primitives;
pub mod matchmake;
pub mod variant;
pub mod ranking;
mod networking;
pub trait RmcSerialize{
fn serialize(&self, writer: &mut dyn Write) -> Result<()>;
fn deserialize(reader: &mut dyn Read) -> Result<Self> where Self: Sized;
fn to_data(&self) -> Vec<u8>{
let mut data = Vec::new();
self.serialize(&mut data).expect("out of memory or something");
data
}
}
impl RmcSerialize for (){
fn serialize(&self, writer: &mut dyn Write) -> Result<()> {
Ok(())
}
fn deserialize(reader: &mut dyn Read) -> Result<Self> {
Ok(())
}
}

View file

@ -0,0 +1,33 @@
use std::io::{Read, Write};
use std::net::{Ipv4Addr, SocketAddrV4};
use rnex_core::prudp::virtual_port::VirtualPort;
use crate::rmc::structures::RmcSerialize;
impl RmcSerialize for SocketAddrV4{
fn serialize(&self, writer: &mut dyn Write) -> crate::rmc::structures::Result<()> {
self.ip().to_bits().serialize(writer)?;
self.port().serialize(writer)?;
Ok(())
}
fn deserialize(reader: &mut dyn Read) -> crate::rmc::structures::Result<Self> {
let ip = u32::deserialize(reader)?;
let port = u16::deserialize(reader)?;
Ok(SocketAddrV4::new(Ipv4Addr::from_bits(ip), port))
}
}
impl RmcSerialize for VirtualPort{
fn serialize(&self, writer: &mut dyn Write) -> crate::rmc::structures::Result<()> {
self.0.serialize(writer)?;
Ok(())
}
fn deserialize(reader: &mut dyn Read) -> crate::rmc::structures::Result<Self> {
Ok(Self(u8::deserialize(reader)?))
}
}

View file

@ -0,0 +1,241 @@
use std::io::{Read, Write};
use bytemuck::bytes_of;
use v_byte_helpers::{IS_BIG_ENDIAN, ReadExtensions};
use crate::rmc::structures::RmcSerialize;
impl RmcSerialize for u8{
fn serialize(&self, writer: &mut dyn Write) -> crate::rmc::structures::Result<()> {
Ok(writer.write_all(bytes_of(self))?)
}
fn deserialize(mut reader: &mut dyn Read) -> crate::rmc::structures::Result<Self> {
Ok(reader.read_struct(IS_BIG_ENDIAN)?)
}
}
impl RmcSerialize for i8{
fn serialize(&self, writer: &mut dyn Write) -> crate::rmc::structures::Result<()> {
Ok(writer.write_all(bytes_of(self))?)
}
fn deserialize(mut reader: &mut dyn Read) -> crate::rmc::structures::Result<Self> {
Ok(reader.read_struct(IS_BIG_ENDIAN)?)
}
}
impl RmcSerialize for u16{
fn serialize(&self, writer: &mut dyn Write) -> crate::rmc::structures::Result<()> {
Ok(writer.write_all(bytes_of(self))?)
}
fn deserialize(mut reader: &mut dyn Read) -> crate::rmc::structures::Result<Self> {
Ok(reader.read_struct(IS_BIG_ENDIAN)?)
}
}
impl RmcSerialize for i16{
fn serialize(&self, writer: &mut dyn Write) -> crate::rmc::structures::Result<()> {
Ok(writer.write_all(bytes_of(self))?)
}
fn deserialize(mut reader: &mut dyn Read) -> crate::rmc::structures::Result<Self> {
Ok(reader.read_struct(IS_BIG_ENDIAN)?)
}
}
impl RmcSerialize for u32{
fn serialize(&self, writer: &mut dyn Write) -> crate::rmc::structures::Result<()> {
Ok(writer.write_all(bytes_of(self))?)
}
fn deserialize(mut reader: &mut dyn Read) -> crate::rmc::structures::Result<Self> {
Ok(reader.read_struct(IS_BIG_ENDIAN)?)
}
}
impl RmcSerialize for i32{
fn serialize(&self, writer: &mut dyn Write) -> crate::rmc::structures::Result<()> {
Ok(writer.write_all(bytes_of(self))?)
}
fn deserialize(mut reader: &mut dyn Read) -> crate::rmc::structures::Result<Self> {
Ok(reader.read_struct(IS_BIG_ENDIAN)?)
}
}
impl RmcSerialize for u64{
fn serialize(&self, writer: &mut dyn Write) -> crate::rmc::structures::Result<()> {
Ok(writer.write_all(bytes_of(self))?)
}
fn deserialize(mut reader: &mut dyn Read) -> crate::rmc::structures::Result<Self> {
Ok(reader.read_struct(IS_BIG_ENDIAN)?)
}
}
impl RmcSerialize for i64{
fn serialize(&self, writer: &mut dyn Write) -> crate::rmc::structures::Result<()> {
Ok(writer.write_all(bytes_of(self))?)
}
fn deserialize(mut reader: &mut dyn Read) -> crate::rmc::structures::Result<Self> {
Ok(reader.read_struct(IS_BIG_ENDIAN)?)
}
}
impl RmcSerialize for f64{
fn serialize(&self, writer: &mut dyn Write) -> crate::rmc::structures::Result<()> {
Ok(writer.write_all(bytes_of(self))?)
}
fn deserialize(mut reader: &mut dyn Read) -> crate::rmc::structures::Result<Self> {
Ok(reader.read_struct(IS_BIG_ENDIAN)?)
}
}
impl RmcSerialize for bool{
fn serialize(&self, writer: &mut dyn Write) -> crate::rmc::structures::Result<()> {
match self{
true => writer.write_all(&[1])?,
false => writer.write_all(&[0])?,
}
Ok(())
}
fn deserialize(reader: &mut dyn Read) -> crate::rmc::structures::Result<Self> {
Ok(u8::deserialize(reader)? != 0)
}
}
impl<T: RmcSerialize, U: RmcSerialize> RmcSerialize for (T, U){
fn serialize(&self, writer: &mut dyn Write) -> crate::rmc::structures::Result<()> {
self.0.serialize(writer)?;
self.1.serialize(writer)?;
Ok(())
}
fn deserialize(reader: &mut dyn Read) -> crate::rmc::structures::Result<Self> {
let first = T::deserialize(reader)?;
let second = U::deserialize(reader)?;
Ok((first, second))
}
}
impl<T: RmcSerialize, U: RmcSerialize, V: RmcSerialize> RmcSerialize for (T, U, V){
fn serialize(&self, writer: &mut dyn Write) -> crate::rmc::structures::Result<()> {
self.0.serialize(writer)?;
self.1.serialize(writer)?;
self.2.serialize(writer)?;
Ok(())
}
fn deserialize(reader: &mut dyn Read) -> crate::rmc::structures::Result<Self> {
let first = T::deserialize(reader)?;
let second = U::deserialize(reader)?;
let third = V::deserialize(reader)?;
Ok((first, second, third))
}
}
impl<T: RmcSerialize, U: RmcSerialize, V: RmcSerialize, W: RmcSerialize> RmcSerialize for (T, U, V, W){
fn serialize(&self, writer: &mut dyn Write) -> crate::rmc::structures::Result<()> {
self.0.serialize(writer)?;
self.1.serialize(writer)?;
self.2.serialize(writer)?;
self.3.serialize(writer)?;
Ok(())
}
fn deserialize(reader: &mut dyn Read) -> crate::rmc::structures::Result<Self> {
let first = T::deserialize(reader)?;
let second = U::deserialize(reader)?;
let third = V::deserialize(reader)?;
let fourth = W::deserialize(reader)?;
Ok((first, second, third, fourth))
}
}
impl<T: RmcSerialize, U: RmcSerialize, V: RmcSerialize, W: RmcSerialize, X: RmcSerialize> RmcSerialize for (T, U, V, W, X){
fn serialize(&self, writer: &mut dyn Write) -> crate::rmc::structures::Result<()> {
self.0.serialize(writer)?;
self.1.serialize(writer)?;
self.2.serialize(writer)?;
self.3.serialize(writer)?;
self.4.serialize(writer)?;
Ok(())
}
fn deserialize(reader: &mut dyn Read) -> crate::rmc::structures::Result<Self> {
let first = T::deserialize(reader)?;
let second = U::deserialize(reader)?;
let third = V::deserialize(reader)?;
let fourth = W::deserialize(reader)?;
let fifth = X::deserialize(reader)?;
Ok((first, second, third, fourth, fifth))
}
}
impl<T: RmcSerialize, U: RmcSerialize, V: RmcSerialize, W: RmcSerialize, X: RmcSerialize, Y: RmcSerialize> RmcSerialize for (T, U, V, W, X, Y){
fn serialize(&self, writer: &mut dyn Write) -> crate::rmc::structures::Result<()> {
self.0.serialize(writer)?;
self.1.serialize(writer)?;
self.2.serialize(writer)?;
self.3.serialize(writer)?;
self.4.serialize(writer)?;
self.5.serialize(writer)?;
Ok(())
}
fn deserialize(reader: &mut dyn Read) -> crate::rmc::structures::Result<Self> {
let first = T::deserialize(reader)?;
let second = U::deserialize(reader)?;
let third = V::deserialize(reader)?;
let fourth = W::deserialize(reader)?;
let fifth = X::deserialize(reader)?;
let sixth = Y::deserialize(reader)?;
Ok((first, second, third, fourth, fifth, sixth))
}
}
impl<T: RmcSerialize, U: RmcSerialize, V: RmcSerialize, W: RmcSerialize, X: RmcSerialize, Y: RmcSerialize, Z: RmcSerialize> RmcSerialize for (T, U, V, W, X, Y, Z){
fn serialize(&self, writer: &mut dyn Write) -> crate::rmc::structures::Result<()> {
self.0.serialize(writer)?;
self.1.serialize(writer)?;
self.2.serialize(writer)?;
self.3.serialize(writer)?;
self.4.serialize(writer)?;
self.5.serialize(writer)?;
self.6.serialize(writer)?;
Ok(())
}
fn deserialize(reader: &mut dyn Read) -> crate::rmc::structures::Result<Self> {
let first = T::deserialize(reader)?;
let second = U::deserialize(reader)?;
let third = V::deserialize(reader)?;
let fourth = W::deserialize(reader)?;
let fifth = X::deserialize(reader)?;
let sixth = Y::deserialize(reader)?;
let seventh = Z::deserialize(reader)?;
Ok((first, second, third, fourth, fifth, sixth, seventh))
}
}
impl<T: RmcSerialize> RmcSerialize for Box<T>{
fn serialize(&self, writer: &mut dyn Write) -> crate::rmc::structures::Result<()> {
self.as_ref().serialize(writer)
}
fn deserialize(reader: &mut dyn Read) -> crate::rmc::structures::Result<Self> {
T::deserialize(reader).map(Box::new)
}
}

View file

@ -0,0 +1,29 @@
use std::io::{Read, Write};
use bytemuck::bytes_of;
use v_byte_helpers::{IS_BIG_ENDIAN, ReadExtensions};
use crate::rmc::structures::{Result, RmcSerialize};
#[derive(Debug)]
pub struct QBuffer(pub Vec<u8>);
impl RmcSerialize for QBuffer{
fn serialize(&self, writer: &mut dyn Write) -> Result<()> {
let len_u16 = self.0.len() as u16;
writer.write(bytes_of(&len_u16))?;
writer.write(&self.0)?;
Ok(())
}
fn deserialize(mut reader: &mut dyn Read) -> Result<Self> {
let size: u16 = reader.read_struct(IS_BIG_ENDIAN)?;
let mut vec = vec![0; size as usize];
reader.read_exact(&mut vec)?;
Ok(Self(vec))
}
}

View file

@ -0,0 +1,37 @@
use std::io::{Read, Write};
use bytemuck::{bytes_of, Pod, Zeroable};
use v_byte_helpers::SwapEndian;
use v_byte_helpers::{IS_BIG_ENDIAN, ReadExtensions};
use crate::rmc::response::ErrorCode;
use crate::rmc::structures::{RmcSerialize, Result};
pub const ERROR_MASK: u32 = 1 << 31;
#[derive(Pod, Zeroable, Copy, Clone, SwapEndian, Debug)]
#[repr(transparent)]
pub struct QResult(u32);
impl QResult{
pub fn success(error_code: ErrorCode) -> Self{
let val: u32 = error_code.into();
Self(val & (!ERROR_MASK))
}
pub fn error(error_code: ErrorCode) -> Self{
let val: u32 = error_code.into();
Self(val | ERROR_MASK)
}
}
impl RmcSerialize for QResult{
fn serialize(&self, writer: &mut dyn Write) -> Result<()> {
writer.write(bytes_of(self))?;
Ok(())
}
fn deserialize(mut reader: &mut dyn Read) -> Result<Self> {
Ok(reader.read_struct(IS_BIG_ENDIAN)?)
}
}

View file

@ -0,0 +1,69 @@
use bytemuck::{Pod, Zeroable};
use macros::RmcSerialize;
use rnex_core::rmc::structures::qbuffer::QBuffer;
#[derive(RmcSerialize, Debug)]
#[rmc_struct(0)]
struct UploadCompetitionData{
winning_team/*?*/: u32,
splatfest_id/*?*/: u32,
unk_2/*?*/: u32,
unk_3: u32,
team_id_1: u8,
team_id_2: u8,
unk_5: u32,
player_data/*?*/: QBuffer,
}
#[derive(Copy, Clone, Pod, Zeroable)]
#[repr(C)]
struct UserData{
name: [u16; 0x10],
}
#[cfg(test)]
mod test{
use std::io::Cursor;
use bytemuck::from_bytes;
use tokio::io::AsyncReadExt;
use crate::rmc::structures::ranking::{UploadCompetitionData, UserData};
use rnex_core::rmc::structures::RmcSerialize;
#[test]
fn test() {
let data: [u8; 0xBD] = [
0x00, 0xB8, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xFC, 0x03, 0x00, 0x00, 0x02, 0x00, 0x00,
0x00, 0x1F, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0xA0, 0x00, 0x00, 0x49, 0x00,
0x7A, 0x00, 0x7A, 0x00, 0x79, 0x00, 0x53, 0x00, 0x50, 0x00, 0x46, 0x00, 0x4E, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0xF2, 0x00, 0x00, 0x00,
0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x1F, 0x5E, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x0C, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x65, 0x90, 0x00, 0x00, 0x00,
0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x0A, 0x00, 0x00, 0x14, 0x87, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
0x02, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x4C, 0x00, 0x00, 0x00,
];
let mut cursor = Cursor::new(data);
let data = UploadCompetitionData::deserialize(&mut cursor).expect("unable to deserialize data");
let user_data: &UserData = from_bytes(&data.player_data.0[..size_of::<UserData>()]);
let pos = user_data.name.iter()
.position(|v| *v == 0x0000)
.unwrap_or(0x10);
let mut name = user_data.name[0..pos].to_vec();
name.iter_mut().for_each(|v| *v = v.swap_bytes());
let name = String::from_utf16(&name).expect("unable to get name");
println!("{:?}", name);
assert!(u8::deserialize(&mut cursor).is_err())
}
}

View file

@ -0,0 +1,44 @@
use std::io::{Cursor, Read, Write};
use bytemuck::bytes_of;
use v_byte_helpers::{IS_BIG_ENDIAN, ReadExtensions};
use crate::rmc::structures::Error::VersionMismatch;
use crate::rmc::structures::Result;
#[repr(C, packed)]
struct StructureHeader{
version: u8,
length: u32
}
pub fn write_struct(writer: &mut dyn Write, version: u8, pred: impl FnOnce(&mut Vec<u8>) -> Result<()> ) -> Result<()> {
writer.write_all(&[version])?;
let mut scratch_space: Vec<u8> = Vec::new();
(pred)(&mut scratch_space)?;
let u32_size = scratch_space.len() as u32;
writer.write_all(bytes_of(&u32_size))?;
writer.write_all(&scratch_space)?;
Ok(())
}
pub fn read_struct<T: Sized>(mut reader: &mut dyn Read, version: u8, pred: impl FnOnce(&mut Cursor<Vec<u8>>) -> Result<T>) -> Result<T> {
let ver: u8 = reader.read_struct(IS_BIG_ENDIAN)?;
if ver != version{
return Err(VersionMismatch(ver));
}
let size: u32 = reader.read_struct(IS_BIG_ENDIAN)?;
let mut vec = vec![0u8; size as usize];
reader.read_exact(&mut vec)?;
let mut cursor = Cursor::new(vec);
Ok(pred(&mut cursor)?)
}

View file

@ -0,0 +1,39 @@
use std::io::{Read, Write};
use bytemuck::bytes_of;
use log::error;
use v_byte_helpers::{IS_BIG_ENDIAN, ReadExtensions};
use super::{Result, RmcSerialize};
impl RmcSerialize for String{
fn deserialize(mut reader: &mut dyn Read) -> Result<Self> {
let len: u16 = reader.read_struct(IS_BIG_ENDIAN)?;
let mut data = vec![0; len as usize - 1];
reader.read_exact(&mut data)?;
let null: u8 = reader.read_struct(IS_BIG_ENDIAN)?;
if null != 0{
error!("unable to find null terminator... continuing anyways");
}
Ok(String::from_utf8(data)?)
}
fn serialize(&self, writer: &mut dyn Write) -> Result<()> {
(&self[..]).serialize(writer)
}
}
impl RmcSerialize for &str{
fn deserialize(_reader: &mut dyn Read) -> Result<Self> {
panic!("cannot serialize to &str")
}
fn serialize(&self, writer: &mut dyn Write) -> Result<()> {
let u16_len: u16 = (self.len() + 1) as u16;
writer.write(bytes_of(&u16_len))?;
writer.write(self.as_bytes())?;
writer.write(&[0])?;
Ok(())
}
}

View file

@ -0,0 +1,65 @@
use std::io::{Read, Write};
use crate::kerberos::KerberosDateTime;
use crate::rmc::structures;
use crate::rmc::structures::RmcSerialize;
#[derive(Debug, Clone, Default)]
pub enum Variant{
#[default]
None,
SInt64(i64),
Double(f64),
Bool(bool),
String(String),
DateTime(KerberosDateTime),
UInt64(u64),
}
impl RmcSerialize for Variant{
fn serialize(&self, writer: &mut dyn Write) -> crate::rmc::structures::Result<()> {
match self{
Variant::None => {
writer.write_all(&[0])?;
}
Variant::SInt64(v) => {
writer.write_all(&[1])?;
v.serialize(writer)?;
}
Variant::Double(v) => {
writer.write_all(&[2])?;
v.serialize(writer)?;
}
Variant::Bool(v) => {
writer.write_all(&[3])?;
v.serialize(writer)?;
}
Variant::String(v) => {
writer.write_all(&[4])?;
v.serialize(writer)?;
}
Variant::DateTime(v) => {
writer.write_all(&[5])?;
v.serialize(writer)?;
}
Variant::UInt64(v) => {
writer.write_all(&[6])?;
v.serialize(writer)?;
}
}
Ok(())
}
fn deserialize(reader: &mut dyn Read) -> crate::rmc::structures::Result<Self> {
match u8::deserialize(reader)?{
0 => Ok(Variant::None),
1 => Ok(Variant::SInt64(i64::deserialize(reader)?)),
2 => Ok(Variant::Double(f64::deserialize(reader)?)),
3 => Ok(Variant::Bool(bool::deserialize(reader)?)),
4 => Ok(Variant::String(String::deserialize(reader)?)),
5 => Ok(Variant::DateTime(KerberosDateTime::deserialize(reader)?)),
6 => Ok(Variant::UInt64(u64::deserialize(reader)?)),
v => Err(structures::Error::UnexpectedValue(v as u64))
}
}
}

View file

@ -0,0 +1,11 @@
use macros::RmcSerialize;
use crate::prudp::socket_addr::PRUDPSockAddr;
#[derive(Debug, RmcSerialize)]
#[rmc_struct(0)]
pub struct ConnectionInitData{
pub prudpsock_addr: PRUDPSockAddr,
pub pid: u32,
}

115
rnex-core/src/util.rs Normal file
View file

@ -0,0 +1,115 @@
use std::ops::Deref;
use std::sync::Arc;
use log::{error, info};
use tokio::io::{AsyncRead, AsyncWrite, AsyncWriteExt};
use tokio::sync::mpsc::{channel, Receiver, Sender};
use tokio::sync::Notify;
use tokio::task;
use crate::reggie::{UnitPacketRead, UnitPacketWrite};
#[derive(Clone)]
pub struct SendingBufferConnection(Sender<Vec<u8>>, Arc<Notify>);
pub struct SplittableBufferConnection(SendingBufferConnection, Receiver<Vec<u8>>);
impl AsRef<SendingBufferConnection> for SplittableBufferConnection{
fn as_ref(&self) -> &SendingBufferConnection {
&self.0
}
}
impl Deref for SplittableBufferConnection{
type Target = SendingBufferConnection;
fn deref(&self) -> &Self::Target {
self.as_ref()
}
}
impl<T: Send + Unpin + AsyncWrite + AsyncRead + 'static> From<T> for SplittableBufferConnection{
fn from(value: T) -> Self {
Self::new(value)
}
}
impl SplittableBufferConnection {
fn new<T: Send + Unpin + AsyncWrite + AsyncRead + 'static>(stream: T) -> Self {
let (outside_send, inside_recv) = channel::<Vec<u8>>(10);
let (inside_send, outside_recv) = channel::<Vec<u8>>(10);
let notify = Arc::new(Notify::new());
{
let notify = notify.clone();
task::spawn(async move {
let sender = inside_send;
let mut recver = inside_recv;
let mut stream = stream;
loop {
tokio::select! {
data = recver.recv() => {
let Some(data) = data else {
break;
};
if let Err(e) = stream.send_buffer(&data[..]).await{
error!("error sending data to backend: {}", e);
break;
}
},
data = stream.read_buffer() => {
let data = match data{
Ok(d) => d,
Err(e) => {
error!("error reveiving data from backend: {}", e);
break;
}
};
if let Err(e) = sender.send(data).await{
error!("a send error occurred {}", e);
return;
}
},
_ = notify.notified() => {
info!("shutting down connection");
break;
}
}
}
stream.shutdown().await;
});
}
Self(SendingBufferConnection(outside_send, notify), outside_recv)
}
}
impl SendingBufferConnection{
pub async fn send(&self, buffer: Vec<u8>) -> Option<()>{
self.0.send(buffer).await.ok()
}
pub fn is_alive(&self) -> bool{
!self.0.is_closed()
}
pub async fn disconnect(&self) {
while !self.0.is_closed() {
self.1.notify_waiters();
tokio::task::yield_now().await;
}
}
}
impl SplittableBufferConnection{
pub async fn recv(&mut self) -> Option<Vec<u8>>{
self.1.recv().await
}
pub fn duplicate_sender(&self) -> SendingBufferConnection{
self.0.clone()
}
}

97
rnex-core/src/versions.rs Normal file
View file

@ -0,0 +1,97 @@
use std::marker::PhantomData;
use std::ops::{BitAnd, BitOr};
use typenum::{Cmp, IsEqual, IsLess, IsLessOrEqual, Unsigned};
/// This trait represents a version at compile time
trait Version{
type Major: Unsigned;
type Minor: Unsigned;
}
/// This struct contains nothing and is used to represent specific versions as an instance of
/// [`Version`]. It is instances as `Ver<Major, Minor>`
struct Ver<MAJ: Unsigned, MIN: Unsigned>{
_phantom: PhantomData<(MAJ, MIN)>
}
impl<MAJ: Unsigned, MIN: Unsigned> Version for Ver<MAJ, MIN>{
type Major = MAJ;
type Minor = MIN;
}
/// Represents two versions which can be compared
trait ComparableVersion<T: Version>: Version{
type IsAtLeast: SameOrUnit;
}
impl<T: Version, U: Version> ComparableVersion<T> for U where
<T as Version>::Major: Cmp<Self::Major>,
<T as Version>::Minor: IsLessOrEqual<Self::Minor>,
<T as Version>::Major: IsEqual<
Self::Major,
Output: BitAnd<
typenum::LeEq<T::Minor, Self::Minor>
>,
>,
<T as Version>::Major: IsLess<
Self::Major,
Output: BitOr<
typenum::And<
typenum::Eq<T::Major, Self::Major>,
typenum::LeEq<T::Minor, Self::Minor>,
>,
Output: SameOrUnit
>
> {
type IsAtLeast = typenum::Or<
typenum::Le<T::Major, Self::Major>,
typenum::And<
typenum::Eq<T::Major, Self::Major>,
typenum::LeEq<T::Minor, Self::Minor>,
>
>;
}
/// Simple check for testing if the `TEST` version is at least `REQ` or higher.
type VersionAbove<REQ, TEST> = <TEST as ComparableVersion<REQ>>::IsAtLeast;
trait VersionIsAtLeast<VER: Version>{}
impl<VER: Version, T: ComparableVersion<VER, IsAtLeast = typenum::True>> VersionIsAtLeast<VER> for T{}
/// Trait for containing the result of elements which only conditionally exist
trait CondElemResult{
type Output;
}
/// Empty helper struct which only servers to give a concrete type when creating fields in rmc
/// structs which have a version requirement. This is not meant to be used directly, use
/// [`MinVersion`] instead.
struct MinVersionElementHelper<T, REQUIRED: Version, VER: Version + ComparableVersion<REQUIRED>>{
_phantom: PhantomData<(T, REQUIRED, VER)>
}
/// This should be used either with [`typenum::True`] or [`typenum::False`]. When `True` the [`Self::Output`]
/// will be the same as the `T` you put into Output. When `False` it will always be `()`
trait SameOrUnit{
type Output<T>;
}
impl SameOrUnit for typenum::True{
type Output<T> = T;
}
impl SameOrUnit for typenum::False{
type Output<T> = ();
}
impl<T, REQUIRED: Version, VER: Version + ComparableVersion<REQUIRED>> CondElemResult for MinVersionElementHelper<T, REQUIRED, VER> where {
type Output = <<VER as ComparableVersion<REQUIRED>>::IsAtLeast as SameOrUnit>::Output<T>;
}
/// When the version condition is met the field will exist and will simply be `T` if not it will be
/// replaced by `()`. Use this when you need to add versioning to rmc structs.
type MinVersion<T, REQUIRED, VER> = <MinVersionElementHelper<T, REQUIRED, VER> as CondElemResult>::Output;

92
rnex-core/src/web/mod.rs Normal file
View file

@ -0,0 +1,92 @@
use std::sync::Arc;
use async_trait::async_trait;
use rocket::{get, routes, Request, State};
use rocket::request::{FromRequest, Outcome};
use rocket::serde::json::Json;
use tokio::task::JoinHandle;
use crate::nex::matchmake::MatchmakeManager;
use crate::rmc::protocols::notifications::NotificationEvent;
struct RnexApiAuth;
#[async_trait]
impl<'r> FromRequest<'r> for RnexApiAuth{
type Error = ();
async fn from_request<'a>(request: &'r Request<'a>) -> Outcome<Self, Self::Error> {
Outcome::Success(RnexApiAuth)
}
}
#[get("/gatherings")]
async fn gatherings(mmm: &State<Arc<MatchmakeManager>>) -> Json<Vec<u32>>{
let matches = mmm.sessions.read().await;
Json(matches.keys().map(|v| *v).collect())
}
#[get("/gathering/<gid>/players")]
async fn players_in_match(mmm: &State<Arc<MatchmakeManager>>, gid: u32) -> Option<Json<Vec<u32>>>{
let mmm = mmm.sessions.read().await;
let gathering = mmm.get(&gid)?;
let gathering = gathering.clone();
drop(mmm);
let gathering = gathering.lock().await;
Some(Json(gathering.connected_players.iter().filter_map(|p| p.upgrade()).map(|p| p.pid).collect()))
}
/*
#[get("/player/<pid>/disconnect")]
async fn disconnect_player(_auth: RnexApiAuth, mmm: &State<Arc<MatchmakeManager>>, pid: u32) -> Option<()>{
// this doesnt work and is broken, there might be some other way to remotely close gatherings...
// also if anyone gets this working change it to POST cause the only reason its get is because
// that makes testing it easier
let mmm = mmm.users.read().await;
for player in mmm.values().filter_map(|p| p.upgrade()).filter(|p| p.pid == pid) {
player.remote.get_connection().0.close_connection().await;
}
Some(())
}*/
#[get("/gathering/<gid>/close")]
async fn close_gathering(_auth: RnexApiAuth, mmm: &State<Arc<MatchmakeManager>>, gid: u32) -> Option<()>{
// this doesnt work and is broken, there might be some other way to remotely close gatherings...
// also if anyone gets this working change it to POST cause the only reason its get is because
// that makes testing it easier
let mmm = mmm.sessions.read().await;
let gathering = mmm.get(&gid)?;
let gathering = gathering.clone();
drop(mmm);
let gathering = gathering.lock().await;
gathering.broadcast_notification(&NotificationEvent{
pid_source: gathering.session.gathering.owner_pid,
notif_type: 109000,
param_1: gathering.session.gathering.self_gid,
..Default::default()
}).await;
Some(())
}
pub async fn start_web(mgr: Arc<MatchmakeManager>) -> JoinHandle<()> {
tokio::spawn(async move {
rocket::build()
.mount("/", routes![gatherings, players_in_match, close_gathering])
.manage(mgr)
.launch().await
.expect("unable to start webserver");
})
}