Merge branch 'v0' of ssh://ssh.spbr.net:51122/spacebar/rust-nex into v0

This commit is contained in:
Maple Nebel 2026-06-10 23:31:41 +02:00
commit 25bdbb42eb
30 changed files with 764 additions and 279 deletions

View file

@ -15,14 +15,14 @@ COPY --from=planner /app/recipe.json recipe.json
ARG EDITION
ARG DATABASE_URL
RUN --mount=type=cache,target=/usr/local/cargo/registry \
--mount=type=cache,target=/app/target \
RUN --mount=type=cache,id=${EDITION}-registry,target=/usr/local/cargo/registry \
--mount=type=cache,id=${EDITION}-target,target=/app/target \
cargo chef cook --release --recipe-path recipe.json --target x86_64-unknown-linux-musl && \
cargo chef cook --tests --target x86_64-unknown-linux-musl --recipe-path recipe.json
COPY . .
RUN --mount=type=cache,target=/usr/local/cargo/registry \
--mount=type=cache,target=/app/target \
RUN --mount=type=cache,id=${EDITION}-registry,target=/usr/local/cargo/registry \
--mount=type=cache,id=${EDITION}-target,target=/app/target \
RNEX_STATIC=1 ./test-edition.sh && RNEX_STATIC=1 ./build-edition.sh && \
mkdir -p /app/dist && \
cp /app/target/x86_64-unknown-linux-musl/release/edge_node_holder_server /app/dist/ && \

View file

