From b79e47d1274bedb0fd7d0700c371a6961e44332c Mon Sep 17 00:00:00 2001 From: Maple Nebel Date: Fri, 19 Jun 2026 17:37:17 +0200 Subject: [PATCH] rework Any --- Cargo.lock | 29 +++- macros/src/lib.rs | 16 +-- macros/src/rmc_struct.rs | 68 ++++++++- rnex-core/Cargo.toml | 1 + rnex-core/src/lib.rs | 2 + rnex-core/src/nex/user.rs | 19 +-- .../src/rmc/protocols/account_management.rs | 17 ++- rnex-core/src/rmc/protocols/matchmake.rs | 3 +- .../src/rmc/protocols/matchmake_extension.rs | 8 +- rnex-core/src/rmc/protocols/secure.rs | 3 +- rnex-core/src/rmc/response.rs | 7 + rnex-core/src/rmc/structures/any.rs | 136 ++++++++++++++++-- rnex-core/src/rmc/structures/mod.rs | 63 ++++---- 13 files changed, 283 insertions(+), 89 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9f158db..496b346 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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]] diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 5bd41bc..e54ee7c 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -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() diff --git a/macros/src/rmc_struct.rs b/macros/src/rmc_struct.rs index 66f9515..b9e701b 100644 --- a/macros/src/rmc_struct.rs +++ b/macros/src/rmc_struct.rs @@ -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, Option, + Option, ) { 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, Option, + Option, ) { 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) } diff --git a/rnex-core/Cargo.toml b/rnex-core/Cargo.toml index 16b8704..e8ea554 100644 --- a/rnex-core/Cargo.toml +++ b/rnex-core/Cargo.toml @@ -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" diff --git a/rnex-core/src/lib.rs b/rnex-core/src/lib.rs index 81e254c..c382964 100644 --- a/rnex-core/src/lib.rs +++ b/rnex-core/src/lib.rs @@ -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; diff --git a/rnex-core/src/nex/user.rs b/rnex-core/src/nex/user.rs index 970eb5b..dffe49f 100644 --- a/rnex-core/src/nex/user.rs +++ b/rnex-core/src/nex/user.rs @@ -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, message: String, ) -> Result<(u32, Vec), ErrorCode> { info!("gathering: {:?}", gathering); - let Some(Ok(session)): Option> = 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, - gathering: Any, + gathering: Any, join_message: String, - ) -> Result { - let session: MatchmakeSession = gathering - .try_get() - .map(|v| v.ok()) - .flatten() - .ok_or(ErrorCode::Core_InvalidArgument)?; + ) -> Result, 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), ErrorCode> { let s = self.matchmake_manager.get_session(gid).await?; let s = s.lock().await; Ok(( diff --git a/rnex-core/src/rmc/protocols/account_management.rs b/rnex-core/src/rmc/protocols/account_management.rs index a245fe7..c17ff86 100644 --- a/rnex-core/src/rmc/protocols/account_management.rs +++ b/rnex-core/src/rmc/protocols/account_management.rs @@ -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)] diff --git a/rnex-core/src/rmc/protocols/matchmake.rs b/rnex-core/src/rmc/protocols/matchmake.rs index d1c43b9..d0f98af 100644 --- a/rnex-core/src/rmc/protocols/matchmake.rs +++ b/rnex-core/src/rmc/protocols/matchmake.rs @@ -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; #[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), ErrorCode>; #[method_id(41)] async fn get_session_urls(&self, gid: u32) -> Result, ErrorCode>; #[method_id(42)] diff --git a/rnex-core/src/rmc/protocols/matchmake_extension.rs b/rnex-core/src/rmc/protocols/matchmake_extension.rs index 7e7a00d..2d745ba 100644 --- a/rnex-core/src/rmc/protocols/matchmake_extension.rs +++ b/rnex-core/src/rmc/protocols/matchmake_extension.rs @@ -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, message: String, ) -> Result<(u32, Vec), ErrorCode>; @@ -40,9 +40,9 @@ pub trait MatchmakeExtension { async fn auto_matchmake_with_search_criteria_postpone( &self, criteria: Vec, - gathering: Any, + gathering: Any, join_msg: String, - ) -> Result; + ) -> Result, ErrorCode>; #[method_id(30)] async fn join_matchmake_session_ex( diff --git a/rnex-core/src/rmc/protocols/secure.rs b/rnex-core/src/rmc/protocols/secure.rs index 503bf93..cf51c80 100644 --- a/rnex-core/src/rmc/protocols/secure.rs +++ b/rnex-core/src/rmc/protocols/secure.rs @@ -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, - data: Any, + data: Any, ) -> Result<(QResult, u32, StationUrl), ErrorCode>; #[method_id(7)] async fn replace_url(&self, target: StationUrl, dest: StationUrl) -> Result<(), ErrorCode>; diff --git a/rnex-core/src/rmc/response.rs b/rnex-core/src/rmc/response.rs index d401892..db093fb 100644 --- a/rnex-core/src/rmc/response.rs +++ b/rnex-core/src/rmc/response.rs @@ -456,6 +456,13 @@ pub enum ErrorCode { Ess_GameSessionMaintenance = 0x00750003, } +impl From for ErrorCode { + fn from(value: super::structures::Error) -> Self { + error!("rmc error occurred during method runtime: {}", value); + Self::Core_InvalidArgument + } +} + impl Into for ErrorCode { fn into(self) -> u32 { unsafe { transmute(self) } diff --git a/rnex-core/src/rmc/structures/any.rs b/rnex-core/src/rmc/structures/any.rs index 635c22b..cdc1357 100644 --- a/rnex-core/src/rmc/structures/any.rs +++ b/rnex-core/src/rmc/structures/any.rs @@ -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 { pub name: String, pub data: Vec, + pub phantom_data: PhantomData, } -impl RmcSerialize for Any { +impl RmcSerialize for Any { 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 { 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(&self) -> Option> { - if self.name != T::name() { - return None; +impl Any { + pub fn try_into(self) -> Result> { + 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(val: &T) -> Result { + pub fn try_get_as(&self) -> Result { + 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(val: &U) -> Result { + 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 { + return T::deserialize(&mut Cursor::new(&self.data[..])); + } + + pub fn emplace_parent(&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 = Any::new(&StructA { + base: StructB { valb: 10 }, + vala: 11, + }) + .unwrap(); + + let encoded = initial.to_data().unwrap(); + + let mut reser_b: Any = 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 = reser_b.try_into().unwrap(); + let data: Any = data.try_into().unwrap(); + let fail: Result, _> = data.try_into(); + assert!(fail.is_err()); + let fail: Result, _> = Any::deserialize(&mut Cursor::new(&encoded[..])); + assert!(fail.is_err()); + } } diff --git a/rnex-core/src/rmc/structures/mod.rs b/rnex-core/src/rmc/structures/mod.rs index 1a17991..d78782f 100644 --- a/rnex-core/src/rmc/structures/mod.rs +++ b/rnex-core/src/rmc/structures/mod.rs @@ -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), + #[error("unexpected out of bounds read/write")] + OOB, } -pub type Result = std::result::Result; +pub type Result = std::result::Result; pub mod any; pub mod buffer; @@ -68,9 +74,6 @@ pub trait RmcSerialize { Ok(data) } - fn name() -> &'static str { - "NoNameSpecified" - } fn version() -> Option { None } @@ -79,21 +82,6 @@ pub trait RmcSerialize { trait SendWrite: Send + Write {} impl 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 DynRmcSerialize for T { - async fn serialize(&self, writer: &mut dyn SendWrite) -> Result<()> { - ::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: AsRef + DynRmcSerialize + RmcStructInstance {} - -trait RmcCastable { - // consumes box and returns a Box containing a Box with the requested Struct details - fn cast_to(self: Box, destination: &RmcStructInfo) -> Box; -} - -struct RmcStructInfo { - inheritors: Vec<&'static RmcStructInfo>, - name: &'static str, - deserialize_abstract: fn(&mut dyn Read) -> Box, +pub struct RmcStructInfo { + // this may never be locked after initialization + pub inheritors: RwLock>, + pub name: &'static str, } impl RmcStructInfo { - fn deserialze_abstract_as( - reader: &impl Read, - ) -> Box> { - 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; -}