nex-account support for nnas
All checks were successful
Build and Test / puyopuyo (push) Successful in 33m7s
Build and Test / wii-sports-club (push) Successful in 33m21s
Build and Test / splatoon (push) Successful in 33m59s
Build and Test / splatoon-testfire (push) Successful in 34m1s
Build and Test / fast-racing-neo (push) Successful in 34m24s
Build and Test / mario-tennis (push) Successful in 34m26s
Build and Test / wii-u-chat (push) Successful in 34m54s
Build and Test / sonic-transformed (push) Successful in 35m16s
Build and Test / minecraft-wiiu (push) Successful in 35m18s
Build and Test / friends (push) Successful in 35m35s
Build and Test / super-mario-maker (push) Successful in 38m8s

This commit is contained in:
Maple Nebel 2026-07-05 22:26:36 +02:00
commit fcc2f94236
2 changed files with 185 additions and 130 deletions

89
Cargo.lock generated
View file

@ -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]]

View file

@ -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<FriendsManager>,
@ -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<crate::rmc::protocols::friends_3ds::FriendInfo>) -> Result<Vec<FriendMii>, ErrorCode> {
async fn get_friend_mii(
&self,
friends: Vec<crate::rmc::protocols::friends_3ds::FriendInfo>,
) -> Result<Vec<FriendMii>, ErrorCode> {
// sorry for the copying pretendo but i don't have a mii on hand rn
let data: Vec<u8> = 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<crate::rmc::protocols::friends_3ds::FriendInfo>) -> Result<Vec<FriendMiiList>, ErrorCode> {
async fn get_friend_mii_list(
&self,
friends: Vec<crate::rmc::protocols::friends_3ds::FriendInfo>,
) -> Result<Vec<FriendMiiList>, ErrorCode> {
Err(ErrorCode::Core_NotImplemented)
}
async fn is_active_game(&self, unk: Vec<u32>, game_key: crate::rmc::protocols::friends_3ds::GameKey) -> Result<Vec<u32>, ErrorCode> {
async fn is_active_game(
&self,
unk: Vec<u32>,
game_key: crate::rmc::protocols::friends_3ds::GameKey,
) -> Result<Vec<u32>, ErrorCode> {
Err(ErrorCode::Core_NotImplemented)
}
async fn get_principal_id_by_local_friend_code(&self, unk1: u64, unk2: Vec<u64>) -> Result<Vec<FriendRelationship>, ErrorCode> {
async fn get_principal_id_by_local_friend_code(
&self,
unk1: u64,
unk2: Vec<u64>,
) -> Result<Vec<FriendRelationship>, ErrorCode> {
Err(ErrorCode::Core_NotImplemented)
}
async fn get_friend_relationships(&self, unk2: Vec<u32>) -> Result<Vec<FriendRelationship>, ErrorCode> {
async fn get_friend_relationships(
&self,
unk2: Vec<u32>,
) -> Result<Vec<FriendRelationship>, 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<PID>) -> Result<Vec<FriendRelationship>, ErrorCode> {
async fn add_friend_by_lst_pid(
&self,
unk: u64,
pid: Vec<PID>,
) -> Result<Vec<FriendRelationship>, ErrorCode> {
Err(ErrorCode::Core_NotImplemented)
}
@ -235,7 +258,12 @@ impl Friends3DS for FriendsUser {
Ok(())
}
async fn sync_friend(&self, unk1: u64, unk2: Vec<u32>, unk3: Vec<u64>) -> Result<Vec<FriendRelationship>, ErrorCode> {
async fn sync_friend(
&self,
unk1: u64,
unk2: Vec<u32>,
unk3: Vec<u64>,
) -> Result<Vec<FriendRelationship>, 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<crate::rmc::protocols::friends_3ds::FriendInfo>) -> Result<Vec<FriendComment>, ErrorCode> {
async fn get_friend_comment(
&self,
unk: Vec<crate::rmc::protocols::friends_3ds::FriendInfo>,
) -> Result<Vec<FriendComment>, 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<u32>) -> Result<Vec<FriendPersistentInfo>, ErrorCode> {
async fn get_friend_persistent_info(
&self,
unk: Vec<u32>,
) -> Result<Vec<FriendPersistentInfo>, 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<NascToken, &'static str> {
// 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::<NascToken>() {
return Err("decoded byte length mismatch");
}
let token = bytemuck::from_bytes::<NascToken>(&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::<NintendoCreateAccountData>() {
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::<AccountExtraInfo>() {
extra_info.nex_token
} else if let Ok(data) = auth_data.try_get_as::<NintendoCreateAccountData>() {
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::<AccountExtraInfo>() {
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));
}
}