@ -16,7 +16,6 @@ use rnex_core::{
};
use std::{
env::{self, VarError},
error,
net::{AddrParseError, Ipv4Addr, SocketAddr, SocketAddrV4},
ops::Deref,
panic,

View file

@ -3,9 +3,12 @@ use std::io::Write;
use hmac::Mac;
use md5::{Digest, Md5};
use rc4::{KeyInit, Rc4, StreamCipher};
use rnex_core::prudp::{
encryption::{DEFAULT_KEY, EncryptionPair},
types_flags::{TypesFlags, types::DATA},
use rnex_core::{
PID,
prudp::{
encryption::{DEFAULT_KEY, EncryptionPair},
types_flags::{TypesFlags, types::DATA},
},
};
use typenum::U5;
@ -29,7 +32,7 @@ impl CryptoInstance for InsecureInstance {
fn encrypt_outgoing(&mut self, data: &mut [u8]) {
self.pair.send.apply_keystream(data);
}
fn get_user_id(&self) -> u32 {
fn get_user_id(&self) -> PID {
0
}
fn generate_signature(&self, types_flags: TypesFlags, data: &[u8]) -> [u8; 4] {

View file

@ -2,6 +2,7 @@ use hmac::Mac;
use md5::{Digest, Md5};
use rc4::{KeyInit, Rc4, StreamCipher};
use rnex_core::{
PID,
executables::common::SECURE_SERVER_ACCOUNT,
nex::account::Account,
prudp::{
@ -22,7 +23,7 @@ use crate::crypto::{
pub struct SecureInstance {
pair: EncryptionPair<Rc4<U16>>,
uid: u32,
uid: PID,
self_signat: [u8; 4],
#[allow(dead_code)]
remote_signat: [u8; 4],
@ -35,7 +36,7 @@ impl CryptoInstance for SecureInstance {
fn encrypt_outgoing(&mut self, data: &mut [u8]) {
self.pair.send.apply_keystream(data);
}
fn get_user_id(&self) -> u32 {
fn get_user_id(&self) -> PID {
self.uid
}
fn generate_signature(&self, types_flags: TypesFlags, data: &[u8]) -> [u8; 4] {

View file

@ -1,5 +1,5 @@
use cfg_if::cfg_if;
use rnex_core::prudp::types_flags::TypesFlags;
use rnex_core::{PID, prudp::types_flags::TypesFlags};
mod common_crypto;
@ -7,7 +7,7 @@ pub trait CryptoInstance: Send + 'static {
fn decrypt_incoming(&mut self, data: &mut [u8]);
fn encrypt_outgoing(&mut self, data: &mut [u8]);
fn generate_signature(&self, types_flags: TypesFlags, data: &[u8]) -> [u8; 4];
fn get_user_id(&self) -> u32;
fn get_user_id(&self) -> PID;
}
pub trait Crypto: Send + Sync + 'static {

View file

@ -3,6 +3,7 @@ use crate::prudp::socket::{CryptoHandler, CryptoHandlerConnectionInstance};
use hmac::digest::consts::U32;
use rc4::cipher::StreamCipherCoreWrapper;
use rc4::{KeyInit, Rc4, Rc4Core, StreamCipher};
use rnex_core::PID;
use rnex_core::nex::account::Account;
use rnex_core::prudp::encryption::EncryptionPair;
use rnex_core::prudp::ticket::read_secure_connection_data;
@ -49,7 +50,7 @@ pub struct SecureInstance {
self_signature: [u8; 16],
#[allow(dead_code)]
remote_signature: [u8; 16],
pid: u32,
pid: PID,
}
impl CryptoHandler for Secure {
@ -108,7 +109,7 @@ impl CryptoHandlerConnectionInstance for SecureInstance {
}
}
fn get_user_id(&self) -> u32 {
fn get_user_id(&self) -> i32 {
self.pid
}

View file

@ -6,6 +6,7 @@ use async_trait::async_trait;
use log::error;
use log::{info, warn};
use rc4::StreamCipher;
use rnex_core::PID;
use rnex_core::prudp::socket_addr::PRUDPSockAddr;
use rnex_core::prudp::types_flags::TypesFlags;
use rnex_core::prudp::types_flags::flags::{ACK, HAS_SIZE, MULTI_ACK, NEED_ACK, RELIABLE};
@ -31,7 +32,7 @@ use tokio::time::{Instant, sleep};
/// PRUDP Socket for accepting connections to then send and recieve data from those clients
pub struct CommonConnection {
pub user_id: u32,
pub user_id: PID,
pub socket_addr: PRUDPSockAddr,
pub server_port: VirtualPort,
session_id: u8,
@ -851,7 +852,7 @@ pub trait CryptoHandlerConnectionInstance: Send + Sync + 'static {
fn decrypt_incoming(&mut self, substream: u8, data: &mut [u8]);
fn encrypt_outgoing(&mut self, substream: u8, data: &mut [u8]);
fn get_user_id(&self) -> u32;
fn get_user_id(&self) -> i32;
fn sign_connect(&self, packet: &mut PRUDPV1Packet);
fn sign_packet(&self, packet: &mut PRUDPV1Packet);
fn verify_packet(&self, packet: &PRUDPV1Packet) -> bool;

View file

@ -62,7 +62,7 @@ impl CryptoHandlerConnectionInstance for UnsecureInstance {
}
}
fn get_user_id(&self) -> u32 {
fn get_user_id(&self) -> i32 {
0
}

13
rnex-core/build.rs Normal file
View file

@ -0,0 +1,13 @@
use std::{env, process::Command};
fn main() {
let output = Command::new("git")
.args(&["rev-parse", "HEAD"])
.output()
.unwrap();
let git_hash = String::from_utf8(output.stdout).unwrap();
println!("cargo:rustc-env=GIT_HASH={}", git_hash);
println!(
"cargo:rustc-env=FEATURESET={}",
env::var("CARGO_CFG_FEATURE").unwrap()
);
}

View file

@ -5,23 +5,24 @@ use rnex_core::common::setup;
async fn main() {
setup();
#[cfg(feature = "database-support")]
{
use rnex_core::executables::common::DB_POOL;
use sqlx::PgPool;
let database_url = std::env::var("RNEX_DATASTORE_DATABASE_URL")
.expect("RNEX_DATASTORE_DATABASE_URL must be set");
let pool = PgPool::connect(&database_url)
.await
.expect("Failed to create pool");
DB_POOL.set(pool).expect("failed to set global DB_POOL");
}
cfg_if! {
if #[cfg(feature = "friends")]{
use rnex_core::executables::friends_backend::start_friends_backend;
start_friends_backend().await;
} else if #[cfg(feature = "datastore")] {
use rnex_core::executables::common::DB_POOL;
use sqlx::PgPool;
let database_url = std::env::var("RNEX_DATASTORE_DATABASE_URL")
.expect("RNEX_DATASTORE_DATABASE_URL must be set");
let pool = PgPool::connect(&database_url)
.await
.expect("Failed to create pool");
DB_POOL.set(pool).expect("failed to set global DB_POOL");
use rnex_core::executables::regular_backend;
regular_backend::start_regular_backend().await
} else {
use rnex_core::executables::regular_backend;
regular_backend::start_regular_backend().await

View file

@ -1,23 +1,18 @@
use crate::reggie::UnitPacketRead;
use cfg_if::cfg_if;
use log::error;
use once_cell::sync::Lazy;
use rnex_core::nex::account::Account;
use rnex_core::rmc::protocols::{RmcCallable, RmcConnection, new_rmc_gateway_connection};
use rnex_core::rmc::structures::RmcSerialize;
use rnex_core::rnex_proxy_common::ConnectionInitData;
use std::env;
use std::error::Error;
use std::fmt::Display;
use std::io::{Cursor, Read, Write};
use std::net::{Ipv4Addr, SocketAddrV4, TcpStream};
use std::sync::Arc;
use tokio::net::TcpListener;
cfg_if! {
if #[cfg(feature = "datastore")] {
use sqlx::postgres::PgPool;
}
}
use crate::reggie::UnitPacketRead;
use cfg_if::cfg_if;
use log::error;
use std::error::Error;
const IP_REQ_SERVICE_URLS: &[(&str, &str, &str)] = &[
("ipinfo.io:80", "ipinfo.io", "/ip"),
@ -30,11 +25,12 @@ const IP_REQ_SERVICE_URLS: &[(&str, &str, &str)] = &[
];
cfg_if! {
if #[cfg(feature = "datastore")] {
if #[cfg(feature = "database-support")] {
use std::sync::{LazyLock, OnceLock};
pub static RNEX_DATASTORE_DATABASE_URL: LazyLock<String> = LazyLock::new(|| {
std::env::var("RNEX_DATASTORE_DATABASE_URL")
.expect("RNEX_DATASTORE_DATABASE_URL must be set")
use sqlx::postgres::PgPool;
pub static RNEX_DATABASE_URL: LazyLock<String> = LazyLock::new(|| {
std::env::var("RNEX_DATABASE_URL")
.expect("RNEX_DATABASE_URL must be set")
});
pub static DB_POOL: OnceLock<PgPool> = OnceLock::new();
@ -42,6 +38,10 @@ cfg_if! {
pub fn get_db() -> &'static PgPool {
DB_POOL.get().expect("db_pool not initialized")
}
}
}
cfg_if! {
if #[cfg(feature = "datastore")]{
pub static RNEX_DATASTORE_S3_ENDPOINT: LazyLock<String> = LazyLock::new(|| {
std::env::var("RNEX_DATASTORE_S3_ENDPOINT")
.expect("RNEX_DATASTORE_S3_ENDPOINT must be set")

View file

@ -166,7 +166,7 @@ impl Client {
.find(|v| v.0 == "pid")
.ok_or(SomethingHappened)?
.1
.as_u32()
.as_i32()
else {
return Err(SomethingHappened);
};

View file

@ -5,9 +5,9 @@
//#![warn(missing_docs)]
#[cfg(feature = "big_pid")]
pub type PID = u64;
pub type PID = i64;
#[cfg(not(feature = "big_pid"))]
pub type PID = u32;
pub type PID = i32;
extern crate self as rnex_core;

View file

@ -10,7 +10,9 @@ use macros::rmc_struct;
use rnex_core::rmc::protocols::account_management::{
AccountManagement, RawAccountManagement, RawAccountManagementInfo, RemoteAccountManagement,
};
use rnex_core::rmc::protocols::friends::{Friends, RawFriends, RawFriendsInfo, RemoteFriends};
use rnex_core::rmc::protocols::friends_wiiu::{
FriendsWiiU, RawFriendsWiiU, RawFriendsWiiUInfo, RemoteFriendsWiiU,
};
use rnex_core::rmc::protocols::nintendo_notification::{
NintendoNotification, RawNintendoNotification, RawNintendoNotificationInfo,
RemoteNintendoNotification,
@ -22,19 +24,20 @@ use rnex_core::{
nex::common::get_station_urls,
prudp::{socket_addr::PRUDPSockAddr, station_url::StationUrl},
rmc::{
protocols::friends::{
protocols::friends_wiiu::{
BlacklistedPrincipal, Comment, FriendInfo, FriendRequest, NNAInfo, NintendoPresenceV2,
PersistentNotification, PrincipalPreference,
PersistentNotification, PrincipalPreference, PrincipalRequestBlockSetting,
},
response::ErrorCode,
structures::{any::Any, qresult::QResult},
},
};
use sqlx::query;
use std::sync::atomic::Ordering::Relaxed;
use tokio::spawn;
use tokio::sync::RwLock;
use rnex_core::rmc::protocols::friends::{GameKey, MiiV2, PrincipalBasicInfo};
use rnex_core::rmc::protocols::friends_wiiu::{GameKey, MiiV2, PrincipalBasicInfo};
use rnex_core::PID;
@ -44,10 +47,12 @@ use rnex_core::rmc::structures::RmcSerialize;
use rnex_core::rmc::structures::data::Data;
use crate::executables::common::get_db;
define_rmc_proto!(
proto FriendsUser{
Secure,
Friends
FriendsWiiU
}
);
define_rmc_proto!(
@ -95,29 +100,12 @@ impl FriendsManager {
}
}
pub fn friend_info_from_user(data: &UserData) -> FriendInfo {
FriendInfo {
data: Data {},
nna_info: data.info.clone(),
presence: data.presence.clone(),
comment: Comment {
data: Data {},
unk: 0,
message: "haii =w=".to_string(),
last_changed: KerberosDateTime::now(),
},
became_friends: KerberosDateTime::now(),
last_online: KerberosDateTime::now(),
unk: 0,
}
}
impl Friends for FriendsUser {
impl FriendsWiiU for FriendsUser {
async fn update_and_get_all_information(
&self,
info: NNAInfo,
presence: NintendoPresenceV2,
_date_time: KerberosDateTime,
date_time: KerberosDateTime,
) -> Result<
(
PrincipalPreference,
@ -132,213 +120,105 @@ impl Friends for FriendsUser {
),
ErrorCode,
> {
println!("updating own data");
let mut data = self.data.write().await;
*data = Some(UserData { info, presence });
let self_fr_info = friend_info_from_user(data.as_ref().unwrap());
let Ok(any_self_fr_info) = Any::new(&self_fr_info) else {
return Err(ErrorCode::RendezVous_ControlScriptFailure);
};
let Ok(any_self_presence) = Any::new(&self_fr_info.presence) else {
return Err(ErrorCode::RendezVous_ControlScriptFailure);
};
drop(data);
// let query = query!("select ", self.pid).fetch_all(get_db()).await;
Err(ErrorCode::Core_NotImplemented)
}
let mut fr_list = vec![FriendInfo {
data: Data{},
became_friends: KerberosDateTime::now(),
comment: Comment {
data: Data{},
last_changed: KerberosDateTime::now(),
message: "I'm just a dummy account :3".to_string(),
unk: 0,
},
last_online: KerberosDateTime::now(),
nna_info: NNAInfo {
data: Data{},
principal_basic_info: PrincipalBasicInfo {
data: Data{},
pid: 101,
nnid: "dummy:3".to_string(),
mii: MiiV2{
data: Data{},
date_time: KerberosDateTime::now(),
name: "TheDummy".to_string(),
mii_data: hex::decode("030000402bd7c32986a771f2dc6b35e31da15e37ff7c0000391e6f006f006d0069000000000000000000000000004040001065033568641e2013661a611821640f0000290052485000000000000000000000000000000000000000000000e838").unwrap(),
unk: 0,
unk2: 0,
},
unk: 0
},
unk: 0,
unk2: 0
},
presence: NintendoPresenceV2{
data: Data{},
changed_flags: 0,
message: "".to_string(),
app_data: vec![],
game_key: GameKey{
data: Data{},
tid: 0x00050002101ce400,
version: 0x0
},
game_server_id: 0,
is_online: true,
gid: 0,
pid: 101,
unk: 0,
unk2: 0,
unk3: 0,
unk4: 0,
unk5: 0,
unk6: 0,
unk7: 0
},
unk: 0
}];
async fn add_friend(&self, friend: PID) -> Result<(FriendRequest, FriendInfo), ErrorCode> {
todo!()
}
println!("acquiring user and current friends locks");
let users = self.fm.users.read().await;
if users.iter().filter(|u| u.upgrade().is_some()).count() >= 100 {
return Err(ErrorCode::RendezVous_ConnectionFailure);
}
println!("started summing users");
for u in users.deref().iter().filter_map(|u| u.upgrade()) {
let data = u.data.read().await;
let Some(inner_data) = data.as_ref() else {
continue;
};
fr_list.push(friend_info_from_user(&inner_data));
drop(data);
async fn add_friend_by_name(
&self,
name: String,
) -> Result<(FriendRequest, FriendInfo), ErrorCode> {
todo!()
}
let mut curr_friends = self.current_friends.write().await;
curr_friends.push(u.pid);
drop(curr_friends);
async fn remove_friend(&self, friend: PID) -> Result<(), ErrorCode> {
todo!()
}
let mut fr = u.current_friends.write().await;
if !fr.contains(&self.pid) {
fr.push(self.pid);
drop(fr);
let data = any_self_fr_info.clone();
let u = u.clone();
let sender = self.pid;
spawn(async move {
u.remote
.process_nintendo_notification_event_1(NintendoNotificationEvent {
event_type: 30,
sender,
data,
})
.await;
});
} else {
let data = any_self_presence.clone();
let u = u.clone();
let sender = self.pid;
spawn(async move {
u.remote
.process_nintendo_notification_event_2(NintendoNotificationEvent {
event_type: 24,
sender,
data,
})
.await;
});
drop(fr);
}
}
println!("finished summing users");
drop(users);
async fn add_friend_request(
&self,
friend: PID,
unk1: u8,
message: String,
unk2: u8,
unk3: String,
game_key: GameKey,
unk4: KerberosDateTime,
) -> Result<(FriendRequest, FriendInfo), ErrorCode> {
todo!()
}
println!("adding self to users");
let mut users = self.fm.users.write().await;
users.push(self.this.clone());
drop(users);
async fn cancel_friend_request(&self, id: u64) -> Result<(), ErrorCode> {
todo!()
}
println!("done...");
Ok((
PrincipalPreference {
data: Data {},
block_friend_request: false,
show_online: false,
show_playing_title: false,
},
Comment {
data: Data {},
last_changed: KerberosDateTime::now(),
message: "".to_string(),
unk: 0,
},
fr_list,
vec![],
vec![],
vec![],
false,
vec![],
false,
))
async fn accept_friend_request(&self, id: u64) -> Result<FriendInfo, ErrorCode> {
todo!()
}
async fn delete_friend_request(&self, id: u64) -> Result<(), ErrorCode> {
todo!()
}
async fn deny_friend_request(&self, id: u64) -> Result<BlacklistedPrincipal, ErrorCode> {
todo!()
}
async fn mark_friend_requests_as_received(&self, ids: Vec<u64>) -> Result<(), ErrorCode> {
todo!()
}
async fn add_blacklist(
&self,
principal: BlacklistedPrincipal,
) -> Result<BlacklistedPrincipal, ErrorCode> {
todo!()
}
async fn remove_blacklist(&self, id: PID) -> Result<(), ErrorCode> {
todo!()
}
async fn update_presence(&self, presence: NintendoPresenceV2) -> Result<(), ErrorCode> {
info!("user updated presence: {:?}", presence);
let mut data = self.data.write().await;
let Some(inner_data) = data.as_mut() else {
log::error!("unable to get presence data");
return Err(ErrorCode::RendezVous_PermissionDenied);
};
inner_data.presence = presence;
let Ok(any_self_fr_info) = Any::new(&inner_data.presence) else {
log::error!("unable to create presence any data holder");
return Err(ErrorCode::RendezVous_ControlScriptFailure);
};
drop(data);
todo!()
}
let users = self.fm.users.read().await;
for u in users.deref().iter().filter_map(|u| u.upgrade()) {
info!("sending presence update");
u.remote
.process_nintendo_notification_event_2(NintendoNotificationEvent {
event_type: 24,
sender: self.pid,
data: any_self_fr_info.clone(),
})
.await;
}
drop(users);
async fn update_mii(&self, presence: MiiV2) -> Result<KerberosDateTime, ErrorCode> {
todo!()
}
Ok(())
async fn update_comment(&self, presence: Comment) -> Result<KerberosDateTime, ErrorCode> {
todo!()
}
async fn update_preference(&self, preference: PrincipalPreference) -> Result<(), ErrorCode> {
todo!()
}
async fn get_basic_info(&self, pids: Vec<PID>) -> Result<Vec<PrincipalBasicInfo>, ErrorCode> {
todo!()
}
async fn delete_persistent_notification(
&self,
_notifs: Vec<PersistentNotification>,
notifs: Vec<PersistentNotification>,
) -> Result<(), ErrorCode> {
Ok(())
todo!()
}
async fn check_setting_status(&self) -> Result<u8, ErrorCode> {
Ok(0xFF)
todo!()
}
async fn update_preference(&self, preference: PrincipalPreference) -> Result<(),ErrorCode> {
info!("user updated preference: {:?}", preference);
let any_presence: Any = Any::new(&preference).expect("out of memory");
let users = self.fm.users.read().await;
for u in users.deref().iter().filter_map(|u| u.upgrade()) {
info!("sending preference update");
u.remote
.process_nintendo_notification_event_2(NintendoNotificationEvent {
event_type: 23,
sender: self.pid,
data: any_presence.clone(),
})
.await;
}
drop(users);
Ok(())
async fn get_request_block_settings(
&self,
unk: Vec<u32>,
) -> Result<Vec<PrincipalRequestBlockSetting>, ErrorCode> {
todo!()
}
}

View file

@ -1,7 +1,6 @@
use log::info;
use rand::random;
use rnex_core::PID;
use rnex_core::kerberos::KerberosDateTime;
use rnex_core::nex::user::User;
use rnex_core::rmc::protocols::notifications::notification_types::{
HOST_CHANGED, OWNERSHIP_CHANGED,
@ -11,9 +10,8 @@ use rnex_core::rmc::response::ErrorCode;
use rnex_core::rmc::response::ErrorCode::{Core_InvalidArgument, RendezVous_SessionVoid};
use rnex_core::rmc::structures::matchmake::gathering_flags::PERSISTENT_GATHERING;
use rnex_core::rmc::structures::matchmake::{
Gathering, MatchmakeParam, MatchmakeSession, MatchmakeSessionSearchCriteria,
Gathering, MatchmakeSession, MatchmakeSessionSearchCriteria,
};
use rnex_core::rmc::structures::variant::Variant;
use std::collections::HashMap;
use std::str::FromStr;
use std::sync::atomic::AtomicU32;
@ -28,7 +26,7 @@ pub struct MatchmakeManager {
pub sessions: RwLock<HashMap<u32, Arc<Mutex<ExtendedMatchmakeSession>>>>,
pub rv_cid_counter: AtomicU32,
pub users: RwLock<HashMap<u32, Weak<User>>>,
pub users_by_pid: RwLock<HashMap<u32, Weak<User>>>,
pub users_by_pid: RwLock<HashMap<PID, Weak<User>>>,
}
impl MatchmakeManager {
@ -162,6 +160,13 @@ impl ExtendedMatchmakeSession {
cfg_if::cfg_if! {
if #[cfg(feature = "v3-5-0")]{
use rnex_core::{
rmc::structures::{
variant::Variant,
matchmake::MatchmakeParam
},
kerberos::KerberosDateTime
};
let mm_session = MatchmakeSession {
gathering: Gathering {
self_gid: gid,

View file

@ -37,7 +37,7 @@ use std::env;
use std::str::FromStr;
use cfg_if::cfg_if;
use log::{error, info, warn};
use log::{error, info};
use macros::rmc_struct;
use rnex_core::prudp::socket_addr::PRUDPSockAddr;
use rnex_core::rmc::protocols::notifications::{NotificationEvent, RemoteNotification};
@ -456,7 +456,7 @@ impl MatchmakeExtension for User {
.users_by_pid
.read()
.await
.get(&recpipent)
.get(&bytemuck::cast(recpipent))
.and_then(|v| v.upgrade())
else {
return Err(ErrorCode::Core_InvalidArgument);
@ -468,8 +468,8 @@ impl MatchmakeExtension for User {
.process_notification_event(NotificationEvent {
pid_source: self.pid,
notif_type: REQUEST_JOIN_GATHERING * 1000,
param_1,
param_2,
param_1: bytemuck::cast(param_1),
param_2: bytemuck::cast(param_2),
#[cfg(feature = "third-notif-param")]
param_3: 0,
str_param,
@ -481,8 +481,8 @@ impl MatchmakeExtension for User {
.process_notification_event(NotificationEvent {
pid_source: self.pid,
notif_type: END_GATHERING * 1000,
param_1,
param_2,
param_1: bytemuck::cast(param_1),
param_2: bytemuck::cast(param_2),
#[cfg(feature = "third-notif-param")]
param_3: 0,
str_param,

View file

@ -5,7 +5,7 @@ use rnex_core::{
rmc::{response::ErrorCode, structures::any::Any},
};
use crate::{kerberos::KerberosDateTime, rmc::protocols::friends::NNAInfo};
use crate::{kerberos::KerberosDateTime, rmc::protocols::friends_wiiu::NNAInfo};
#[derive(RmcSerialize, Debug, Clone)]
#[rmc_struct(0)]

View file

@ -0,0 +1,250 @@
use macros::{RmcSerialize, method_id, rmc_proto};
use rnex_core::rmc::{response::ErrorCode, structures::data::Data};
use crate::{PID, kerberos::KerberosDateTime};
#[derive(RmcSerialize)]
#[rmc_struct(0)]
pub struct MyProfile {
#[extends]
pub data: Data,
pub region: u8,
pub country: u8,
pub area: u8,
pub language: u8,
pub platform: u8,
pub local_friend_code_seed: u64,
pub mac_address: String,
pub serial_number: String,
}
#[derive(RmcSerialize)]
#[rmc_struct(0)]
pub struct Mii {
#[extends]
pub data: Data,
pub unk1: String,
pub unk2: bool,
pub unk3: u8,
pub mii_data: Vec<u8>,
}
#[derive(RmcSerialize)]
#[rmc_struct(0)]
pub struct MiiList {
#[extends]
pub data: Data,
pub unk1: String,
pub unk2: bool,
pub unk3: u8,
pub mii_data: Vec<Vec<u8>>,
}
#[derive(RmcSerialize)]
#[rmc_struct(0)]
pub struct GameKey {
#[extends]
pub data: Data,
pub title_id: u64,
pub version: u16,
}
#[derive(RmcSerialize)]
#[rmc_struct(0)]
pub struct PlayedGame {
#[extends]
pub data: Data,
pub game_key: GameKey,
pub date_time: KerberosDateTime,
}
#[derive(RmcSerialize)]
#[rmc_struct(0)]
pub struct FriendInfo {
pub unk1: u32,
pub unk2: KerberosDateTime,
}
#[derive(RmcSerialize)]
#[rmc_struct(0)]
pub struct FriendMii {
#[extends]
pub data: Data,
pub pid: PID,
pub mii: Mii,
pub modified_at: KerberosDateTime,
}
#[derive(RmcSerialize)]
#[rmc_struct(0)]
pub struct FriendMiiList {
#[extends]
pub data: Data,
pub unk1: u32,
pub mii_list: MiiList,
pub unk2: KerberosDateTime,
}
#[derive(RmcSerialize)]
#[rmc_struct(0)]
pub struct FriendRelationship {
#[extends]
pub data: Data,
pub unk1: u32,
pub unk2: u64,
pub unk3: u8,
}
#[derive(RmcSerialize)]
#[rmc_struct(0)]
pub struct NintendoPresence {
#[extends]
pub data: Data,
pub changed_bit_flag: u32,
pub game_key: GameKey,
pub game_mode_desctiption: String,
pub join_availibility_flag: u32,
pub mm_system_type: u8,
pub join_game_id: u32,
pub join_game_mode: u32,
pub owner_pid: PID,
pub join_group_id: u32,
pub application_arg: Vec<u8>,
}
#[derive(RmcSerialize)]
#[rmc_struct(0)]
pub struct FriendPresence {
#[extends]
pub data: Data,
pub unk: u32,
pub presence: NintendoPresence,
}
#[derive(RmcSerialize)]
#[rmc_struct(0)]
pub struct FriendComment {
#[extends]
pub data: Data,
pub pid: PID,
pub comment: String,
pub modified_at: KerberosDateTime,
}
#[derive(RmcSerialize)]
#[rmc_struct(0)]
pub struct FriendPicture {
#[extends]
pub data: Data,
pub unk1: u32,
pub pic_data: Vec<u32>,
pub date_time: KerberosDateTime,
}
#[derive(RmcSerialize)]
#[rmc_struct(0)]
pub struct FriendPersistentInfo {
#[extends]
pub data: Data,
pub pid: PID,
pub region: u8,
pub country: u8,
pub area: u8,
pub language: u8,
pub platform: u8,
pub game_key: GameKey,
pub message: String,
pub msg_updated_at: KerberosDateTime,
pub friended_at: KerberosDateTime,
pub last_online: KerberosDateTime,
}
#[rmc_proto(101)]
pub trait Friends3DS {
#[method_id(1)]
async fn update_profile(&self, profile: MyProfile) -> Result<(), ErrorCode>;
#[method_id(2)]
async fn update_mii(&self, profile: Mii) -> Result<(), ErrorCode>;
#[method_id(3)]
async fn update_mii_list(&self, profile: MiiList) -> Result<(), ErrorCode>;
#[method_id(4)]
async fn update_played_games(&self, profile: Vec<PlayedGame>) -> Result<(), ErrorCode>;
#[method_id(5)]
async fn update_preference(
&self,
show_online_status: bool,
show_current_title: bool,
block_friend_requests: bool,
) -> Result<(), ErrorCode>;
#[method_id(6)]
async fn get_friend_mii(&self, friends: Vec<FriendInfo>) -> Result<Vec<FriendMii>, ErrorCode>;
#[method_id(7)]
async fn get_friend_mii_list(
&self,
friends: Vec<FriendInfo>,
) -> Result<Vec<FriendMiiList>, ErrorCode>;
#[method_id(8)]
async fn is_active_game(&self, unk: Vec<u32>, game_key: GameKey)
-> Result<Vec<u32>, ErrorCode>;
#[method_id(9)]
async fn get_principal_id_by_local_friend_code(
&self,
unk1: u64,
unk2: Vec<u64>,
) -> Result<Vec<FriendRelationship>, ErrorCode>;
#[method_id(10)]
async fn get_friend_relationships(
&self,
unk2: Vec<u32>,
) -> Result<Vec<FriendRelationship>, ErrorCode>;
#[method_id(11)]
async fn add_friend_by_pid(&self, unk: u64, pid: PID) -> Result<FriendRelationship, ErrorCode>;
#[method_id(12)]
async fn add_friend_by_lst_pid(
&self,
unk: u64,
pid: Vec<PID>,
) -> Result<Vec<FriendRelationship>, ErrorCode>;
#[method_id(13)]
async fn remove_friend_by_local_code(&self, local_code: u64) -> Result<(), ErrorCode>;
#[method_id(14)]
async fn remove_friend_by_pid(&self, pid: PID) -> Result<(), ErrorCode>;
#[method_id(15)]
async fn get_all_friends(&self) -> Result<Vec<FriendRelationship>, ErrorCode>;
#[method_id(16)]
async fn update_blacklist(&self) -> Result<(), ErrorCode>;
#[method_id(17)]
async fn sync_friend(
&self,
unk1: u64,
unk2: Vec<u32>,
unk3: Vec<u64>,
) -> Result<Vec<FriendRelationship>, ErrorCode>;
#[method_id(18)]
async fn update_presence(
&self,
nintendo_presence: NintendoPresence,
unk: bool,
) -> Result<(), ErrorCode>;
#[method_id(19)]
async fn update_favorite_game_key(&self, game_key: GameKey) -> Result<(), ErrorCode>;
#[method_id(20)]
async fn update_comment(&self, comment: String) -> Result<(), ErrorCode>;
#[method_id(21)]
async fn update_picture(&self, unk: u32, picture: Vec<u8>) -> Result<(), ErrorCode>;
#[method_id(22)]
async fn get_friend_presence(&self, unk: Vec<u32>) -> Result<Vec<FriendPresence>, ErrorCode>;
#[method_id(23)]
async fn get_friend_comment(
&self,
unk: Vec<FriendInfo>,
) -> Result<Vec<FriendComment>, ErrorCode>;
#[method_id(24)]
async fn get_friend_picture(&self, unk: Vec<u32>) -> Result<Vec<FriendPicture>, ErrorCode>;
#[method_id(25)]
async fn get_friend_persistent_info(
&self,
unk: Vec<u32>,
) -> Result<Vec<FriendPersistentInfo>, ErrorCode>;
#[method_id(26)]
async fn send_invitation(&self, unk: Vec<u32>) -> Result<(), ErrorCode>;
}

View file

@ -4,6 +4,8 @@ use rnex_core::{kerberos::KerberosDateTime, rmc::response::ErrorCode};
use rnex_core::rmc::structures::data::Data;
use rnex_core::PID;
#[derive(RmcSerialize, Debug, Clone)]
#[rmc_struct(0)]
pub struct MiiV2 {
@ -21,7 +23,7 @@ pub struct MiiV2 {
pub struct PrincipalBasicInfo {
#[extends]
pub data: Data,
pub pid: u32,
pub pid: PID,
pub nnid: String,
pub mii: MiiV2,
pub unk: u8,
@ -147,8 +149,17 @@ pub struct PersistentNotification {
pub unk5: String,
}
#[derive(RmcSerialize)]
#[rmc_struct(0)]
pub struct PrincipalRequestBlockSetting {
#[extends]
pub data: Data,
pub pid: PID,
pub blocked: bool,
}
#[rmc_proto(102)]
pub trait Friends {
pub trait FriendsWiiU {
#[method_id(1)]
async fn update_and_get_all_information(
&self,
@ -169,10 +180,53 @@ pub trait Friends {
),
ErrorCode,
>;
#[method_id(2)]
async fn add_friend(&self, friend: PID) -> Result<(FriendRequest, FriendInfo), ErrorCode>;
#[method_id(3)]
async fn add_friend_by_name(
&self,
name: String,
) -> Result<(FriendRequest, FriendInfo), ErrorCode>;
#[method_id(4)]
async fn remove_friend(&self, friend: PID) -> Result<(), ErrorCode>;
#[method_id(5)]
async fn add_friend_request(
&self,
friend: PID,
unk1: u8,
message: String,
unk2: u8,
unk3: String,
game_key: GameKey,
unk4: KerberosDateTime,
) -> Result<(FriendRequest, FriendInfo), ErrorCode>;
#[method_id(6)]
async fn cancel_friend_request(&self, id: u64) -> Result<(), ErrorCode>;
#[method_id(7)]
async fn accept_friend_request(&self, id: u64) -> Result<FriendInfo, ErrorCode>;
#[method_id(8)]
async fn delete_friend_request(&self, id: u64) -> Result<(), ErrorCode>;
#[method_id(9)]
async fn deny_friend_request(&self, id: u64) -> Result<BlacklistedPrincipal, ErrorCode>;
#[method_id(10)]
async fn mark_friend_requests_as_received(&self, ids: Vec<u64>) -> Result<(), ErrorCode>;
#[method_id(11)]
async fn add_blacklist(
&self,
principal: BlacklistedPrincipal,
) -> Result<BlacklistedPrincipal, ErrorCode>;
#[method_id(12)]
async fn remove_blacklist(&self, id: PID) -> Result<(), ErrorCode>;
#[method_id(13)]
async fn update_presence(&self, presence: NintendoPresenceV2) -> Result<(), ErrorCode>;
#[method_id(14)]
async fn update_mii(&self, presence: MiiV2) -> Result<KerberosDateTime, ErrorCode>;
#[method_id(15)]
async fn update_comment(&self, presence: Comment) -> Result<KerberosDateTime, ErrorCode>;
#[method_id(16)]
async fn update_preference(&self, preference: PrincipalPreference) -> Result<(), ErrorCode>;
#[method_id(17)]
async fn get_basic_info(&self, pids: Vec<PID>) -> Result<Vec<PrincipalBasicInfo>, ErrorCode>;
#[method_id(18)]
async fn delete_persistent_notification(
&self,
@ -180,4 +234,9 @@ pub trait Friends {
) -> Result<(), ErrorCode>;
#[method_id(19)]
async fn check_setting_status(&self) -> Result<u8, ErrorCode>;
#[method_id(20)]
async fn get_request_block_settings(
&self,
unk: Vec<u32>,
) -> Result<Vec<PrincipalRequestBlockSetting>, ErrorCode>;
}

View file

@ -3,7 +3,8 @@
pub mod account_management;
pub mod auth;
pub mod datastore;
pub mod friends;
pub mod friends_3ds;
pub mod friends_wiiu;
pub mod matchmake;
pub mod matchmake_ext;
pub mod matchmake_extension;

View file

@ -1,10 +1,12 @@
use macros::{RmcSerialize, method_id, rmc_proto};
use rnex_core::kerberos::KerberosDateTime;
use rnex_core::rmc::structures::qbuffer::QBuffer;
use rnex_core::rmc::structures::resultsrange::ResultsRange;
use rnex_core::rmc::response::ErrorCode;
use rnex_core::rmc::structures::qbuffer::QBuffer;
use rnex_core::rmc::structures::ranking::UploadCompetitionData;
use rnex_core::rmc::structures::resultsrange::ResultsRange;
use crate::PID;
#[derive(RmcSerialize, Debug, Default, Clone)]
#[rmc_struct(1)]
@ -28,7 +30,7 @@ pub struct CompetitionRankingScoreInfo {
#[rmc_struct(0)]
pub struct CompetitionRankingScoreData {
pub unk: u32,
pub pid: u32,
pub pid: PID,
pub score: u32,
pub modified: KerberosDateTime,
pub unk2: u8,

View file

@ -1,6 +1,5 @@
use cfg_if::cfg_if;
use macros::RmcSerialize;
use rnex_core::kerberos::KerberosDateTime;
use rnex_core::rmc::structures::variant::Variant;
use rnex_core::PID;
@ -32,6 +31,7 @@ pub struct MatchmakeParam {
cfg_if! {
if #[cfg(feature = "v3-5-0")]{
use rnex_core::kerberos::KerberosDateTime;
#[derive(RmcSerialize, Debug, Clone, Default, PartialEq)]
#[rmc_struct(3)]
pub struct MatchmakeSession {

View file

@ -0,0 +1,88 @@
use std::{
hash::{DefaultHasher, Hash, Hasher},
sync::Arc,
};
use rnex_server_api::gatherings::{
Gatherings, gathering_info_service_server::GatheringInfoService,
};
use tonic::{Request, Response, Status, async_trait};
use crate::{
nex::matchmake::{ExtendedMatchmakeSession, MatchmakeManager},
rmc::structures::matchmake::{self, MatchmakeSession},
server_api,
};
impl Into<rnex_server_api::gatherings::Gathering> for &ExtendedMatchmakeSession {
fn into(self) -> rnex_server_api::gatherings::Gathering {
let players = self.get_active_players().map(|p| *p.pid as u64).collect();
let ExtendedMatchmakeSession {
session:
MatchmakeSession {
gathering:
matchmake::Gathering {
state,
flags,
host_pid,
description,
maximum_participants,
minimum_participants,
owner_pid,
participant_policy,
policy_argument,
self_gid,
},
application_buffer,
attributes,
gamemode,
matchmake_system_type,
open_participation,
participation_count,
session_key,
},
connected_players,
} = self;
rnex_server_api::gatherings::Gathering {
players,
description: description.clone(),
flags: *flags,
host_pid: *host_pid as _,
maximum_participants: *maximum_participants as _,
minimum_participants: *minimum_participants as _,
owner_pid: *owner_pid as _,
participant_policy: *participant_policy,
policy_argument: *policy_argument,
self_gid: *self_gid,
state: *state,
application_buffer: application_buffer.clone(),
attributes: attributes.clone(),
..Default::default()
}
}
}
struct GatheringsApi(Arc<MatchmakeManager>);
#[async_trait]
impl GatheringInfoService for GatheringsApi {
async fn get_gatherings(
&self,
request: Request<()>,
) -> std::result::Result<Response<Gatherings>, Status> {
Ok(Response::new(Gatherings {
gatherings: self
.0
.sessions
.read()
.await
.keys()
.map(|gid| {
let mut hasher = DefaultHasher::new();
gid.hash(&mut hasher);
hasher.finish()
})
.collect(),
}))
}
}

View file

@ -0,0 +1,36 @@
use rnex_server_api::meta::{
ApiFeature, ApiFeatures, BuildInfo, server_meta_service_server::ServerMetaService,
};
use tonic::{Request, Response, Status, async_trait, transport::Server};
pub struct ServerMeta;
#[async_trait]
impl ServerMetaService for ServerMeta {
async fn get_build_info(&self, request: Request<()>) -> Result<Response<BuildInfo>, Status> {
Ok(Response::new(BuildInfo {
version: env!("CARGO_PKG_VERSION").to_owned(),
edition: env!("EDITION").to_owned(),
build_hash: env!("GIT_HASH").to_owned(),
feature_set: env!("FEATURESET").to_owned(),
}))
}
async fn get_api_features(
&self,
request: Request<()>,
) -> Result<Response<ApiFeatures>, Status> {
let mut api_features = vec![ApiFeature {
name: "meta".to_owned(),
needs_admin: false,
version: 0,
}];
#[cfg(not(feature = "friends"))]
api_features.push(ApiFeature {
name: "gatherings".to_owned(),
version: 0,
needs_admin: true,
});
Ok(Response::new(ApiFeatures { api_features }))
}
}

View file

@ -0,0 +1,22 @@
use std::{net::SocketAddr, str::FromStr};
use rnex_server_api::meta::server_meta_service_server::ServerMetaServiceServer;
use tonic::transport::Server;
#[cfg(not(feature = "friends"))]
use crate::nex::matchmake::MatchmakeManager;
use crate::server_api::meta::ServerMeta;
#[cfg(not(feature = "friends"))]
use std::sync::Arc;
#[cfg(not(feature = "friends"))]
mod gatherings;
mod meta;
pub async fn launch_server_api(#[cfg(not(feature = "friends"))] mmm: Arc<MatchmakeManager>) {
let mut server = Server::builder().add_service(ServerMetaServiceServer::new(ServerMeta));
server
.serve(SocketAddr::from_str("0.0.0.0:80").expect("unable to make sockaddr from ip"))
.await;
}

View file

@ -0,0 +1,54 @@
syntax = "proto3";
// NOTE: NEVER give out gids to the general public, people can and will be
// able to join random mkatches which they arent supposed to join via them
//
// As an alternative use a unique hash of the Gid
import "google/protobuf/empty.proto";
package gatherings;
message MatchmakeParam{
}
message KerberosDateTime{
uint32 raw = 1;
}
message Gathering{
repeated uint64 players = 1;
uint32 self_gid = 2;
uint64 owner_pid = 3;
uint64 host_pid = 4;
uint32 minimum_participants = 5;
uint32 maximum_participants = 6;
uint32 participant_policy = 7;
uint32 policy_argument = 8;
uint32 flags = 9;
uint32 state = 10;
string description = 11;
uint32 gamemode = 12;
repeated uint32 attributes = 13;
bool open_participation = 14;
uint32 matchmake_system_type = 15;
bytes application_buffer = 16;
uint32 participation_count = 17;
bytes session_key = 18;
optional uint32 progress_score = 19;
optional uint32 option0 = 20;
optional MatchmakeParam matchmake_param = 21;
optional KerberosDateTime datetime = 22;
optional string user_password = 23;
optional uint32 refer_gid = 24;
optional bool user_password_enabled = 25;
optional bool system_password_enabled = 26;
}
message Gatherings{
repeated Gathering gatherings = 1;
}
service GatheringInfoService{
rpc GetGatherings(google.protobuf.Empty) returns (Gatherings);
}

View file

@ -0,0 +1,27 @@
syntax = "proto3";
package meta;
import "google/protobuf/empty.proto";
message BuildInfo {
string version = 1;
string edition = 2;
string feature_set = 3;
string build_hash = 4;
}
message ApiFeature {
string name = 1;
int32 version = 2;
bool needs_admin = 3;
}
message ApiFeatures {
repeated ApiFeature api_features = 1;
}
service ServerMetaService {
rpc GetBuildInfo(google.protobuf.Empty) returns (BuildInfo);
rpc GetApiFeatures(google.protobuf.Empty) returns (ApiFeatures);
}

View file

@ -0,0 +1,12 @@
[package]
name = "rnex-server-api"
version = "0.1.0"
edition = "2024"
[dependencies]
prost = "0.14.3"
tonic-prost = "*"
tonic = "0.14.6"
[build-dependencies]
tonic-prost-build = "0.14.6"

24
rnex-server-api/build.rs Normal file
View file

@ -0,0 +1,24 @@
use std::{env, fs};
fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-changed=../rnex-server-api-grpc/");
let protos: Vec<String> = fs::read_dir("../rnex-server-api-grpc/")?
.filter(|e| {
e.as_ref().is_ok_and(|e| {
e.file_name()
.to_str()
.is_some_and(|n| n.ends_with(".proto"))
})
})
.filter_map(|e| {
e.ok()
.map(|v| v.path().to_str().map(|v| v.to_owned()))
.flatten()
})
.collect();
tonic_prost_build::configure()
.compile_protos(&protos[..], &["../rnex-server-api-grpc/".to_owned()])?;
Ok(())
}

View file

@ -0,0 +1,6 @@
pub mod meta {
tonic::include_proto!("meta");
}
pub mod gatherings {
tonic::include_proto!("gatherings");
}