rework Any

This commit is contained in:
Maple Nebel 2026-06-19 17:37:17 +02:00
commit b79e47d127
13 changed files with 283 additions and 89 deletions

29
Cargo.lock generated
View file

@ -861,6 +861,16 @@ dependencies = [
"hybrid-array",
]
[[package]]
name = "ctor"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01334b89b69ff726750c5ce5073fc8bd860e99aa9a8fc5ca11b04730e3aee97a"
dependencies = [
"link-section",
"linktime-proc-macro",
]
[[package]]
name = "ctutils"
version = "0.4.2"
@ -1022,7 +1032,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [
"libc",
"windows-sys 0.52.0",
"windows-sys 0.61.2",
]
[[package]]
@ -1572,7 +1582,7 @@ dependencies = [
"libc",
"percent-encoding",
"pin-project-lite",
"socket2 0.5.10",
"socket2 0.6.3",
"tokio",
"tower-service",
"tracing",
@ -1821,6 +1831,18 @@ dependencies = [
"vcpkg",
]
[[package]]
name = "link-section"
version = "0.18.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2b1dd6fe32e55c0fc0ea9493aa57459ca3cf4ff3c857c7d0302290150da6e4f"
[[package]]
name = "linktime-proc-macro"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c7b0a3383c2a1002d11349c92c85a666a5fb679e96c79d782cf0dbe557fd6ee"
[[package]]
name = "litemap"
version = "0.8.2"
@ -2403,6 +2425,7 @@ dependencies = [
"bytemuck",
"cfg-if",
"chrono",
"ctor",
"dotenv",
"futures",
"hex",
@ -3625,7 +3648,7 @@ version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
dependencies = [
"windows-sys 0.48.0",
"windows-sys 0.61.2",
]
[[package]]

View file

@ -17,20 +17,16 @@ use syn::{parse_macro_input, Data, DeriveInput, Lit, LitStr};
pub fn rmc_serialize(input: TokenStream) -> TokenStream {
let derive_input = parse_macro_input!(input as DeriveInput);
let (serialize, deserialize, write_size, version) = match &derive_input.data {
Data::Struct(s) => rmc_serialize_struct(s, &derive_input),
let (serialize, deserialize, write_size, version, rmc_struct_impl) = match &derive_input.data {
Data::Struct(s) => rmc_serialize_struct(s, &derive_input.ident, &derive_input),
Data::Enum(e) => rmc_serialize_enum(e, &derive_input),
Data::Union(_) => {
unimplemented!("serialize a union is not allowed");
unimplemented!("serializing a union is not allowed");
}
};
// generate base data
let str_name = Lit::Str(LitStr::new(
&derive_input.ident.to_string(),
derive_input.ident.span(),
));
let ident = derive_input.ident;
let write_size = if let Some(v) = write_size {
@ -52,6 +48,7 @@ pub fn rmc_serialize(input: TokenStream) -> TokenStream {
} else {
quote! {}
};
let rmc_struct_impl = rmc_struct_impl.unwrap_or_default();
let tokens = quote! {
impl rnex_core::rmc::structures::RmcSerialize for #ident{
@ -67,11 +64,8 @@ pub fn rmc_serialize(input: TokenStream) -> TokenStream {
#write_size
#version
fn name() -> &'static str{
#str_name
}
}
#rmc_struct_impl
};
tokens.into()

View file

@ -1,8 +1,8 @@
use proc_macro2::{Literal, Span, TokenStream};
use quote::quote;
use quote::{quote, ToTokens};
use syn::{
bracketed, parse::Parse, punctuated::Punctuated, token::Bracket, DataEnum, DataStruct,
DeriveInput, Field, Fields, Ident, Meta, Token, Variant,
bracketed, ext, parse::Parse, punctuated::Punctuated, token::Bracket, DataEnum, DataStruct,
DeriveInput, Field, Fields, Ident, LitStr, Meta, Token, Variant,
};
use crate::util::fold_tokenable;
@ -235,14 +235,60 @@ fn generate_struct_version(attr: Option<&RmcStructAttr>) -> proc_macro2::TokenSt
}
}
fn gen_rmc_struct_impl(
struct_ident: &Ident,
extended_struct: Option<&Field>,
) -> proc_macro2::TokenStream {
let self_name_str_lit = LitStr::new(&struct_ident.to_string(), struct_ident.span());
let register = if let Some(extended_struct) = extended_struct {
let extended_struct_ty = &extended_struct.ty;
let ext_ty_name = LitStr::new(
&extended_struct_ty.to_token_stream().to_string(),
Span::call_site(),
);
quote! {
#[::rnex_core::ctor(unsafe)]
#[allow(nonstandard_style)]
fn register_fun() {
println!("registering {} as parent of {}", #self_name_str_lit, #ext_ty_name);
let mut wr = <#extended_struct_ty as ::rnex_core::rmc::structures::RmcStruct>::get_struct_info()
.inheritors
.write()
.expect("poisoned");
wr.push(<#struct_ident as ::rnex_core::rmc::structures::RmcStruct>::get_struct_info());
}
}
} else {
quote! {}
};
quote! {
impl ::rnex_core::rmc::structures::RmcStruct for #struct_ident {
fn get_struct_info() -> &'static ::rnex_core::rmc::structures::RmcStructInfo {
#register
static STRUCT_DATA: ::rnex_core::rmc::structures::RmcStructInfo =
::rnex_core::rmc::structures::RmcStructInfo {
inheritors: ::std::sync::RwLock::new(::std::vec::Vec::new()),
name: #self_name_str_lit,
};
&STRUCT_DATA
}
}
}
}
pub fn rmc_serialize_struct(
s: &DataStruct,
name: &Ident,
derive_input: &DeriveInput,
) -> (
proc_macro2::TokenStream,
proc_macro2::TokenStream,
Option<proc_macro2::TokenStream>,
Option<proc_macro2::TokenStream>,
Option<proc_macro2::TokenStream>,
) {
let struct_attr = derive_input.attrs.iter().find(|a| {
a.path().segments.len() == 1
@ -285,8 +331,19 @@ pub fn rmc_serialize_struct(
generate_deserialize_struct(s, extended_struct, elements, struct_attr.is_some());
let write_size = generate_write_size_struct(s, struct_attr.is_some());
let version = generate_struct_version(struct_attr);
let rmc_struct_impl = if struct_attr.is_some() {
Some(gen_rmc_struct_impl(name, extended_struct))
} else {
None
};
(serialize, deserialize, Some(write_size), Some(version))
(
serialize,
deserialize,
Some(write_size),
Some(version),
rmc_struct_impl,
)
}
fn field_to_ident(field: &Field, idx: usize) -> Ident {
@ -405,6 +462,7 @@ pub fn rmc_serialize_enum(
proc_macro2::TokenStream,
Option<proc_macro2::TokenStream>,
Option<proc_macro2::TokenStream>,
Option<proc_macro2::TokenStream>,
) {
let repr_attr = derive_input.attrs.iter().find(|a| {
a.path().segments.len() == 1
@ -422,5 +480,5 @@ pub fn rmc_serialize_enum(
let serialize = rmc_generate_serialize_enum(&enum_data, &ty);
let deserialize = rmc_generate_deserialize_enum(&enum_data, &ty);
(serialize, deserialize, None, None)
(serialize, deserialize, None, None, None)
}

View file

@ -36,6 +36,7 @@ sha2 = "0.10.9"
urlencoding = "2.1.3"
futures = "0.3.32"
async-trait = "0.1.89"
ctor = "1.0.7"
[dev-dependencies]
# criterion = "0.7.0"

View file

@ -9,6 +9,8 @@ pub type PID = i64;
#[cfg(not(feature = "big_pid"))]
pub type PID = i32;
pub use ctor::ctor;
extern crate self as rnex_core;
pub mod prudp;

View file

@ -54,6 +54,7 @@ use rnex_core::rmc::structures::ranking::UploadCompetitionData;
use std::sync::{Arc, Weak};
use tokio::sync::{Mutex, RwLock};
use crate::rmc::structures::matchmake::Gathering;
use crate::rmc::structures::matchmake::MatchmakeSessionSearchCriteria;
cfg_if! {
@ -419,13 +420,11 @@ impl MatchmakeExtension for User {
async fn create_matchmake_session(
&self,
gathering: Any,
gathering: Any<Gathering>,
message: String,
) -> Result<(u32, Vec<u8>), ErrorCode> {
info!("gathering: {:?}", gathering);
let Some(Ok(session)): Option<Result<MatchmakeSession, _>> = gathering.try_get() else {
return Err(ErrorCode::Core_InvalidArgument);
};
let session: MatchmakeSession = gathering.try_get_as()?;
let session = self
.create_matchmake_session_with_param(CreateMatchmakeSessionParam {
@ -517,14 +516,10 @@ impl MatchmakeExtension for User {
async fn auto_matchmake_with_search_criteria_postpone(
&self,
criteria: Vec<MatchmakeSessionSearchCriteria>,
gathering: Any,
gathering: Any<Gathering>,
join_message: String,
) -> Result<Any, ErrorCode> {
let session: MatchmakeSession = gathering
.try_get()
.map(|v| v.ok())
.flatten()
.ok_or(ErrorCode::Core_InvalidArgument)?;
) -> Result<Any<Gathering>, ErrorCode> {
let session: MatchmakeSession = gathering.try_get_as()?;
println!("{:?}", criteria);
@ -548,7 +543,7 @@ impl MatchmakeExtension for User {
}
impl Matchmake for User {
async fn find_by_single_id(&self, gid: u32) -> Result<(bool, Any), ErrorCode> {
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((

View file

@ -5,17 +5,32 @@ use rnex_core::{
rmc::{response::ErrorCode, structures::any::Any},
};
use crate::{kerberos::KerberosDateTime, rmc::protocols::friends_wiiu::NNAInfo};
use crate::{
kerberos::KerberosDateTime,
rmc::{protocols::friends_wiiu::NNAInfo, structures::data::Data},
};
#[derive(RmcSerialize, Debug, Clone)]
#[rmc_struct(0)]
pub struct NintendoCreateAccountData {
#[extends]
pub data: Data,
pub nna_info: NNAInfo,
pub nex_token: String,
pub birthday: KerberosDateTime,
pub unk: u64,
}
#[derive(RmcSerialize, Debug, Clone)]
#[rmc_struct(0)]
pub struct AccountExtraInfo {
#[extends]
pub data: Data,
pub unk1: u64,
pub unk2: u32,
pub nex_token: String,
}
#[rmc_proto(25)]
pub trait AccountManagement {
#[method_id(27)]

View file

@ -5,13 +5,14 @@ use rnex_core::rmc::response::ErrorCode;
use rnex_core::PID;
use crate::rmc::structures::any::Any;
use crate::rmc::structures::matchmake::Gathering;
#[rmc_proto(21)]
pub trait Matchmake {
#[method_id(2)]
async fn unregister_gathering(&self, gid: u32) -> Result<bool, ErrorCode>;
#[method_id(21)]
async fn find_by_single_id(&self, gid: u32) -> Result<(bool, Any), ErrorCode>;
async fn find_by_single_id(&self, gid: u32) -> Result<(bool, Any<Gathering>), ErrorCode>;
#[method_id(41)]
async fn get_session_urls(&self, gid: u32) -> Result<Vec<StationUrl>, ErrorCode>;
#[method_id(42)]

View file

@ -6,7 +6,7 @@ use rnex_core::rmc::structures::matchmake::{
use crate::rmc::protocols::notifications::NotificationEvent;
use crate::rmc::structures::any::Any;
use crate::rmc::structures::matchmake::MatchmakeSessionSearchCriteria;
use crate::rmc::structures::matchmake::{Gathering, MatchmakeSessionSearchCriteria};
#[rmc_proto(109)]
pub trait MatchmakeExtension {
@ -18,7 +18,7 @@ pub trait MatchmakeExtension {
#[method_id(6)]
async fn create_matchmake_session(
&self,
gathering: Any,
gathering: Any<Gathering>,
message: String,
) -> Result<(u32, Vec<u8>), ErrorCode>;
@ -40,9 +40,9 @@ pub trait MatchmakeExtension {
async fn auto_matchmake_with_search_criteria_postpone(
&self,
criteria: Vec<MatchmakeSessionSearchCriteria>,
gathering: Any,
gathering: Any<Gathering>,
join_msg: String,
) -> Result<Any, ErrorCode>;
) -> Result<Any<Gathering>, ErrorCode>;
#[method_id(30)]
async fn join_matchmake_session_ex(

View file

@ -4,6 +4,7 @@ use rnex_core::rmc::response::ErrorCode;
use rnex_core::rmc::structures::qresult::QResult;
use crate::rmc::structures::any::Any;
use crate::rmc::structures::data::Data;
#[rmc_proto(11)]
pub trait Secure {
@ -17,7 +18,7 @@ pub trait Secure {
async fn register_ex(
&self,
station_urls: Vec<StationUrl>,
data: Any,
data: Any<Data>,
) -> Result<(QResult, u32, StationUrl), ErrorCode>;
#[method_id(7)]
async fn replace_url(&self, target: StationUrl, dest: StationUrl) -> Result<(), ErrorCode>;

View file

@ -456,6 +456,13 @@ pub enum ErrorCode {
Ess_GameSessionMaintenance = 0x00750003,
}
impl From<super::structures::Error> for ErrorCode {
fn from(value: super::structures::Error) -> Self {
error!("rmc error occurred during method runtime: {}", value);
Self::Core_InvalidArgument
}
}
impl Into<u32> for ErrorCode {
fn into(self) -> u32 {
unsafe { transmute(self) }

View file

@ -1,14 +1,20 @@
use rnex_core::rmc::structures::{Result, RmcSerialize};
use std::io::{Cursor, Read, Write};
use std::{
io::{Cursor, Read, Write},
marker::PhantomData,
};
use v_byte_helpers::{IS_BIG_ENDIAN, ReadExtensions};
#[derive(Debug, Default, Clone)]
pub struct Any {
use crate::rmc::structures::{RmcStruct, data::Data};
#[derive(Clone, Debug)]
pub struct Any<T: RmcStruct = Data> {
pub name: String,
pub data: Vec<u8>,
pub phantom_data: PhantomData<T>,
}
impl RmcSerialize for Any {
impl<T: RmcStruct> RmcSerialize for Any<T> {
fn serialize(&self, writer: &mut (impl Write + ?Sized)) -> Result<()> {
self.name.serialize(writer)?;
@ -21,25 +27,131 @@ impl RmcSerialize for Any {
fn deserialize(mut reader: &mut (impl Read + ?Sized)) -> Result<Self> {
let name = String::deserialize(reader)?;
if !T::get_struct_info().is_inheritor(&name) {
return Err(super::Error::InheritanceError);
}
// also length ?
let _len2: u32 = reader.read_struct(IS_BIG_ENDIAN)?;
let data = Vec::deserialize(reader)?;
Ok(Any { name, data })
Ok(Any {
name,
data,
phantom_data: PhantomData,
})
}
}
impl Any {
pub fn try_get<T: RmcSerialize>(&self) -> Option<Result<T>> {
if self.name != T::name() {
return None;
impl<T: RmcStruct> Any<T> {
pub fn try_into<U: RmcStruct>(self) -> Result<Any<U>> {
if !U::get_struct_info().is_inheritor(&self.name) {
return Err(super::Error::InheritanceError);
}
return Some(T::deserialize(&mut Cursor::new(&self.data[..])));
Ok(Any {
data: self.data,
name: self.name,
phantom_data: PhantomData,
})
}
pub fn new<T: RmcSerialize>(val: &T) -> Result<Self> {
pub fn try_get_as<U: RmcStruct>(&self) -> Result<U> {
if !U::get_struct_info().is_inheritor(&self.name) {
return Err(super::Error::InheritanceError);
}
return U::deserialize(&mut Cursor::new(&self.data[..]));
}
pub fn new<U: RmcStruct>(val: &U) -> Result<Self> {
if !T::get_struct_info().is_inheritor(U::get_struct_info().name) {
return Err(super::Error::InheritanceError);
}
return Ok(Self {
name: T::name().to_owned(),
name: U::get_struct_info().name.to_owned(),
data: val.to_data()?,
phantom_data: PhantomData,
});
}
pub fn get(&self) -> Result<T> {
return T::deserialize(&mut Cursor::new(&self.data[..]));
}
pub fn emplace_parent<E: RmcStruct>(&mut self, parent: &E) -> Result<()> {
// validate if E is actually a parent of the contained struct
if !E::get_struct_info().is_inheritor(&self.name) {
return Err(super::Error::InheritanceError);
}
let mut cur = Cursor::new(&self.data[..]);
// skip the parent part of the struct
let _ = T::deserialize(&mut cur)?;
let end_of_parent_pos = cur.position();
let rest_of_struct = &self
.data
.get(end_of_parent_pos as usize..)
.ok_or(super::Error::OOB)?;
let mut new_data = parent.to_data()?;
new_data.extend_from_slice(&rest_of_struct);
self.data = new_data;
Ok(())
}
}
#[cfg(test)]
mod test {
use std::io::Cursor;
use macros::{RmcSerialize, rmc_struct};
use crate::rmc::structures::RmcSerialize;
use crate::rmc::structures::any::Any;
#[derive(RmcSerialize)]
#[rmc_struct(0)]
struct StructB {
valb: u32,
}
#[derive(RmcSerialize)]
#[rmc_struct(0)]
struct StructA {
#[extends]
base: StructB,
vala: u32,
}
#[derive(RmcSerialize)]
#[rmc_struct(0)]
struct Unrelated {
vala: u32,
}
#[test]
fn test() {
let initial: Any<StructA> = Any::new(&StructA {
base: StructB { valb: 10 },
vala: 11,
})
.unwrap();
let encoded = initial.to_data().unwrap();
let mut reser_b: Any<StructB> = Any::deserialize(&mut Cursor::new(&encoded[..])).unwrap();
let mut data_b: StructB = reser_b.get().unwrap();
assert_eq!(data_b.valb, 10);
data_b.valb = 20;
reser_b.emplace_parent(&data_b).unwrap();
let data_a: StructA = reser_b.try_get_as().unwrap();
assert_eq!(data_a.vala, 11);
assert_eq!(data_a.base.valb, 20);
let data: Any<StructA> = reser_b.try_into().unwrap();
let data: Any<StructB> = data.try_into().unwrap();
let fail: Result<Any<Unrelated>, _> = data.try_into();
assert!(fail.is_err());
let fail: Result<Any<Unrelated>, _> = Any::deserialize(&mut Cursor::new(&encoded[..]));
assert!(fail.is_err());
}
}

View file

@ -1,7 +1,9 @@
use crate::rmc::structures::helpers::DummyWriter;
use async_trait::async_trait;
use ctor::ctor;
use std::io::{Read, Write};
use std::string::FromUtf8Error;
use std::sync::RwLock;
use std::{fmt, io};
use thiserror::Error;
//ideas for the future: make a proc macro library which allows generation of struct reads
@ -21,11 +23,15 @@ pub enum Error {
StationUrlInvalid,
#[error("error formatting text: {0}")]
FormatError(#[from] fmt::Error),
#[error("tried to validate inheritance chain")]
InheritanceError,
#[error("uncategorized rmc error occurred: {0}")]
Other(Box<dyn std::error::Error + Send + Sync>),
#[error("unexpected out of bounds read/write")]
OOB,
}
pub type Result<T> = std::result::Result<T, Error>;
pub type Result<T, E = Error> = std::result::Result<T, E>;
pub mod any;
pub mod buffer;
@ -68,9 +74,6 @@ pub trait RmcSerialize {
Ok(data)
}
fn name() -> &'static str {
"NoNameSpecified"
}
fn version() -> Option<u8> {
None
}
@ -79,21 +82,6 @@ pub trait RmcSerialize {
trait SendWrite: Send + Write {}
impl<T: Send + Write> SendWrite for T {}
// beware that this trait throws away most of the optimizations which come with using
// the regular `Serialize` trait, ONLY use this when it is 100% required to have dyn
// compatibility
#[async_trait]
trait DynRmcSerialize: Send + Sync {
async fn serialize(&self, writer: &mut dyn SendWrite) -> Result<()>;
}
#[async_trait]
impl<T: RmcSerialize + Send + Sync> DynRmcSerialize for T {
async fn serialize(&self, writer: &mut dyn SendWrite) -> Result<()> {
<Self as RmcSerialize>::serialize(&self, writer)
}
}
impl RmcSerialize for () {
fn serialize(&self, _writer: &mut (impl Write + ?Sized)) -> Result<()> {
Ok(())
@ -106,31 +94,28 @@ impl RmcSerialize for () {
}
}
trait RmcInternalAnyUnknownAs<T: RmcStruct>: AsRef<T> + DynRmcSerialize + RmcStructInstance {}
trait RmcCastable {
// consumes box and returns a Box containing a Box with the requested Struct details
fn cast_to(self: Box<Self>, destination: &RmcStructInfo) -> Box<dyn std::any::Any>;
}
struct RmcStructInfo {
inheritors: Vec<&'static RmcStructInfo>,
name: &'static str,
deserialize_abstract: fn(&mut dyn Read) -> Box<dyn std::any::Any>,
pub struct RmcStructInfo {
// this may never be locked after initialization
pub inheritors: RwLock<Vec<&'static RmcStructInfo>>,
pub name: &'static str,
}
impl RmcStructInfo {
fn deserialze_abstract_as<T: RmcStruct>(
reader: &impl Read,
) -> Box<dyn RmcInternalAnyUnknownAs<T>> {
todo!()
fn is_inheritor(&self, name: &str) -> bool {
if name == self.name {
return true;
}
let inheritors = self.inheritors.read().expect("poisoned");
for inheritor in inheritors.iter() {
if inheritor.is_inheritor(name) {
return true;
}
}
return false;
}
}
trait RmcStruct {
pub trait RmcStruct: RmcSerialize {
fn get_struct_info() -> &'static RmcStructInfo;
}
trait RmcStructInstance {
fn get_self_struct_info(&self) -> &'static RmcStructInfo;
}