From fcc2f942368b04e84789b7824e82cc6ad2021fb4 Mon Sep 17 00:00:00 2001 From: Maple Nebel Date: Sun, 5 Jul 2026 22:26:36 +0200 Subject: [PATCH] nex-account support for nnas --- Cargo.lock | 89 +++++++++-- rnex-core/src/nex/friends_handler.rs | 224 +++++++++++++-------------- 2 files changed, 184 insertions(+), 129 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9ad2257..9946174 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,6 +8,17 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" +[[package]] +name = "aes" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1fc76eaeac4c9164506c466d4ffdd8ec9d0c5bf57ee97177c4d8eceb3a0e138" +dependencies = [ + "cipher 0.5.1", + "cpubits", + "cpufeatures 0.3.0", +] + [[package]] name = "aho-corasick" version = "1.1.4" @@ -719,7 +730,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ "crypto-common 0.1.7", - "inout", + "inout 0.1.4", +] + +[[package]] +name = "cipher" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e34d8227fe1ba289043aeb13792056ff80fd6de1a9f49137a5f499de8e8c78ea" +dependencies = [ + "crypto-common 0.2.1", + "inout 0.2.2", ] [[package]] @@ -803,6 +824,12 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "cpubits" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15b85f9c39137c3a891689859392b1bd49812121d0d61c9caf00d46ed5ce06ae" + [[package]] name = "cpufeatures" version = "0.2.17" @@ -1084,7 +1111,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -1579,10 +1606,11 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hybrid-array" -version = "0.4.11" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d46837a0ed51fe95bd3b05de33cd64a1ee88fc797477ca48446872504507c5" +checksum = "818356c5132c1fede50f837ca96afbe78ff42413047f4abb886217845e1b6c8c" dependencies = [ + "bytemuck", "typenum", ] @@ -1693,7 +1721,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2 0.6.3", + "socket2 0.5.10", "tokio", "tower-service", "tracing", @@ -1853,6 +1881,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "inout" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4250ce6452e92010fdf7268ccc5d14faa80bb12fc741938534c58f16804e03c7" +dependencies = [ + "hybrid-array", +] + [[package]] name = "ipnet" version = "2.12.0" @@ -2091,16 +2128,20 @@ checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" [[package]] name = "nex-account" -version = "0.2.1" +version = "0.2.3" source = "sparse+https://crates.spbr.net/api/v1/crates/" -checksum = "51215ae67dfb36017956435ee4935bd62d64b62a8dd716c4614e11c8c29f941e" +checksum = "2dd9e8b23a09bb5ab8aeb5f39ed5ed1861549e4294757c9be30eb281c4ad11d0" dependencies = [ + "base64", + "bytemuck", "hmac 0.13.0", "log", "md-5 0.11.0", "prost", "simplelog", + "spbr-common", "sqlx 0.9.0", + "thiserror", "tokio", "tonic", "tonic-prost", @@ -2568,9 +2609,9 @@ dependencies = [ [[package]] name = "rand" -version = "0.10.1" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" +checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" dependencies = [ "chacha20", "getrandom 0.4.2", @@ -2627,7 +2668,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f1256e23efe6097f27aa82d6ca6889361c001586ae0f6917cbad072f05eb275" dependencies = [ - "cipher", + "cipher 0.4.4", ] [[package]] @@ -2732,7 +2773,7 @@ dependencies = [ "nex-account", "once_cell", "paste", - "rand 0.10.1", + "rand 0.10.2", "rc4", "serde", "serde_json", @@ -2787,7 +2828,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -3128,6 +3169,22 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "spbr-common" +version = "0.1.0" +source = "sparse+https://crates.spbr.net/api/v1/crates/" +checksum = "04f4e4710d23bb730a95d00b853e304a4c1c9e86276792cd32a92057e9833452" +dependencies = [ + "aes", + "base64", + "bytemuck", + "hmac 0.13.0", + "hybrid-array", + "md-5 0.11.0", + "rand 0.10.2", + "thiserror", +] + [[package]] name = "spin" version = "0.9.8" @@ -3465,7 +3522,7 @@ dependencies = [ "log", "md-5 0.11.0", "memchr", - "rand 0.10.1", + "rand 0.10.2", "serde", "serde_json", "sha2 0.11.0", @@ -3595,10 +3652,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom 0.3.4", + "getrandom 0.4.2", "once_cell", "rustix", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -4237,7 +4294,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.48.0", ] [[package]] diff --git a/rnex-core/src/nex/friends_handler.rs b/rnex-core/src/nex/friends_handler.rs index 6980744..7686462 100644 --- a/rnex-core/src/nex/friends_handler.rs +++ b/rnex-core/src/nex/friends_handler.rs @@ -4,8 +4,9 @@ use std::ops::Deref; use std::sync::{Arc, atomic::AtomicU32}; use std::sync::{LazyLock, Weak}; -use base64::{engine::general_purpose, Engine as _}; -use bytemuck::{bytes_of, Pod, Zeroable}; +use base64::{Engine as _, engine::general_purpose}; +use bytemuck::{Pod, Zeroable, bytes_of}; +use hex::decode; use hmac::Mac; use log::info; use macros::rmc_struct; @@ -13,12 +14,12 @@ use rnex_core::rmc::protocols::account_management::{ AccountExtraInfo, AccountManagement, RawAccountManagement, RawAccountManagementInfo, RemoteAccountManagement, }; -use rnex_core::rmc::protocols::friends_wiiu::{ - FriendsWiiU, RawFriendsWiiU, RawFriendsWiiUInfo, RemoteFriendsWiiU, -}; use rnex_core::rmc::protocols::friends_3ds::{ Friends3DS, RawFriends3DS, RawFriends3DSInfo, RemoteFriends3DS, }; +use rnex_core::rmc::protocols::friends_wiiu::{ + FriendsWiiU, RawFriendsWiiU, RawFriendsWiiUInfo, RemoteFriendsWiiU, +}; use rnex_core::rmc::protocols::nintendo_notification::{ NintendoNotification, RawNintendoNotification, RawNintendoNotificationInfo, RemoteNintendoNotification, @@ -40,7 +41,6 @@ use rnex_core::{ }; use sqlx::query; use std::sync::atomic::Ordering::Relaxed; -use hex::decode; use tokio::spawn; use tokio::sync::RwLock; @@ -56,10 +56,13 @@ use rnex_core::rmc::structures::data::Data; use crate::executables::common::get_db; -use nex_account::derive_pid_hmac; +use crate::rmc::protocols::friends_3ds::{ + FriendComment, FriendMii, FriendMiiList, FriendPersistentInfo, FriendPicture, FriendPresence, + FriendRelationship, Mii, MiiList, MyProfile, NintendoPresence, PlayedGame, +}; use nex_account::grpc::ActCreateInfo; use nex_account::grpc::nex_account_service_client::NexAccountServiceClient; -use crate::rmc::protocols::friends_3ds::{FriendComment, FriendMii, FriendMiiList, FriendPersistentInfo, FriendPicture, FriendPresence, FriendRelationship, Mii, MiiList, MyProfile, NintendoPresence, PlayedGame}; +use nex_account::{derive_pid_hmac, grpc_client}; define_rmc_proto!( proto FriendsUser{ @@ -96,7 +99,6 @@ pub struct NascToken { pub pwd_hash: [u8; 4], } - #[rmc_struct(FriendsUser)] pub struct FriendsUser { pub fm: Arc, @@ -143,26 +145,29 @@ impl Friends3DS for FriendsUser { Ok(()) } - async fn update_preference(&self, show_online_status: bool, show_current_title: bool, block_friend_requests: bool) -> Result<(), ErrorCode> { + async fn update_preference( + &self, + show_online_status: bool, + show_current_title: bool, + block_friend_requests: bool, + ) -> Result<(), ErrorCode> { // stubbed Ok(()) } - async fn get_friend_mii(&self, friends: Vec) -> Result, ErrorCode> { + async fn get_friend_mii( + &self, + friends: Vec, + ) -> Result, ErrorCode> { // sorry for the copying pretendo but i don't have a mii on hand rn let data: Vec = vec![ - 0x03, 0x00, 0x00, 0x40, 0xE9, 0x55, 0xA2, 0x09, - 0xE7, 0xC7, 0x41, 0x82, 0xD9, 0x7D, 0x0B, 0x2D, - 0x03, 0xB3, 0xB8, 0x8D, 0x27, 0xD9, 0x00, 0x00, - 0x01, 0x40, 0x62, 0x00, 0x65, 0x00, 0x6C, 0x00, - 0x6C, 0x00, 0x61, 0x00, 0x00, 0x00, 0x45, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x40, - 0x12, 0x00, 0x81, 0x01, 0x04, 0x68, 0x43, 0x18, - 0x20, 0x34, 0x46, 0x14, 0x81, 0x12, 0x17, 0x68, - 0x0D, 0x00, 0x00, 0x29, 0x03, 0x52, 0x48, 0x50, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0x86, + 0x03, 0x00, 0x00, 0x40, 0xE9, 0x55, 0xA2, 0x09, 0xE7, 0xC7, 0x41, 0x82, 0xD9, 0x7D, + 0x0B, 0x2D, 0x03, 0xB3, 0xB8, 0x8D, 0x27, 0xD9, 0x00, 0x00, 0x01, 0x40, 0x62, 0x00, + 0x65, 0x00, 0x6C, 0x00, 0x6C, 0x00, 0x61, 0x00, 0x00, 0x00, 0x45, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x40, 0x40, 0x12, 0x00, 0x81, 0x01, 0x04, 0x68, 0x43, 0x18, + 0x20, 0x34, 0x46, 0x14, 0x81, 0x12, 0x17, 0x68, 0x0D, 0x00, 0x00, 0x29, 0x03, 0x52, + 0x48, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0x86, ]; let dummymii = FriendMii { @@ -181,19 +186,33 @@ impl Friends3DS for FriendsUser { Ok(vec![dummymii]) } - async fn get_friend_mii_list(&self, friends: Vec) -> Result, ErrorCode> { + async fn get_friend_mii_list( + &self, + friends: Vec, + ) -> Result, ErrorCode> { Err(ErrorCode::Core_NotImplemented) } - async fn is_active_game(&self, unk: Vec, game_key: crate::rmc::protocols::friends_3ds::GameKey) -> Result, ErrorCode> { + async fn is_active_game( + &self, + unk: Vec, + game_key: crate::rmc::protocols::friends_3ds::GameKey, + ) -> Result, ErrorCode> { Err(ErrorCode::Core_NotImplemented) } - async fn get_principal_id_by_local_friend_code(&self, unk1: u64, unk2: Vec) -> Result, ErrorCode> { + async fn get_principal_id_by_local_friend_code( + &self, + unk1: u64, + unk2: Vec, + ) -> Result, ErrorCode> { Err(ErrorCode::Core_NotImplemented) } - async fn get_friend_relationships(&self, unk2: Vec) -> Result, ErrorCode> { + async fn get_friend_relationships( + &self, + unk2: Vec, + ) -> Result, ErrorCode> { let dummy = FriendRelationship { data: Data {}, pid: 69, @@ -208,7 +227,11 @@ impl Friends3DS for FriendsUser { Err(ErrorCode::Core_NotImplemented) } - async fn add_friend_by_lst_pid(&self, unk: u64, pid: Vec) -> Result, ErrorCode> { + async fn add_friend_by_lst_pid( + &self, + unk: u64, + pid: Vec, + ) -> Result, ErrorCode> { Err(ErrorCode::Core_NotImplemented) } @@ -235,7 +258,12 @@ impl Friends3DS for FriendsUser { Ok(()) } - async fn sync_friend(&self, unk1: u64, unk2: Vec, unk3: Vec) -> Result, ErrorCode> { + async fn sync_friend( + &self, + unk1: u64, + unk2: Vec, + unk3: Vec, + ) -> Result, ErrorCode> { log::info!("params: {:?}, {:?}, {:?}", unk1, unk2, unk3); let dummy = FriendRelationship { @@ -248,11 +276,18 @@ impl Friends3DS for FriendsUser { Ok(vec![dummy]) } - async fn update_presence(&self, nintendo_presence: NintendoPresence, unk: bool) -> Result<(), ErrorCode> { + async fn update_presence( + &self, + nintendo_presence: NintendoPresence, + unk: bool, + ) -> Result<(), ErrorCode> { Ok(()) } - async fn update_favorite_game_key(&self, game_key: rnex_core::rmc::protocols::friends_3ds::GameKey) -> Result<(), ErrorCode> { + async fn update_favorite_game_key( + &self, + game_key: rnex_core::rmc::protocols::friends_3ds::GameKey, + ) -> Result<(), ErrorCode> { log::info!("favorite game key: {:?}", game_key); Ok(()) @@ -294,7 +329,10 @@ impl Friends3DS for FriendsUser { Ok(vec![presence]) } - async fn get_friend_comment(&self, unk: Vec) -> Result, ErrorCode> { + async fn get_friend_comment( + &self, + unk: Vec, + ) -> Result, ErrorCode> { Err(ErrorCode::Core_NotImplemented) } @@ -302,7 +340,10 @@ impl Friends3DS for FriendsUser { Err(ErrorCode::Core_NotImplemented) } - async fn get_friend_persistent_info(&self, unk: Vec) -> Result, ErrorCode> { + async fn get_friend_persistent_info( + &self, + unk: Vec, + ) -> Result, ErrorCode> { let dummypersistentinfo = FriendPersistentInfo { data: Data {}, pid: 69, @@ -508,29 +549,6 @@ impl Secure for FriendsGuest { } } - -// this is probably a horrible way to do this. it's 1:27am. fuck off please i'll fix it later. -fn decode_token(encoded_str: &str) -> Result { - // i dont like the use of replace here as this will reallocate the entire string - // im gonna keep it though as long as noone comes up with a good alternative for this - // without complicating the code/making it far less readable/unnescesarily long - let standard_b64 = encoded_str.replace('.', "+").replace('-', "/").replace('*', "="); - - let standard = general_purpose::STANDARD.decode(standard_b64).map_err(|_| "failed to convert")?; - - let bytes = general_purpose::STANDARD - .decode(standard) - .map_err(|_| "failed to decode Base64 string")?; - - if bytes.len() != std::mem::size_of::() { - return Err("decoded byte length mismatch"); - } - - let token = bytemuck::from_bytes::(&bytes); - - Ok(*token) -} - impl AccountManagement for FriendsGuest { async fn nintendo_create_account( &self, @@ -542,70 +560,50 @@ impl AccountManagement for FriendsGuest { ) -> Result<(PID, String), ErrorCode> { println!("{}, {}, {}, {}", principal_name, key, groups, email); - if let Ok(data) = auth_data.try_get_as::() { - let pid = data.nna_info.principal_basic_info.pid; - info!("create account via standard data: {}", pid); + let nex_token = if let Ok(extra_info) = auth_data.try_get_as::() { + extra_info.nex_token + } else if let Ok(data) = auth_data.try_get_as::() { + data.nex_token + } else { + return Err(ErrorCode::Authentication_InvalidParam); + }; + let (pid, nex_key) = nex_account::decode_nexact_token(&nex_token).map_err(|e| { + log::error!("failed to decode token: {}", e); + log::info!("{:?}", nex_token); + ErrorCode::Authentication_InvalidParam + })?; - let nexkey: [u8; 16] = hex::decode(key) - .map_err(|_| ErrorCode::Authentication_InvalidParam)? - .as_slice() - .try_into() - .map_err(|_| ErrorCode::Authentication_InvalidParam)?; + //let mac = derive_pid_hmac(data.nna_info.principal_basic_info.pid, &nexkey); - let mac = derive_pid_hmac(data.nna_info.principal_basic_info.pid, &nexkey); + let mut client = grpc_client().await.map_err(|e| { + eprintln!("error occurred: {:?}", e); + ErrorCode::Core_Unknown + })?; - let hex_str = hex::encode(mac); - return Ok((pid, hex_str)); + let new_account: ActCreateInfo = ActCreateInfo { + principal_name, + key: nex_key.into(), + email, + pid, + }; + + let nexkey = client + .create_new_sequential_or_update_and_get_account(new_account) + .await + .map_err(|e| ErrorCode::Core_Unknown)? + .into_inner(); + + if nexkey.key.len() != 16 { + log::error!("nex key was not 16 bytes long"); + return Err(ErrorCode::Authentication_InvalidParam); } - if let Ok(extra_info) = auth_data.try_get_as::() { - info!("create account via extra info"); + let nexkeyarray: [u8; 16] = nexkey.key.try_into().expect("how...?"); - let decoded_token = decode_token(&*extra_info.nex_token).map_err(|e| { - log::error!("failed to decode token: {}", e); - log::info!("{:?}", extra_info.nex_token); - ErrorCode::Authentication_InvalidParam - })?; + let mac = derive_pid_hmac(pid, &nexkeyarray); - log::info!("decoded token: {:?}", decoded_token); + let hex_str = hex::encode(mac); - let mut client = NexAccountServiceClient::connect(NEX_ACCOUNT_URL.as_str()) - .await - .map_err(|e| { - eprintln!("error occurred: {:?}", e); - ErrorCode::Core_Unknown - })?; - - let pid = decoded_token.pid; - - let new_account: ActCreateInfo = ActCreateInfo { - principal_name, - key: vec![], - email, - pid, - }; - - let nexkey = client - .create_new_sequential_or_update_and_get_account(new_account) - .await - .map_err(|e| {ErrorCode::Core_Unknown})? - .into_inner(); - - if nexkey.key.len() != 16 { - log::error!("nex key was not 16 bytes long"); - return Err(ErrorCode::Authentication_InvalidParam); - } - - let nexkeyarray: [u8; 16] = nexkey.key.try_into() - .expect("how...?"); - - let mac = derive_pid_hmac(pid, &nexkeyarray); - - let hex_str = hex::encode(mac); - - return Ok((pid, hex_str)); - } - - Err(ErrorCode::Authentication_InvalidParam) + return Ok((pid, hex_str)); } }