progress
Some checks failed
Build and Test / super-mario-maker (push) Failing after 10m30s
Build and Test / fast-racing-neo (push) Failing after 11m40s
Build and Test / friends (push) Failing after 11m56s
Build and Test / wii-sports-club (push) Failing after 12m9s
Build and Test / splatoon (push) Failing after 12m14s
Build and Test / puyopuyo (push) Failing after 12m23s
Build and Test / wii-u-chat (push) Failing after 12m23s
Build and Test / mario-tennis (push) Failing after 12m23s
Build and Test / sonic-transformed (push) Failing after 12m23s
Build and Test / splatoon-testfire (push) Failing after 12m30s
Build and Test / minecraft-wiiu (push) Failing after 12m37s
Some checks failed
Build and Test / super-mario-maker (push) Failing after 10m30s
Build and Test / fast-racing-neo (push) Failing after 11m40s
Build and Test / friends (push) Failing after 11m56s
Build and Test / wii-sports-club (push) Failing after 12m9s
Build and Test / splatoon (push) Failing after 12m14s
Build and Test / puyopuyo (push) Failing after 12m23s
Build and Test / wii-u-chat (push) Failing after 12m23s
Build and Test / mario-tennis (push) Failing after 12m23s
Build and Test / sonic-transformed (push) Failing after 12m23s
Build and Test / splatoon-testfire (push) Failing after 12m30s
Build and Test / minecraft-wiiu (push) Failing after 12m37s
This commit is contained in:
parent
7dd502aa7a
commit
4ff0a5efcc
187 changed files with 4526 additions and 5132 deletions
39
rnex-server-nex-modules/rnex-auth/Cargo.toml
Normal file
39
rnex-server-nex-modules/rnex-auth/Cargo.toml
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
[package]
|
||||
name = "rnex-auth"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
rnex-rmc = { path = "../../rnex-rmc" }
|
||||
rnex-auth-protos = { path = "../../rnex-protocols/auth-protos" }
|
||||
rnex-reggie-protos = { path = "../../rnex-protocols/reggie-protos" }
|
||||
rnex-server = { path = "../../rnex-server" }
|
||||
rnex-prudp = { path = "../../rnex-prudp" }
|
||||
hex = "0.4.3"
|
||||
rand = "0.10.2"
|
||||
nex-account = { version = "0.2.4", registry = "spbr" }
|
||||
tonic = "0.14.6"
|
||||
tracing = "0.1.44"
|
||||
cfg-if = "1.0.4"
|
||||
anyhow = "1.0.103"
|
||||
tokio = { version = "1.52.3", features = ["net"] }
|
||||
|
||||
[features]
|
||||
rmc_struct_header = []
|
||||
guest_login = []
|
||||
friends = ["guest_login", "database-support"]
|
||||
big_pid = []
|
||||
third-notif-param = []
|
||||
v3-3-2 = []
|
||||
v3-4-0 = ["v3-3-2", "third-notif-param", "rmc_struct_header"]
|
||||
v3-5-0 = ["v3-4-0"]
|
||||
v3-8-15 = ["v3-5-0"]
|
||||
v3-10-22 = ["v3-8-15"]
|
||||
v4-3-11 = ["v3-8-15"]
|
||||
nx = ["big_pid"]
|
||||
splatoon = ["v3-5-0"]
|
||||
datastore = ["database-support", "v3-8-15"]
|
||||
database-support = []
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
354
rnex-server-nex-modules/rnex-auth/src/auth_handler.rs
Normal file
354
rnex-server-nex-modules/rnex-auth/src/auth_handler.rs
Normal file
|
|
@ -0,0 +1,354 @@
|
|||
use std::{
|
||||
hash::{DefaultHasher, Hasher},
|
||||
net::SocketAddrV4,
|
||||
sync::{Arc, LazyLock},
|
||||
};
|
||||
|
||||
use cfg_if::cfg_if;
|
||||
use nex_account::{
|
||||
grpc::{self, nex_account_service_client},
|
||||
grpc_client,
|
||||
};
|
||||
use rnex_auth_protos::{
|
||||
LocalAuthProtocol,
|
||||
auth::{Auth, ConnectionData, ConnectionDataOld},
|
||||
};
|
||||
use rnex_prudp::kerberos::{Ticket, TicketInternalData};
|
||||
use rnex_reggie_protos::reggie::{RemoteEdgeNodeHolder, RemoteEdgeNodeManagement};
|
||||
use rnex_rmc::{
|
||||
OnlyRemote,
|
||||
any::Any,
|
||||
define_rmc_proto,
|
||||
qresult::QResult,
|
||||
rand,
|
||||
response::ErrorCode,
|
||||
rmc_struct,
|
||||
util::{PID, account::Account, date_time::DateTime},
|
||||
};
|
||||
use rnex_server::PassthroughInitModule;
|
||||
use tracing::{info, warn};
|
||||
|
||||
use crate::AuthManager;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[rmc_struct(AuthProtocol)]
|
||||
pub struct AuthHandler {
|
||||
pub(crate) am: PassthroughInitModule<AuthManager>,
|
||||
}
|
||||
|
||||
pub fn generate_ticket(
|
||||
source_act_login_data: (PID, [u8; 16]),
|
||||
dest_act_login_data: (PID, [u8; 16]),
|
||||
) -> Box<[u8]> {
|
||||
let source_key = source_act_login_data.1;
|
||||
let dest_key = dest_act_login_data.1;
|
||||
|
||||
let internal_data = TicketInternalData::new(source_act_login_data.0);
|
||||
|
||||
let encrypted_inner = internal_data.encrypt(dest_key);
|
||||
Ticket {
|
||||
pid: dest_act_login_data.0,
|
||||
session_key: internal_data.session_key,
|
||||
}
|
||||
.encrypt(source_key, &encrypted_inner)
|
||||
}
|
||||
pub fn generate_ticket_with_string_user_key(
|
||||
source_act: PID,
|
||||
dest_act_login_data: (PID, [u8; 16]),
|
||||
) -> (String, Box<[u8]>) {
|
||||
let source_key: [u8; 8] = rand::random();
|
||||
let key_string = hex::encode(source_key);
|
||||
let key_data: [u8; 16] = key_string.as_bytes().try_into().unwrap();
|
||||
let dest_key = dest_act_login_data.1;
|
||||
|
||||
let internal_data = TicketInternalData::new(source_act);
|
||||
|
||||
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(key_data, &encrypted_inner);
|
||||
|
||||
(key_string, encrypted_session_ticket)
|
||||
}
|
||||
|
||||
async fn get_login_data_by_pid(pid: PID) -> Option<(PID, [u8; 16])> {
|
||||
if pid == GUEST_ACCOUNT.pid {
|
||||
let source_login_data = GUEST_ACCOUNT.get_login_data();
|
||||
|
||||
return Some((source_login_data.0, source_login_data.1));
|
||||
}
|
||||
|
||||
let Ok(mut client) = nex_account::grpc_client().await else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let Ok(passwd) = client.get_nex_key_by_pid(grpc::Pid { pid }).await else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let passwd = passwd.into_inner().key.try_into().ok()?;
|
||||
|
||||
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()
|
||||
)
|
||||
}
|
||||
|
||||
static GUEST_ACCOUNT: LazyLock<Account> =
|
||||
LazyLock::new(|| Account::new(100, "guest", "MMQea3n!fsik"));
|
||||
|
||||
impl AuthHandler {
|
||||
pub async fn generate_ticket_from_name(
|
||||
&self,
|
||||
name: &str,
|
||||
) -> Result<(PID, Box<[u8]>), ErrorCode> {
|
||||
#[cfg(feature = "guest_login")]
|
||||
{
|
||||
if name == GUEST_ACCOUNT.username {
|
||||
info!("guest account login");
|
||||
let source_login_data = GUEST_ACCOUNT.get_login_data();
|
||||
let destination_login_data = self.am.destination_server_acct.get_login_data();
|
||||
|
||||
return Ok((
|
||||
source_login_data.0,
|
||||
generate_ticket(source_login_data, destination_login_data),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
info!("parsing pid");
|
||||
let Ok(pid) = name.parse() else {
|
||||
warn!("unable to connect to parse pid: {}", name);
|
||||
return Err(ErrorCode::Core_InvalidArgument);
|
||||
};
|
||||
|
||||
info!("creating account grpc client");
|
||||
let Ok(mut client) = grpc_client().await else {
|
||||
warn!("unable to connect to grpc");
|
||||
return Err(ErrorCode::Core_Exception);
|
||||
};
|
||||
|
||||
info!("grabbing nex key");
|
||||
let Ok(passwd) = client.get_nex_key_by_pid(grpc::Pid { pid }).await else {
|
||||
warn!("unable to get nex password for pid: {}:", pid);
|
||||
return Err(ErrorCode::Core_Exception);
|
||||
};
|
||||
|
||||
let passwd = passwd
|
||||
.into_inner()
|
||||
.key
|
||||
.try_into()
|
||||
.map_err(|_| ErrorCode::RendezVous_InvalidPassword)?;
|
||||
info!("source login data");
|
||||
let source_login_data = (pid, passwd);
|
||||
println!("{}, {:?}", pid, passwd);
|
||||
let destination_login_data = self.am.destination_server_acct.get_login_data();
|
||||
|
||||
info!("we are a-ok here");
|
||||
Ok((
|
||||
pid,
|
||||
generate_ticket(source_login_data, destination_login_data),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn generate_ticket_from_name_string_user_key(
|
||||
&self,
|
||||
name: &str,
|
||||
) -> Result<(PID, String, Box<[u8]>), ErrorCode> {
|
||||
{
|
||||
if name == GUEST_ACCOUNT.username {
|
||||
let source_login_data = GUEST_ACCOUNT.get_login_data();
|
||||
let destination_login_data = self.am.destination_server_acct.get_login_data();
|
||||
let ticket = generate_ticket_with_string_user_key(
|
||||
source_login_data.0,
|
||||
destination_login_data,
|
||||
);
|
||||
|
||||
return Ok((source_login_data.0, ticket.0, ticket.1));
|
||||
}
|
||||
}
|
||||
let Ok(pid) = name.parse() else {
|
||||
warn!("unable to connect to parse pid: {}", name);
|
||||
return Err(ErrorCode::Core_InvalidArgument);
|
||||
};
|
||||
let destination_login_data = self.am.destination_server_acct.get_login_data();
|
||||
|
||||
let data = generate_ticket_with_string_user_key(pid, destination_login_data);
|
||||
Ok((pid, data.0, data.1))
|
||||
}
|
||||
}
|
||||
|
||||
impl Auth for AuthHandler {
|
||||
async fn login(
|
||||
&self,
|
||||
name: String,
|
||||
) -> Result<(QResult, PID, Vec<u8>, ConnectionDataOld, String), ErrorCode> {
|
||||
let (pid, ticket) = self.generate_ticket_from_name(&name).await?;
|
||||
|
||||
let result = QResult::success(ErrorCode::Core_Unknown);
|
||||
|
||||
let mut hasher = DefaultHasher::new();
|
||||
|
||||
hasher.write(name.as_bytes());
|
||||
|
||||
let Ok(addr) = self.am.control_server.get_url(hasher.finish()).await else {
|
||||
warn!("no secure proxies");
|
||||
return Err(ErrorCode::Core_Exception);
|
||||
};
|
||||
|
||||
let connection_data = ConnectionDataOld {
|
||||
station_url: station_url_from_sock_addr(addr),
|
||||
special_station_url: "".to_string(),
|
||||
special_protocols: Vec::new(),
|
||||
};
|
||||
|
||||
let ret = (
|
||||
result,
|
||||
pid,
|
||||
ticket.into(),
|
||||
connection_data,
|
||||
self.am.build_name.to_string(),
|
||||
);
|
||||
|
||||
info!("data: {:?}", ret);
|
||||
Ok(ret)
|
||||
}
|
||||
cfg_if! {
|
||||
|
||||
if #[cfg(feature = "nx")]{
|
||||
async fn login_ex(
|
||||
&self,
|
||||
name: String,
|
||||
_extra_data: Any,
|
||||
) -> Result<(QResult, PID, Vec<u8>, ConnectionData, String, String), ErrorCode> {
|
||||
let (pid, key, ticket) = self.generate_ticket_from_name_string_user_key(&name).await?;
|
||||
|
||||
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 {
|
||||
warn!("no secure proxies");
|
||||
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(),
|
||||
};
|
||||
|
||||
let ret = (
|
||||
result,
|
||||
pid,
|
||||
ticket.into(),
|
||||
connection_data,
|
||||
self.build_name.to_string(),
|
||||
key
|
||||
);
|
||||
|
||||
info!("data: {:?}", ret);
|
||||
Ok(ret)
|
||||
}
|
||||
async fn request_ticket(
|
||||
&self,
|
||||
source_pid: PID,
|
||||
destination_pid: PID,
|
||||
) -> Result<(QResult, Vec<u8>, String), ErrorCode> {
|
||||
let Some((pid, _)) = 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 {
|
||||
return Err(ErrorCode::RendezVous_InvalidOperation);
|
||||
};
|
||||
|
||||
let result = QResult::success(Core_Unknown);
|
||||
|
||||
let ticket = generate_ticket_with_string_user_key(pid, desgination_login_data);
|
||||
|
||||
Ok((result, ticket.1.into(), ticket.0))
|
||||
}
|
||||
} else {
|
||||
async fn login_ex(
|
||||
&self,
|
||||
name: String,
|
||||
_extra_data: Any,
|
||||
) -> Result<(QResult, PID, Vec<u8>, ConnectionData, String), ErrorCode> {
|
||||
let (pid, ticket) = self.generate_ticket_from_name(&name).await?;
|
||||
|
||||
let result = QResult::success(ErrorCode::Core_Unknown);
|
||||
|
||||
let mut hasher = DefaultHasher::new();
|
||||
|
||||
hasher.write(name.as_bytes());
|
||||
|
||||
let Ok(addr) = self.am.control_server.get_url(hasher.finish()).await else {
|
||||
warn!("no secure proxies");
|
||||
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: DateTime::now(),
|
||||
special_protocols: Vec::new(),
|
||||
};
|
||||
|
||||
let ret = (
|
||||
result,
|
||||
pid,
|
||||
ticket.into(),
|
||||
connection_data,
|
||||
self.am.build_name.to_string(),
|
||||
);
|
||||
|
||||
info!("data: {:?}", ret);
|
||||
Ok(ret)
|
||||
}
|
||||
async fn request_ticket(
|
||||
&self,
|
||||
source_pid: PID,
|
||||
destination_pid: PID,
|
||||
) -> Result<(QResult, Vec<u8>), ErrorCode> {
|
||||
let Some((pid, passwd)) = get_login_data_by_pid(source_pid).await else {
|
||||
return Err(ErrorCode::Core_Exception);
|
||||
};
|
||||
|
||||
let desgination_login_data = if destination_pid == self.am.destination_server_acct.pid {
|
||||
self.am.destination_server_acct.get_login_data()
|
||||
} else {
|
||||
return Err(ErrorCode::RendezVous_InvalidOperation);
|
||||
};
|
||||
|
||||
let result = QResult::success(ErrorCode::Core_Unknown);
|
||||
|
||||
let ticket = generate_ticket((pid, passwd), 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: PID) -> Result<String, ErrorCode> {
|
||||
Err(ErrorCode::Core_Exception)
|
||||
}
|
||||
}
|
||||
87
rnex-server-nex-modules/rnex-auth/src/lib.rs
Normal file
87
rnex-server-nex-modules/rnex-auth/src/lib.rs
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
#[allow(async_fn_in_trait)]
|
||||
pub mod auth_handler;
|
||||
|
||||
use std::{convert::Infallible, env, error::Error, net::SocketAddr, sync::Arc};
|
||||
use tokio::net::TcpStream;
|
||||
|
||||
use nex_account::{grpc::Pid, grpc_client};
|
||||
use rnex_reggie_protos::reggie::{EdgeNodeHolderConnectOption::DontRegister, RemoteEdgeNodeHolder};
|
||||
use rnex_rmc::{
|
||||
OnlyRemote, new_rmc_gateway_connection,
|
||||
serialization::RmcSerialize,
|
||||
util::{SplittableBufferConnection, account::Account},
|
||||
};
|
||||
use rnex_server::{RnexManager, RnexModule, WeakPassthroughInitModule};
|
||||
use tracing::info;
|
||||
|
||||
use crate::auth_handler::AuthHandler;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct AuthManager {
|
||||
pub destination_server_acct: Account,
|
||||
pub build_name: &'static str,
|
||||
pub control_server: Arc<OnlyRemote<RemoteEdgeNodeHolder>>,
|
||||
}
|
||||
#[derive(Debug)]
|
||||
pub struct AuthModule;
|
||||
|
||||
impl RnexManager for AuthManager {
|
||||
type User = AuthHandler;
|
||||
type InitData = SocketAddr;
|
||||
async fn init_new_user(
|
||||
this: rnex_server::PassthroughInitModule<Self>,
|
||||
_: &rnex_server::ModuleHolder,
|
||||
_: &rnex_rmc::RmcConnection,
|
||||
init_data: &Self::InitData,
|
||||
_: WeakPassthroughInitModule<Self::User>,
|
||||
) -> Self::User {
|
||||
info!(target: "proxy_connections", address = ?init_data, "user connected");
|
||||
Self::User { am: this }
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
pub static FORWARD_EDGE_NODE_HOLDER: Lazy<SocketAddrV4> = Lazy::new(|| {
|
||||
env::var("FORWARD_EDGE_NODE_HOLDER")
|
||||
.ok()
|
||||
.and_then(|s| Some(s.parse().unwrap()))
|
||||
.expect("FORWARD_EDGE_NODE_HOLDER not set")
|
||||
});*/
|
||||
|
||||
impl RnexModule for AuthModule {
|
||||
type Manager = AuthManager;
|
||||
type InitError = anyhow::Error;
|
||||
async fn create_manager(
|
||||
_: &rnex_server::ModuleHolder,
|
||||
) -> Result<Self::Manager, Self::InitError> {
|
||||
let conn = TcpStream::connect(env::var("FORWARD_EDGE_NODE_HOLDER")?)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let conn: SplittableBufferConnection = conn.into();
|
||||
|
||||
conn.send(DontRegister.to_data().unwrap()).await;
|
||||
|
||||
let conn = new_rmc_gateway_connection(conn, async |r| {
|
||||
Arc::new(OnlyRemote::<RemoteEdgeNodeHolder>::new(r))
|
||||
})
|
||||
.await;
|
||||
Ok(AuthManager {
|
||||
build_name: option_env!("AUTH_REPORT_VERSION").unwrap_or("no version specified"),
|
||||
control_server: conn,
|
||||
// todo: update nex-account to allow pulling the entire account info for rnex
|
||||
destination_server_acct: Account::new_raw_key(
|
||||
2,
|
||||
"Quazal Rendez-Vous",
|
||||
grpc_client()
|
||||
.await?
|
||||
.get_nex_key_by_pid(Pid { pid: 2 })
|
||||
.await?
|
||||
.into_inner()
|
||||
.key
|
||||
.try_into()
|
||||
.map_err(|_| anyhow::Error::msg("invalid key size"))?,
|
||||
),
|
||||
})
|
||||
}
|
||||
}
|
||||
17
rnex-server-nex-modules/rnex-base/Cargo.toml
Normal file
17
rnex-server-nex-modules/rnex-base/Cargo.toml
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
[package]
|
||||
name = "rnex-base"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
rnex-rmc = { path = "../../rnex-rmc" }
|
||||
rnex-util = { path = "../../rnex-util" }
|
||||
rnex-base-protos = { path = "../../rnex-protocols/base-protos" }
|
||||
rnex-server = { path = "../../rnex-server" }
|
||||
tokio = { version = "1.52.3", features = ["sync"] }
|
||||
rand = "0.10.2"
|
||||
tracing = "0.1.44"
|
||||
cfg-if = "1.0.4"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
52
rnex-server-nex-modules/rnex-base/src/lib.rs
Normal file
52
rnex-server-nex-modules/rnex-base/src/lib.rs
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
use std::{
|
||||
convert::Infallible,
|
||||
sync::atomic::{AtomicU32, Ordering::Relaxed},
|
||||
};
|
||||
|
||||
use rnex_server::{ConnectionInitData, RnexManager, RnexModule, WeakPassthroughInitModule};
|
||||
|
||||
use crate::user::BaseUser;
|
||||
|
||||
pub mod user;
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct BaseManager {
|
||||
cid_counter: AtomicU32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct BaseModule;
|
||||
|
||||
impl RnexManager for BaseManager {
|
||||
type User = BaseUser;
|
||||
|
||||
type InitData = ConnectionInitData;
|
||||
|
||||
async fn init_new_user(
|
||||
this: rnex_server::PassthroughInitModule<Self>,
|
||||
_mod_holder: &rnex_server::ModuleHolder,
|
||||
_remote: &rnex_rmc::RmcConnection,
|
||||
init_data: &Self::InitData,
|
||||
_: WeakPassthroughInitModule<Self::User>,
|
||||
) -> Self::User {
|
||||
BaseUser {
|
||||
cid: this.cid_counter.fetch_add(1, Relaxed),
|
||||
bm: this,
|
||||
addr: init_data.addr,
|
||||
pid: init_data.pid,
|
||||
station_url: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RnexModule for BaseModule {
|
||||
type Manager = BaseManager;
|
||||
|
||||
type InitError = Infallible;
|
||||
|
||||
async fn create_manager(
|
||||
_mod_holder: &rnex_server::ModuleHolder,
|
||||
) -> Result<Self::Manager, Self::InitError> {
|
||||
Ok(BaseManager::default())
|
||||
}
|
||||
}
|
||||
198
rnex-server-nex-modules/rnex-base/src/user.rs
Normal file
198
rnex-server-nex-modules/rnex-base/src/user.rs
Normal file
|
|
@ -0,0 +1,198 @@
|
|||
use std::net::SocketAddr;
|
||||
|
||||
use rnex_base_protos::{LocalBaseProtocol, secure::Secure, util::Utility};
|
||||
use rnex_rmc::{any::Any, qresult::QResult, response::ErrorCode, rmc_struct};
|
||||
use rnex_server::PassthroughInitModule;
|
||||
use rnex_util::{
|
||||
PID,
|
||||
station_url::{StationUrl, UrlOptions, nat_types::PUBLIC},
|
||||
};
|
||||
use tokio::sync::{Mutex, RwLock};
|
||||
use tracing::info;
|
||||
|
||||
use crate::BaseManager;
|
||||
|
||||
pub async fn get_station_urls(
|
||||
station_urls: &[StationUrl],
|
||||
addr: SocketAddr,
|
||||
pid: PID,
|
||||
cid: u32,
|
||||
) -> Result<Vec<StationUrl>, ErrorCode> {
|
||||
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 UrlOptions::NatType(v) = v
|
||||
&& *v & PUBLIC != 0
|
||||
{
|
||||
return true;
|
||||
}
|
||||
false
|
||||
});
|
||||
|
||||
let Some(nat_filtering) = station.options.iter().find_map(|v| match v {
|
||||
UrlOptions::NatFiltering(v) => Some(v),
|
||||
_ => None,
|
||||
}) else {
|
||||
return Err(ErrorCode::Core_InvalidArgument);
|
||||
};
|
||||
|
||||
let Some(nat_mapping) = station.options.iter().find_map(|v| match v {
|
||||
UrlOptions::NatMapping(v) => Some(v),
|
||||
_ => None,
|
||||
}) else {
|
||||
return Err(ErrorCode::Core_InvalidArgument);
|
||||
};
|
||||
|
||||
if !is_public || (*nat_filtering == 0 && *nat_mapping == 0) {
|
||||
private_station = Some(station.clone());
|
||||
}
|
||||
|
||||
if is_public {
|
||||
public_station = Some(station.clone());
|
||||
}
|
||||
}
|
||||
|
||||
let Some(mut private_station) = private_station else {
|
||||
return Err(ErrorCode::Core_InvalidArgument);
|
||||
};
|
||||
|
||||
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| {
|
||||
!matches!(
|
||||
v,
|
||||
UrlOptions::Address(_)
|
||||
| UrlOptions::Port(_)
|
||||
| UrlOptions::NatFiltering(_)
|
||||
| UrlOptions::NatMapping(_)
|
||||
| UrlOptions::NatType(_)
|
||||
)
|
||||
});
|
||||
|
||||
public_station.options.push(UrlOptions::Address(addr.ip()));
|
||||
public_station.options.push(UrlOptions::Port(addr.port()));
|
||||
public_station.options.push(UrlOptions::NatFiltering(0));
|
||||
public_station.options.push(UrlOptions::NatMapping(0));
|
||||
public_station.options.push(UrlOptions::NatType(3));
|
||||
|
||||
public_station
|
||||
};
|
||||
|
||||
let both = [&mut public_station, &mut private_station];
|
||||
|
||||
for station in both {
|
||||
station.options.retain(|v| {
|
||||
!matches!(
|
||||
v,
|
||||
UrlOptions::PrincipalID(_)
|
||||
| UrlOptions::RVConnectionID(_)
|
||||
| UrlOptions::ConnectionID(_)
|
||||
)
|
||||
});
|
||||
|
||||
station.options.push(UrlOptions::PrincipalID(pid));
|
||||
station.options.push(UrlOptions::RVConnectionID(cid));
|
||||
station.options.push(UrlOptions::ConnectionID(cid));
|
||||
}
|
||||
|
||||
Ok(vec![public_station])
|
||||
}
|
||||
|
||||
#[rmc_struct(BaseProtocol)]
|
||||
#[derive(Debug)]
|
||||
pub struct BaseUser {
|
||||
pub(crate) bm: PassthroughInitModule<BaseManager>,
|
||||
pub addr: SocketAddr,
|
||||
pub station_url: RwLock<Vec<StationUrl>>,
|
||||
pub pid: PID,
|
||||
pub cid: u32,
|
||||
}
|
||||
|
||||
impl Secure for BaseUser {
|
||||
async fn register(
|
||||
&self,
|
||||
station_urls: Vec<StationUrl>,
|
||||
) -> Result<(QResult, u32, StationUrl), ErrorCode> {
|
||||
let cid = self.cid;
|
||||
println!("{:?}", station_urls);
|
||||
|
||||
/*
|
||||
let mut users = self.matchmake_manager.users.write().await;
|
||||
users.insert(cid, self.this.clone());
|
||||
drop(users);
|
||||
let mut users = self.matchmake_manager.users_by_pid.write().await;
|
||||
users.insert(self.pid, self.this.clone());
|
||||
drop(users);
|
||||
*/
|
||||
|
||||
let stations = get_station_urls(&station_urls, self.addr, self.pid, cid).await?;
|
||||
|
||||
let first = stations.first().unwrap().clone();
|
||||
|
||||
let mut lock = self.station_url.write().await;
|
||||
|
||||
*lock = stations;
|
||||
|
||||
drop(lock);
|
||||
|
||||
Ok((QResult::success(ErrorCode::Core_Unknown), self.cid, first))
|
||||
}
|
||||
|
||||
async fn register_ex(
|
||||
&self,
|
||||
station_urls: Vec<StationUrl>,
|
||||
_data: Any,
|
||||
) -> Result<(QResult, u32, StationUrl), ErrorCode> {
|
||||
self.register(station_urls).await
|
||||
}
|
||||
|
||||
async fn replace_url(&self, target_url: StationUrl, dest: StationUrl) -> Result<(), ErrorCode> {
|
||||
let mut lock = self.station_url.write().await;
|
||||
info!("target URL: {:?}", target_url);
|
||||
info!("dest URL: {:?}", dest);
|
||||
|
||||
let Some(target_addr) = target_url
|
||||
.options
|
||||
.iter()
|
||||
.find(|v| matches!(v, UrlOptions::Address(_)))
|
||||
else {
|
||||
return Err(ErrorCode::Core_InvalidArgument);
|
||||
};
|
||||
|
||||
let Some(target_port) = target_url
|
||||
.options
|
||||
.iter()
|
||||
.find(|v| matches!(v, UrlOptions::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 {
|
||||
//probably internal ip
|
||||
return Ok(());
|
||||
};
|
||||
*replacement_target = dest;
|
||||
|
||||
drop(lock);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Utility for BaseUser {
|
||||
async fn acquire_nex_unique_id(&self) -> Result<u64, ErrorCode> {
|
||||
return Ok(rand::random());
|
||||
}
|
||||
|
||||
async fn get_integer_settings(&self, _index: u32) -> Result<Vec<(u16, i32)>, ErrorCode> {
|
||||
Ok(vec![(0, 1), (1, 2), (2, 0), (3, 4)])
|
||||
}
|
||||
}
|
||||
9
rnex-server-nex-modules/rnex-ds/Cargo.toml
Normal file
9
rnex-server-nex-modules/rnex-ds/Cargo.toml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
[package]
|
||||
name = "rnex-ds"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
3
rnex-server-nex-modules/rnex-ds/src/main.rs
Normal file
3
rnex-server-nex-modules/rnex-ds/src/main.rs
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
fn main() {
|
||||
println!("Hello, world!");
|
||||
}
|
||||
9
rnex-server-nex-modules/rnex-fpd/Cargo.toml
Normal file
9
rnex-server-nex-modules/rnex-fpd/Cargo.toml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
[package]
|
||||
name = "rnex-fpd"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
3
rnex-server-nex-modules/rnex-fpd/src/main.rs
Normal file
3
rnex-server-nex-modules/rnex-fpd/src/main.rs
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
fn main() {
|
||||
println!("Hello, world!");
|
||||
}
|
||||
38
rnex-server-nex-modules/rnex-mm/Cargo.toml
Normal file
38
rnex-server-nex-modules/rnex-mm/Cargo.toml
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
[package]
|
||||
name = "rnex-mm"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
rnex-rmc = { path = "../../rnex-rmc" }
|
||||
rnex-util = { path = "../../rnex-util" }
|
||||
rnex-mm-protos = { path = "../../rnex-protocols/mm-protos" }
|
||||
rnex-base-protos = { path = "../../rnex-protocols/base-protos" }
|
||||
rnex-server = { path = "../../rnex-server" }
|
||||
rnex-base = { path = "../rnex-base" }
|
||||
tokio = { version = "1.52.3", features = ["sync"] }
|
||||
rand = "0.10.2"
|
||||
tracing = "0.1.44"
|
||||
cfg-if = "1.0.4"
|
||||
bytemuck = "1.25.1"
|
||||
serde = { version = "1.0.228", features = ["derive"] }
|
||||
ureq = "3.3.0"
|
||||
|
||||
[features]
|
||||
rmc_struct_header = []
|
||||
guest_login = []
|
||||
friends = ["guest_login", "database-support"]
|
||||
big_pid = []
|
||||
third-notif-param = []
|
||||
v3-3-2 = []
|
||||
v3-4-0 = ["v3-3-2", "third-notif-param", "rmc_struct_header"]
|
||||
v3-5-0 = ["v3-4-0"]
|
||||
v3-8-15 = ["v3-5-0"]
|
||||
v3-10-22 = ["v3-8-15"]
|
||||
v4-3-11 = ["v3-8-15"]
|
||||
nx = ["big_pid"]
|
||||
splatoon = ["v3-5-0"]
|
||||
datastore = ["database-support", "v3-8-15"]
|
||||
database-support = []
|
||||
[lints]
|
||||
workspace = true
|
||||
25
rnex-server-nex-modules/rnex-mm/src/lib.rs
Normal file
25
rnex-server-nex-modules/rnex-mm/src/lib.rs
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
use std::{convert::Infallible, sync::atomic::AtomicU32};
|
||||
|
||||
use rnex_server::RnexModule;
|
||||
|
||||
use crate::matchmake::MatchmakeManager;
|
||||
|
||||
pub mod matchmake;
|
||||
pub mod user;
|
||||
|
||||
pub struct MatchMakeModule;
|
||||
impl RnexModule for MatchMakeModule {
|
||||
type Manager = MatchmakeManager;
|
||||
type InitError = Infallible;
|
||||
|
||||
async fn create_manager(
|
||||
_: &rnex_server::ModuleHolder,
|
||||
) -> Result<Self::Manager, Self::InitError> {
|
||||
Ok(MatchmakeManager {
|
||||
sessions: Default::default(),
|
||||
rv_cid_counter: AtomicU32::new(1),
|
||||
users: Default::default(),
|
||||
users_by_pid: Default::default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
619
rnex-server-nex-modules/rnex-mm/src/matchmake.rs
Normal file
619
rnex-server-nex-modules/rnex-mm/src/matchmake.rs
Normal file
|
|
@ -0,0 +1,619 @@
|
|||
use rnex_mm_protos::{RemoteMatchMakingClientProtocol, notifications::RemoteNotification};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
str::FromStr,
|
||||
sync::{
|
||||
Arc, Weak,
|
||||
atomic::{AtomicU32, Ordering},
|
||||
},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use cfg_if::cfg_if;
|
||||
use rand::random;
|
||||
use rnex_mm_protos::{
|
||||
matchmake::{
|
||||
Gathering, MatchmakeSession, MatchmakeSessionSearchCriteria,
|
||||
gathering_flags::PERSISTENT_GATHERING,
|
||||
},
|
||||
notifications::{
|
||||
NotificationEvent,
|
||||
notification_types::{HOST_CHANGED, OWNERSHIP_CHANGED},
|
||||
},
|
||||
};
|
||||
use rnex_rmc::{RmcPureRemoteObject, response::ErrorCode};
|
||||
use rnex_server::{
|
||||
ConnectionInitData, PassthroughInitModule, RnexManager, WeakPassthroughInitModule,
|
||||
};
|
||||
use rnex_util::PID;
|
||||
use tokio::{
|
||||
sync::{Mutex, RwLock},
|
||||
time::sleep,
|
||||
};
|
||||
use tracing::{info, instrument};
|
||||
|
||||
use crate::user::MatchmakeUser;
|
||||
|
||||
#[derive(Debug)]
|
||||
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, WeakPassthroughInitModule<MatchmakeUser>>>,
|
||||
pub users_by_pid: RwLock<HashMap<PID, WeakPassthroughInitModule<MatchmakeUser>>>,
|
||||
}
|
||||
|
||||
impl MatchmakeManager {
|
||||
pub fn next_gid(&self) -> u32 {
|
||||
random()
|
||||
//self.gid_counter.fetch_add(1, Relaxed)
|
||||
}
|
||||
|
||||
pub fn next_cid(&self) -> u32 {
|
||||
self.rv_cid_counter.fetch_add(1, Ordering::Relaxed)
|
||||
}
|
||||
|
||||
#[instrument]
|
||||
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(ErrorCode::RendezVous_SessionVoid);
|
||||
};
|
||||
|
||||
let session = session.clone();
|
||||
drop(sessions);
|
||||
|
||||
Ok(session)
|
||||
}
|
||||
|
||||
#[instrument]
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
#[instrument]
|
||||
pub fn initialize_garbage_collect_thread(this: Weak<Self>) {
|
||||
tokio::spawn(async move {
|
||||
while let Some(this) = this.upgrade() {
|
||||
this.garbage_collect().await;
|
||||
|
||||
// every 5 minutes
|
||||
sleep(Duration::from_secs(60 * 5)).await;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// this could be far more efficient but it is INCREDIBLY difficult to iterate over something
|
||||
// asyncronously propperly
|
||||
#[instrument]
|
||||
pub async fn search_by_criteria(
|
||||
&self,
|
||||
criterias: &[MatchmakeSessionSearchCriteria],
|
||||
) -> Result<Vec<Arc<Mutex<ExtendedMatchmakeSession>>>, ErrorCode> {
|
||||
let sessions = self.sessions.read().await;
|
||||
let mut list = Vec::with_capacity(sessions.len());
|
||||
for session in sessions.values() {
|
||||
let inner_session = session.lock().await;
|
||||
if !inner_session.is_joinable() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut bool_matched_criteria = false;
|
||||
|
||||
for criteria in criterias {
|
||||
if inner_session.matches_criteria(criteria)? {
|
||||
bool_matched_criteria = true;
|
||||
}
|
||||
}
|
||||
|
||||
if bool_matched_criteria {
|
||||
println!("matched session: {:?}", session);
|
||||
list.push(session.clone());
|
||||
}
|
||||
}
|
||||
|
||||
drop(sessions);
|
||||
Ok(list)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct ExtendedMatchmakeSession {
|
||||
pub session: MatchmakeSession,
|
||||
pub connected_players: Vec<WeakPassthroughInitModule<MatchmakeUser>>,
|
||||
}
|
||||
|
||||
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> {
|
||||
if let Some(bounds) = read_bounds_string::<T>(str) {
|
||||
return Some(bounds.0 <= compare && compare <= bounds.1);
|
||||
}
|
||||
if let Ok(val) = T::from_str(str) {
|
||||
return Some(val == compare);
|
||||
}
|
||||
if str.is_empty() {
|
||||
return Some(true);
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub async fn broadcast_notification<T: AsRef<MatchmakeUser>>(
|
||||
players: impl Iterator<Item = 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) -> impl Iterator<Item = PassthroughInitModule<MatchmakeUser>> {
|
||||
self.connected_players.iter().filter_map(|u| u.upgrade())
|
||||
}
|
||||
|
||||
#[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: &WeakPassthroughInitModule<MatchmakeUser>,
|
||||
) -> Self {
|
||||
let Some(host) = host.upgrade() else {
|
||||
return Default::default();
|
||||
};
|
||||
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "v3-5-0")]{
|
||||
let mm_session = MatchmakeSession {
|
||||
gathering: Gathering {
|
||||
self_gid: gid,
|
||||
owner_pid: host.pid,
|
||||
host_pid: host.pid,
|
||||
..session.gathering.clone()
|
||||
},
|
||||
datetime: DateTime::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
|
||||
};
|
||||
|
||||
return Self {
|
||||
session: mm_session,
|
||||
connected_players: Default::default(),
|
||||
}
|
||||
} else {
|
||||
let mm_session = MatchmakeSession {
|
||||
gathering: Gathering {
|
||||
self_gid: gid,
|
||||
owner_pid: host.base.pid,
|
||||
host_pid: host.base.pid,
|
||||
..session.gathering.clone()
|
||||
},
|
||||
session_key: (0..32).map(|_| random()).collect(),
|
||||
..session
|
||||
};
|
||||
return Self {
|
||||
session: mm_session,
|
||||
connected_players: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn add_players(
|
||||
&mut self,
|
||||
conns: &[WeakPassthroughInitModule<MatchmakeUser>],
|
||||
join_msg: String,
|
||||
) {
|
||||
let Some(initiating_user) = conns[0].upgrade() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let initiating_pid = initiating_user.base.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.base.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: 122_000,
|
||||
param_1: self.session.gathering.self_gid as PID,
|
||||
param_2: other_pid,
|
||||
str_param: "".into(),
|
||||
#[cfg(feature = "third-notif-param")]
|
||||
param_3: 0,
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
let list_of_connected_pids: Vec<_> = self
|
||||
.connected_players
|
||||
.iter()
|
||||
.filter_map(|p| p.upgrade())
|
||||
.map(|p| p.base.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 as PID,
|
||||
param_2: *pid,
|
||||
str_param: join_msg.clone(),
|
||||
#[cfg(feature = "third-notif-param")]
|
||||
param_3: self.connected_players.len() as _,
|
||||
})
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
for old_conns in &old_particip {
|
||||
let Some(old_conns) = old_conns.upgrade() else {
|
||||
continue;
|
||||
};
|
||||
/*if old_conns.pid != self.session.gathering.host_pid {
|
||||
continue;
|
||||
}*/
|
||||
for new_conn_pid in conns
|
||||
.iter()
|
||||
.filter_map(WeakPassthroughInitModule::upgrade)
|
||||
.map(|c| c.base.pid)
|
||||
{
|
||||
old_conns
|
||||
.remote
|
||||
.process_notification_event(NotificationEvent {
|
||||
pid_source: initiating_pid,
|
||||
notif_type: 3001,
|
||||
param_1: self.session.gathering.self_gid as PID,
|
||||
param_2: new_conn_pid,
|
||||
str_param: join_msg.clone(),
|
||||
#[cfg(feature = "third-notif-param")]
|
||||
param_3: self.connected_players.len() as _,
|
||||
})
|
||||
.await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn has_min_active_players(&self) -> bool {
|
||||
self.connected_players
|
||||
.iter()
|
||||
.filter(|v| v.upgrade().is_some())
|
||||
.count()
|
||||
>= self.session.gathering.minimum_participants as _
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_host(&self) -> Option<PassthroughInitModule<MatchmakeUser>> {
|
||||
self.get_active_players()
|
||||
.find(|v| v.base.pid == self.session.gathering.host_pid)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_reachable(&self) -> bool {
|
||||
self.get_active_players()
|
||||
.any(|v| v.base.pid == self.session.gathering.host_pid)
|
||||
&& (if self.session.gathering.flags & PERSISTENT_GATHERING != 0 {
|
||||
if self.has_min_active_players() {
|
||||
true
|
||||
} else {
|
||||
self.session.open_participation
|
||||
}
|
||||
} else {
|
||||
self.has_min_active_players()
|
||||
}) & self.has_min_active_players()
|
||||
}
|
||||
#[inline]
|
||||
pub fn is_joinable(&self) -> bool {
|
||||
#[cfg(not(feature = "splatoon"))]
|
||||
let is_open = self.session.open_participation;
|
||||
#[cfg(feature = "splatoon")]
|
||||
let is_open = if self.session.gamemode == 12 {
|
||||
true
|
||||
} else {
|
||||
self.session.open_participation
|
||||
};
|
||||
self.is_reachable() && is_open
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(feature = "v3-5-0")]{
|
||||
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(ErrorCode::Core_InvalidArgument)?
|
||||
{
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
if !check_bounds_str(
|
||||
self.session.gathering.maximum_participants,
|
||||
&search_criteria.maximum_participants,
|
||||
)
|
||||
.ok_or(ErrorCode::Core_InvalidArgument)?
|
||||
{
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
let game_mode: u32 = search_criteria
|
||||
.game_mode
|
||||
.parse()
|
||||
.map_err(|_| ErrorCode::Core_InvalidArgument)?;
|
||||
|
||||
if self.session.gamemode != game_mode {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
let mm_sys_type: u32 = search_criteria
|
||||
.matchmake_system_type
|
||||
.parse()
|
||||
.map_err(|_| ErrorCode::Core_InvalidArgument)?;
|
||||
|
||||
if self.session.matchmake_system_type != mm_sys_type {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
#[cfg(feature = "splatoon")]
|
||||
{
|
||||
if !search_criteria.attribs.get(0).is_some_and(|s| {
|
||||
self.session
|
||||
.attributes
|
||||
.get(0)
|
||||
.is_some_and(|a| s.0.contains(a))
|
||||
}) {
|
||||
return Ok(false);
|
||||
}
|
||||
if !search_criteria.attribs.get(2).is_some_and(|s| {
|
||||
self.session
|
||||
.attributes
|
||||
.get(2)
|
||||
.is_some_and(|a| s.0.contains(a))
|
||||
}) {
|
||||
return Ok(false);
|
||||
}
|
||||
if !search_criteria.attribs.get(3).is_some_and(|s| {
|
||||
self.session
|
||||
.attributes
|
||||
.get(3)
|
||||
.is_some_and(|a| s.0.contains(a))
|
||||
}) {
|
||||
return Ok(false);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
pub async fn migrate_ownership(&mut self, initiator_pid: PID) -> Result<(), ErrorCode> {
|
||||
let players: Vec<_> = self
|
||||
.connected_players
|
||||
.iter()
|
||||
.filter_map(|p| p.upgrade())
|
||||
.collect();
|
||||
|
||||
let Some(new_owner) = players
|
||||
.iter()
|
||||
.find(|p| p.base.pid != self.session.gathering.owner_pid)
|
||||
else {
|
||||
self.session.gathering.owner_pid = 0;
|
||||
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
self.session.gathering.owner_pid = new_owner.base.pid;
|
||||
|
||||
self.broadcast_notification(&NotificationEvent {
|
||||
pid_source: initiator_pid,
|
||||
notif_type: OWNERSHIP_CHANGED,
|
||||
param_1: self.session.gathering.self_gid as PID,
|
||||
param_2: new_owner.base.pid,
|
||||
..Default::default()
|
||||
})
|
||||
.await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn migrate_host(&mut self, initiator_pid: PID) -> 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 as PID,
|
||||
..Default::default()
|
||||
})
|
||||
.await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn remove_player_from_session(
|
||||
&mut self,
|
||||
pid: PID,
|
||||
message: &str,
|
||||
) -> Result<(), ErrorCode> {
|
||||
self.connected_players
|
||||
.retain(|u| u.upgrade().is_some_and(|u| u.base.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 as PID,
|
||||
param_2: pid,
|
||||
str_param: message.to_owned(),
|
||||
..Default::default()
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl RnexManager for MatchmakeManager {
|
||||
type User = MatchmakeUser;
|
||||
|
||||
type InitData = ConnectionInitData;
|
||||
|
||||
async fn init_new_user(
|
||||
this: PassthroughInitModule<Self>,
|
||||
mod_holder: &rnex_server::ModuleHolder,
|
||||
remote: &rnex_rmc::RmcConnection,
|
||||
_: &Self::InitData,
|
||||
user: WeakPassthroughInitModule<Self::User>,
|
||||
) -> Self::User {
|
||||
MatchmakeUser {
|
||||
base: mod_holder
|
||||
.get_ref_init_pt()
|
||||
.expect("matchmaking module cannot work without the base module"),
|
||||
matchmake_manager: this,
|
||||
remote: RemoteMatchMakingClientProtocol::new(remote.clone()),
|
||||
this: user,
|
||||
}
|
||||
}
|
||||
async fn post_init(user: &Self::User) {
|
||||
let mut users = user.matchmake_manager.users.write().await;
|
||||
users.insert(user.base.cid, user.this.clone());
|
||||
drop(users);
|
||||
let mut users = user.matchmake_manager.users_by_pid.write().await;
|
||||
users.insert(user.base.pid, user.this.clone());
|
||||
drop(users);
|
||||
}
|
||||
}
|
||||
719
rnex-server-nex-modules/rnex-mm/src/user.rs
Normal file
719
rnex-server-nex-modules/rnex-mm/src/user.rs
Normal file
|
|
@ -0,0 +1,719 @@
|
|||
use std::{env, sync::Arc};
|
||||
|
||||
use rnex_base::user::BaseUser;
|
||||
use rnex_base_protos::ResultsRange;
|
||||
use rnex_mm_protos::{
|
||||
LocalMatchMakingProtocol, RemoteMatchMakingClientProtocol,
|
||||
matchmake::{
|
||||
AutoMatchmakeParam, CreateMatchmakeSessionParam, Gathering, JoinMatchmakeSessionParam,
|
||||
Matchmake, MatchmakeSession, MatchmakeSessionSearchCriteria,
|
||||
},
|
||||
matchmake_ext::MatchmakeExt,
|
||||
matchmake_extension::MatchmakeExtension,
|
||||
nat_traversal::NatTraversal,
|
||||
nat_traversal::RemoteNatTraversalConsole,
|
||||
notifications::{
|
||||
NotificationEvent, RemoteNotification,
|
||||
notification_types::{END_GATHERING, REQUEST_JOIN_GATHERING},
|
||||
},
|
||||
};
|
||||
use rnex_rmc::{any::Any, response::ErrorCode, rmc_struct};
|
||||
use rnex_server::{PassthroughInitModule, WeakPassthroughInitModule};
|
||||
use rnex_util::{
|
||||
PID,
|
||||
station_url::{StationUrl, UrlOptions},
|
||||
};
|
||||
use tokio::sync::Mutex;
|
||||
use tracing::{error, info};
|
||||
|
||||
use crate::matchmake::{ExtendedMatchmakeSession, MatchmakeManager};
|
||||
|
||||
/*cfg_if! {
|
||||
if #[cfg(feature = "datastore")] {
|
||||
use rnex_core::rmc::protocols::datastore::{DataStore, RawDataStore, RawDataStoreInfo, RemoteDataStore};
|
||||
define_rmc_proto!(
|
||||
proto UserProtocol{
|
||||
Secure,
|
||||
MatchmakeExtension,
|
||||
MatchmakeExt,
|
||||
Matchmake,
|
||||
NatTraversal,
|
||||
Ranking,
|
||||
Utility,
|
||||
DataStore,
|
||||
MessageDelivery
|
||||
}
|
||||
);
|
||||
} else {
|
||||
define_rmc_proto!(
|
||||
proto UserProtocol{
|
||||
Secure,
|
||||
MatchmakeExtension,
|
||||
MatchmakeExt,
|
||||
Matchmake,
|
||||
NatTraversal,
|
||||
Utility,
|
||||
Ranking,
|
||||
MessageDelivery
|
||||
}
|
||||
);
|
||||
}
|
||||
}*/
|
||||
|
||||
/// Connection tickets are allowances to join a specific lobby, they are given out as soon as nat checks pass,
|
||||
/// there are 2 stages of tickets because both sides have to do nat checking before we let the player join
|
||||
/// the lobby
|
||||
pub struct ConnectionTicket {
|
||||
pub cid: u32,
|
||||
pub result: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[rmc_struct(MatchMakingProtocol)]
|
||||
pub struct MatchmakeUser {
|
||||
pub base: PassthroughInitModule<BaseUser>,
|
||||
/*
|
||||
pub pid: PID,
|
||||
pub cid: u32,
|
||||
pub ip: PRUDPSockAddr,
|
||||
*/
|
||||
pub this: WeakPassthroughInitModule<MatchmakeUser>,
|
||||
pub remote: RemoteMatchMakingClientProtocol,
|
||||
//pub station_url: RwLock<Vec<StationUrl>>,
|
||||
pub matchmake_manager: PassthroughInitModule<MatchmakeManager>,
|
||||
}
|
||||
|
||||
impl MatchmakeExtension for MatchmakeUser {
|
||||
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 browse_matchmake_session(
|
||||
&self,
|
||||
browse_criteria: MatchmakeSessionSearchCriteria,
|
||||
result_range: ResultsRange,
|
||||
) -> Result<Vec<Any<Gathering>>, ErrorCode> {
|
||||
let results = self
|
||||
.matchmake_manager
|
||||
.search_by_criteria(&[browse_criteria])
|
||||
.await?;
|
||||
let mm_list = result_range.make_from_list(&results[..]);
|
||||
|
||||
let mut list = Vec::with_capacity(mm_list.len());
|
||||
|
||||
for mm_sess in mm_list {
|
||||
let mm_sess = mm_sess.lock().await;
|
||||
list.push(Any::new(&mm_sess.session).expect("type error"));
|
||||
}
|
||||
|
||||
Ok(list)
|
||||
}
|
||||
|
||||
async fn get_playing_session(&self, _pids: Vec<u32>) -> Result<Vec<()>, ErrorCode> {
|
||||
Ok(Vec::new())
|
||||
}
|
||||
|
||||
#[cfg(feature = "v3-5-0")]
|
||||
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.base.pid)
|
||||
});
|
||||
for player in players {
|
||||
joining_players.push(PassthroughInitModule::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;
|
||||
|
||||
#[cfg(feature = "v3-5-0")]
|
||||
{
|
||||
if join_session_param.user_password != session.session.user_password {
|
||||
return Err(ErrorCode::RendezVous_MatchmakeSessionUserPasswordUnmatch);
|
||||
}
|
||||
}
|
||||
|
||||
session
|
||||
.connected_players
|
||||
.retain(|v| v.upgrade().is_some_and(|v| v.base.pid != self.base.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.base.pid)
|
||||
});
|
||||
for player in players {
|
||||
joining_players.push(PassthroughInitModule::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.base.pid)
|
||||
});
|
||||
for player in players {
|
||||
joining_players.push(PassthroughInitModule::downgrade(&player));
|
||||
}
|
||||
}
|
||||
|
||||
drop(users);
|
||||
|
||||
let sessions = self
|
||||
.matchmake_manager
|
||||
.search_by_criteria(¶m.search_criteria[..])
|
||||
.await?;
|
||||
|
||||
if let Some(session) = sessions.get(0) {
|
||||
let mut session = session.lock().await;
|
||||
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(())
|
||||
}
|
||||
|
||||
async fn create_matchmake_session(
|
||||
&self,
|
||||
gathering: Any<Gathering>,
|
||||
message: String,
|
||||
) -> Result<(u32, Vec<u8>), ErrorCode> {
|
||||
info!("gathering: {:?}", gathering);
|
||||
let session: MatchmakeSession = gathering.try_get_as()?;
|
||||
|
||||
let session = self
|
||||
.create_matchmake_session_with_param(CreateMatchmakeSessionParam {
|
||||
matchmake_session: session,
|
||||
additional_participants: vec![],
|
||||
gid_for_participation_check: 0,
|
||||
create_matchmake_session_option: 0,
|
||||
join_message: message,
|
||||
participation_count: 1,
|
||||
})
|
||||
.await?;
|
||||
|
||||
Ok((session.gathering.self_gid, session.session_key))
|
||||
}
|
||||
|
||||
async fn get_friend_notification_data(
|
||||
&self,
|
||||
_ty: i32,
|
||||
) -> Result<Vec<NotificationEvent>, ErrorCode> {
|
||||
Ok(vec![])
|
||||
}
|
||||
async fn update_notification_data(
|
||||
&self,
|
||||
ty: u32,
|
||||
param_1: u32,
|
||||
param_2: u32,
|
||||
str_param: String,
|
||||
) -> Result<(), ErrorCode> {
|
||||
let recpipent = param_2;
|
||||
let Some(user) = self
|
||||
.matchmake_manager
|
||||
.users_by_pid
|
||||
.read()
|
||||
.await
|
||||
.get(&bytemuck::cast(recpipent))
|
||||
.and_then(|v| v.upgrade())
|
||||
else {
|
||||
return Err(ErrorCode::Core_InvalidArgument);
|
||||
};
|
||||
println!("notif ty : {}", ty);
|
||||
match ty {
|
||||
REQUEST_JOIN_GATHERING => {
|
||||
user.remote
|
||||
.process_notification_event(NotificationEvent {
|
||||
pid_source: self.base.pid,
|
||||
notif_type: REQUEST_JOIN_GATHERING * 1000,
|
||||
param_1: bytemuck::cast(param_1),
|
||||
param_2: bytemuck::cast(param_2),
|
||||
#[cfg(feature = "third-notif-param")]
|
||||
param_3: 0,
|
||||
str_param,
|
||||
})
|
||||
.await;
|
||||
}
|
||||
END_GATHERING => {
|
||||
user.remote
|
||||
.process_notification_event(NotificationEvent {
|
||||
pid_source: self.base.pid,
|
||||
notif_type: END_GATHERING * 1000,
|
||||
param_1: bytemuck::cast(param_1),
|
||||
param_2: bytemuck::cast(param_2),
|
||||
#[cfg(feature = "third-notif-param")]
|
||||
param_3: 0,
|
||||
str_param,
|
||||
})
|
||||
.await;
|
||||
}
|
||||
_ => {
|
||||
return Err(ErrorCode::Core_InvalidArgument);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn update_application_buffer(
|
||||
&self,
|
||||
gid: u32,
|
||||
application_buffer: Vec<u8>,
|
||||
) -> Result<(), ErrorCode> {
|
||||
let session = self.matchmake_manager.get_session(gid).await?;
|
||||
|
||||
let mut session = session.lock().await;
|
||||
|
||||
if session.session.gathering.host_pid == self.base.pid {
|
||||
return Err(ErrorCode::RendezVous_PermissionDenied);
|
||||
}
|
||||
if session.session.gathering.owner_pid == self.base.pid {
|
||||
return Err(ErrorCode::RendezVous_PermissionDenied);
|
||||
}
|
||||
|
||||
session.session.application_buffer = application_buffer;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn join_matchmake_session_ex(
|
||||
&self,
|
||||
gid: u32,
|
||||
message: String,
|
||||
_dont_care_block_list: bool,
|
||||
//participation_count: u16,
|
||||
) -> Result<Vec<u8>, ErrorCode> {
|
||||
let sess = self.matchmake_manager.get_session(gid).await?;
|
||||
let mut sess = sess.lock().await;
|
||||
sess.add_players(&[self.this.clone()], message).await;
|
||||
|
||||
Ok(sess.session.session_key.clone())
|
||||
}
|
||||
|
||||
async fn auto_matchmake_with_search_criteria_postpone(
|
||||
&self,
|
||||
criteria: Vec<MatchmakeSessionSearchCriteria>,
|
||||
gathering: Any<Gathering>,
|
||||
join_message: String,
|
||||
) -> Result<Any<Gathering>, ErrorCode> {
|
||||
let session: MatchmakeSession = gathering.try_get_as()?;
|
||||
|
||||
println!("{:?}", criteria);
|
||||
|
||||
let session = self
|
||||
.auto_matchmake_with_param_postpone(AutoMatchmakeParam {
|
||||
matchmake_session: session,
|
||||
additional_participants: vec![],
|
||||
gid_for_participation_check: 0,
|
||||
auto_matchmake_option: 0,
|
||||
join_message,
|
||||
participation_count: 0,
|
||||
search_criteria: criteria,
|
||||
target_gids: vec![],
|
||||
})
|
||||
.await?;
|
||||
|
||||
let any = Any::new(&session).map_err(|_| ErrorCode::Core_SystemError)?;
|
||||
|
||||
Ok(any)
|
||||
}
|
||||
}
|
||||
|
||||
impl Matchmake for MatchmakeUser {
|
||||
async fn find_by_single_id(&self, gid: u32) -> Result<(bool, Any<Gathering>), ErrorCode> {
|
||||
let s = self.matchmake_manager.get_session(gid).await?;
|
||||
let s = s.lock().await;
|
||||
Ok((
|
||||
true,
|
||||
Any::new(&s.session).map_err(|_| ErrorCode::Custom_Unknown)?,
|
||||
))
|
||||
}
|
||||
|
||||
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.base.pid == session.session.gathering.host_pid)
|
||||
.map(|u| async move { u.base.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.base.pid;
|
||||
|
||||
for player in &session.connected_players {
|
||||
let Some(player) = player.upgrade() else {
|
||||
continue;
|
||||
};
|
||||
|
||||
player
|
||||
.remote
|
||||
.process_notification_event(NotificationEvent {
|
||||
notif_type: 110_000,
|
||||
pid_source: self.base.pid,
|
||||
param_1: gid as PID,
|
||||
param_2: self.base.pid,
|
||||
#[cfg(feature = "third-notif-param")]
|
||||
param_3: 0,
|
||||
str_param: "".to_string(),
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
if change_session_owner {
|
||||
session.session.gathering.owner_pid = self.base.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.base.pid,
|
||||
param_1: gid as PID,
|
||||
param_2: self.base.pid,
|
||||
#[cfg(feature = "third-notif-param")]
|
||||
param_3: 0,
|
||||
str_param: "".to_string(),
|
||||
})
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn migrate_gathering_ownership(
|
||||
&self,
|
||||
gid: u32,
|
||||
candidates: Vec<PID>,
|
||||
_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(ErrorCode::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.base.pid,
|
||||
param_1: gid as PID,
|
||||
param_2: *candidate as PID,
|
||||
#[cfg(feature = "third-notif-param")]
|
||||
param_3: 0,
|
||||
str_param: "".to_string(),
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl MatchmakeExt for MatchmakeUser {
|
||||
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.base.pid, &message)
|
||||
.await?;
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
|
||||
impl NatTraversal for MatchmakeUser {
|
||||
async fn report_nat_properties(
|
||||
&self,
|
||||
nat_mapping: u32,
|
||||
nat_filtering: u32,
|
||||
_rtt: u32,
|
||||
) -> Result<(), ErrorCode> {
|
||||
let mut urls = self.base.station_url.write().await;
|
||||
|
||||
for station_url in urls.iter_mut() {
|
||||
station_url.options.retain(|o| match o {
|
||||
UrlOptions::NatMapping(_) | UrlOptions::NatFiltering(_) => false,
|
||||
_ => true,
|
||||
});
|
||||
|
||||
station_url
|
||||
.options
|
||||
.push(UrlOptions::NatMapping(nat_mapping as u8));
|
||||
station_url
|
||||
.options
|
||||
.push(UrlOptions::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(ErrorCode::RendezVous_AccountExpired)
|
||||
}
|
||||
|
||||
async fn request_probe_initialization_ext(
|
||||
&self,
|
||||
target_list: Vec<StationUrl>,
|
||||
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 url in target_list {
|
||||
let Some(UrlOptions::RVConnectionID(v)) = url
|
||||
.options
|
||||
.into_iter()
|
||||
.find(|o| matches!(o, &UrlOptions::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(())
|
||||
}
|
||||
}
|
||||
9
rnex-server-nex-modules/rnex-msg/Cargo.toml
Normal file
9
rnex-server-nex-modules/rnex-msg/Cargo.toml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
[package]
|
||||
name = "rnex-msg"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
3
rnex-server-nex-modules/rnex-msg/src/main.rs
Normal file
3
rnex-server-nex-modules/rnex-msg/src/main.rs
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
fn main() {
|
||||
println!("Hello, world!");
|
||||
}
|
||||
37
rnex-server-nex-modules/rnex-msg/src/user.rs
Normal file
37
rnex-server-nex-modules/rnex-msg/src/user.rs
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
impl MessageDelivery for User {
|
||||
async fn deliver_message(&self, mut message: Any<UserMessage>) -> Result<(), ErrorCode> {
|
||||
let mut msg = message.get()?;
|
||||
|
||||
let _users = match msg.recipient_type {
|
||||
1 => {
|
||||
let Some(user) = self
|
||||
.matchmake_manager
|
||||
.users_by_pid
|
||||
.read()
|
||||
.await
|
||||
.get(&msg.recipient_id)
|
||||
.map(Weak::upgrade)
|
||||
.flatten()
|
||||
else {
|
||||
return Err(ErrorCode::Core_InvalidArgument);
|
||||
};
|
||||
if msg.flags & 1 != 0 {
|
||||
msg.recipient_id = user.pid;
|
||||
msg.recipient_type = 1;
|
||||
}
|
||||
|
||||
message.emplace_parent(&msg)?;
|
||||
|
||||
user.remote.deliver_message(message).await;
|
||||
}
|
||||
2 => {
|
||||
return Err(ErrorCode::Core_NotImplemented);
|
||||
}
|
||||
_ => {
|
||||
return Err(ErrorCode::Core_InvalidArgument);
|
||||
}
|
||||
};
|
||||
|
||||
Err(ErrorCode::Core_NotImplemented)
|
||||
}
|
||||
}
|
||||
12
rnex-server-nex-modules/rnex-node-holder/Cargo.toml
Normal file
12
rnex-server-nex-modules/rnex-node-holder/Cargo.toml
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
[package]
|
||||
name = "rnex-node-holder"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
rnex-rmc = { path = "../../rnex-rmc" }
|
||||
rnex-reggie-protos = { path = "../../rnex-protocols/reggie-protos" }
|
||||
rnex-server = { path = "../../rnex-server" }
|
||||
tokio = { version = "1.52.3", features = ["sync"] }
|
||||
[lints]
|
||||
workspace = true
|
||||
84
rnex-server-nex-modules/rnex-node-holder/src/lib.rs
Normal file
84
rnex-server-nex-modules/rnex-node-holder/src/lib.rs
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
use std::{
|
||||
convert::Infallible,
|
||||
net::{Ipv4Addr, SocketAddr, SocketAddrV4},
|
||||
sync::{Arc, Weak},
|
||||
};
|
||||
|
||||
use rnex_reggie_protos::reggie::{
|
||||
EdgeNodeHolderConnectOption, EdgeNodeManagement, LocalEdgeNodeHolder, RemoteEdgeNodeHolder,
|
||||
};
|
||||
use rnex_rmc::{response::ErrorCode, rmc_struct, tracing::info};
|
||||
use rnex_server::{
|
||||
ConnectionInitData, PassthroughInitModule, RnexManager, RnexModule, WeakPassthroughInitModule,
|
||||
};
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[rmc_struct(EdgeNodeHolder)]
|
||||
pub struct EdgeNode {
|
||||
em: PassthroughInitModule<NodeHolderManager>,
|
||||
address: SocketAddrV4,
|
||||
}
|
||||
|
||||
impl EdgeNodeManagement for EdgeNode {
|
||||
async fn get_url(&self, seed: u64) -> Result<SocketAddrV4, ErrorCode> {
|
||||
let nodes = self.em.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)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct NodeHolderManager {
|
||||
edge_nodes: RwLock<Vec<Weak<EdgeNode>>>,
|
||||
}
|
||||
#[derive(Default, Debug)]
|
||||
pub struct NodeHolderModule;
|
||||
|
||||
impl RnexManager for NodeHolderManager {
|
||||
type User = Arc<EdgeNode>;
|
||||
type InitData = EdgeNodeHolderConnectOption;
|
||||
async fn init_new_user(
|
||||
this: PassthroughInitModule<Self>,
|
||||
mod_holder: &rnex_server::ModuleHolder,
|
||||
remote: &rnex_rmc::RmcConnection,
|
||||
init_data: &Self::InitData,
|
||||
_: WeakPassthroughInitModule<Self::User>,
|
||||
) -> Self::User {
|
||||
match init_data {
|
||||
EdgeNodeHolderConnectOption::DontRegister => Arc::new(EdgeNode {
|
||||
em: this,
|
||||
address: SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 0),
|
||||
}),
|
||||
EdgeNodeHolderConnectOption::Register(socket_addr_v4) => {
|
||||
let node = Arc::new(EdgeNode {
|
||||
em: this.clone(),
|
||||
address: *socket_addr_v4,
|
||||
});
|
||||
this.edge_nodes.write().await.push(Arc::downgrade(&node));
|
||||
|
||||
node
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RnexModule for NodeHolderModule {
|
||||
type Manager = NodeHolderManager;
|
||||
type InitError = Infallible;
|
||||
|
||||
async fn create_manager(
|
||||
_: &rnex_server::ModuleHolder,
|
||||
) -> Result<Self::Manager, Self::InitError> {
|
||||
Ok(NodeHolderManager::default())
|
||||
}
|
||||
}
|
||||
9
rnex-server-nex-modules/rnex-rk/Cargo.toml
Normal file
9
rnex-server-nex-modules/rnex-rk/Cargo.toml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
[package]
|
||||
name = "rnex-rk"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
3
rnex-server-nex-modules/rnex-rk/src/main.rs
Normal file
3
rnex-server-nex-modules/rnex-rk/src/main.rs
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
fn main() {
|
||||
println!("Hello, world!");
|
||||
}
|
||||
155
rnex-server-nex-modules/rnex-rk/src/user.rs
Normal file
155
rnex-server-nex-modules/rnex-rk/src/user.rs
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
#[derive(Serialize, Deserialize)]
|
||||
pub struct CompetitionPostResults {
|
||||
pub splatfest_id: u32,
|
||||
pub score: u32,
|
||||
pub team_id: u8,
|
||||
pub team_win: u8,
|
||||
pub user: PID,
|
||||
}
|
||||
|
||||
// Seperate function because I cannot give a fuck right now
|
||||
async fn fetch_team_votes(fest_id: u32) -> Result<Vec<u32>, ErrorCode> {
|
||||
let endpoint_votes = env::var("RNEX_SPLATOON_RESULTS_VOTES_GET").map_err(|_| {
|
||||
error!("RNEX_SPLATOON_RESULTS_VOTES_GET not set");
|
||||
ErrorCode::RendezVous_InvalidConfiguration
|
||||
})?;
|
||||
|
||||
let url_votes = format!("{}?splatfest_id={}", endpoint_votes, fest_id);
|
||||
let mut response = tokio::task::spawn_blocking(|| {
|
||||
ureq::get(&url_votes).call().map_err(|e| {
|
||||
error!("GET for votes failed: {:?}", e);
|
||||
ErrorCode::RendezVous_InvalidConfiguration
|
||||
})
|
||||
})
|
||||
.await?;
|
||||
|
||||
let body = response.body_mut().read_to_string().map_err(|e| {
|
||||
error!("failed to read votes body: {:?}", e);
|
||||
ErrorCode::RendezVous_InvalidConfiguration
|
||||
})?;
|
||||
|
||||
let body = body.trim().trim_start_matches('[').trim_end_matches(']');
|
||||
let votes: Result<Vec<u32>, _> = body.split(',').map(|s| u32::from_str(s.trim())).collect();
|
||||
|
||||
votes.map_err(|e| {
|
||||
error!("failed to parse votes: {:?}", e);
|
||||
ErrorCode::RendezVous_InvalidConfiguration
|
||||
})
|
||||
}
|
||||
|
||||
impl Ranking for User {
|
||||
async fn competition_ranking_get_param(
|
||||
&self,
|
||||
param: CompetitionRankingGetParam,
|
||||
) -> Result<Vec<CompetitionRankingScoreInfo>, ErrorCode> {
|
||||
let fest_id = param.festival_ids.get(0).copied().unwrap_or(0);
|
||||
|
||||
let endpoint_results = env::var("RNEX_SPLATOON_RESULTS_GET").map_err(|_| {
|
||||
error!("RNEX_SPLATOON_RESULTS_GET not set");
|
||||
ErrorCode::RendezVous_InvalidConfiguration
|
||||
})?;
|
||||
|
||||
let url_results = format!("{}?splatfest_id={}", endpoint_results, fest_id);
|
||||
let response_results = ureq::get(&url_results).call();
|
||||
|
||||
let results: Vec<CompetitionPostResults> = match response_results {
|
||||
Ok(mut res) => res.body_mut().read_json().map_err(|e| {
|
||||
error!("failed to parse JSON: {:?}", e);
|
||||
ErrorCode::RendezVous_InvalidConfiguration
|
||||
})?,
|
||||
Err(e) => {
|
||||
error!("GET failed: {:?}", e);
|
||||
return Err(ErrorCode::RendezVous_InvalidConfiguration);
|
||||
}
|
||||
};
|
||||
|
||||
let offset = param.range.offset as usize;
|
||||
let size = param.range.size as usize;
|
||||
|
||||
let start = offset.min(results.len());
|
||||
let end = (start + size).min(results.len());
|
||||
|
||||
let team_votes = fetch_team_votes(fest_id)?;
|
||||
let mut wins = vec![0u32, 0u32];
|
||||
for r in &results {
|
||||
let won_team = (r.team_id ^ (!r.team_win)) & 1;
|
||||
if let Some(team) = wins.get_mut(won_team as usize) {
|
||||
*team += 1
|
||||
};
|
||||
}
|
||||
|
||||
let score_data: Vec<CompetitionRankingScoreData> = results[start..end]
|
||||
.iter()
|
||||
.map(|r| CompetitionRankingScoreData {
|
||||
unk: 1,
|
||||
pid: r.user,
|
||||
score: r.score,
|
||||
modified: KerberosDateTime::now(),
|
||||
unk2: 1,
|
||||
appdata: QBuffer(vec![]),
|
||||
})
|
||||
.collect();
|
||||
|
||||
let info = CompetitionRankingScoreInfo {
|
||||
fest_id,
|
||||
score_data,
|
||||
unk: 0,
|
||||
team_wins: wins,
|
||||
team_votes,
|
||||
};
|
||||
|
||||
println!("range: {:?}", param.range);
|
||||
|
||||
Ok(vec![info])
|
||||
}
|
||||
|
||||
async fn upload_competition_ranking_score(
|
||||
&self,
|
||||
param: UploadCompetitionData,
|
||||
) -> Result<bool, ErrorCode> {
|
||||
info!("fest results for user {:?}:", self.pid);
|
||||
info!("fest id: {:?}", param.splatfest_id);
|
||||
info!("score: {:?}", param.score);
|
||||
info!("team id: {:?}", param.team_id);
|
||||
info!("did current team win: {:?}", param.team_win);
|
||||
|
||||
let endpoint = match env::var("RNEX_SPLATOON_RESULTS_POST") {
|
||||
Ok(url) => url,
|
||||
Err(_) => {
|
||||
error!("RNEX_SPLATOON_RESULTS_POST not set");
|
||||
return Ok(false);
|
||||
}
|
||||
};
|
||||
|
||||
let payload = CompetitionPostResults {
|
||||
splatfest_id: param.splatfest_id,
|
||||
score: param.score,
|
||||
team_id: param.team_id,
|
||||
team_win: param.team_win,
|
||||
user: self.pid,
|
||||
};
|
||||
|
||||
let json_body = match serde_json::to_string(&payload) {
|
||||
Ok(j) => j,
|
||||
Err(e) => {
|
||||
error!("error making json_body: {:?}", e);
|
||||
return Ok(false);
|
||||
}
|
||||
};
|
||||
|
||||
let response = ureq::post(&endpoint)
|
||||
.header("Content-Type", "application/json")
|
||||
.send(json_body);
|
||||
|
||||
match response {
|
||||
Ok(res) => {
|
||||
info!("POST worked: {}", res.status());
|
||||
}
|
||||
Err(e) => {
|
||||
error!("POST borked: {:?}", e);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue