use nexact server
All checks were successful
Build and Test / splatoon-testfire (push) Successful in 5m20s
Build and Test / fast-racing-neo (push) Successful in 5m20s
Build and Test / mario-tennis (push) Successful in 5m26s
Build and Test / splatoon (push) Successful in 5m27s
Build and Test / wii-u-chat (push) Successful in 5m30s
Build and Test / friends (push) Successful in 5m30s
Build and Test / super-mario-maker (push) Successful in 5m50s
Build and Test / minecraft-wiiu (push) Successful in 9m59s
Build and Test / wii-sports-club (push) Successful in 10m24s
Build and Test / puyopuyo (push) Successful in 10m24s
Build and Test / sonic-transformed (push) Successful in 10m45s

This commit is contained in:
red binder 2026-06-22 11:55:53 +02:00
commit db6589d361
5 changed files with 95 additions and 130 deletions

1
Cargo.lock generated
View file

@ -2741,6 +2741,7 @@ dependencies = [
"sqlx 0.8.6",
"thiserror",
"tokio",
"tonic",
"typenum",
"ureq",
"urlencoding",

View file

@ -38,6 +38,7 @@ futures = "0.3.32"
async-trait = "0.1.89"
ctor = "1.0.7"
nex-account = { version = "0.1.3", registry = "spbr" }
tonic = "0.14.6"
[dev-dependencies]
# criterion = "0.7.0"

View file

@ -5,8 +5,13 @@ use rnex_core::PID;
use std::array::TryFromSliceError;
use std::ops::Deref;
use std::{env, result};
use std::sync::LazyLock;
use thiserror::Error;
use tokio::task::{JoinError, spawn_blocking};
use nex_account::grpc::nex_account_service_client::NexAccountServiceClient;
use nex_account::grpc::Pid;
use tonic::transport::Channel;
static API_KEY: Lazy<String> = Lazy::new(|| {
let key = env::var("ACCOUNT_GQL_API_KEY").expect("no graphql ip specified");
@ -26,8 +31,10 @@ pub enum Error {
RequestError(#[from] ureq::Error),
#[error(transparent)]
Json(#[from] json::Error),
//#[error(transparent)]
//Status(#[from] tonic::Status),
#[error(transparent)]
Status(#[from] tonic::Status),
#[error(transparent)]
Transport(#[from] tonic::transport::Error),
#[error("invalid password size: {0}")]
PasswordConversion(#[from] TryFromSliceError),
#[error("something happened")]
@ -38,141 +45,97 @@ pub enum Error {
pub type Result<T> = result::Result<T, Error>;
pub struct Client; //(reqwest::Client);
static NEX_ACCOUNT_URL: LazyLock<String> =
LazyLock::new(|| env::var("NEX_ACCOUNT_ENDPOINT").expect("NEX_ACCOUNT_ENDPOINT not set"));
pub struct Client(NexAccountServiceClient<Channel>); //(reqwest::Client);
impl Client {
pub async fn new() -> Result<Self> {
//Ok(Self(reqwest::ClientBuilder::new().build()?))
Ok(Self)
}
async fn do_request(&self, request_data: JsonValue) -> Result<JsonValue> {
let request = ureq::post(CLIENT_URI.as_str())
.header("X-API-Key", API_KEY.deref())
.content_type("application/json");
let mut response = spawn_blocking(move || request.send(request_data.to_string())).await??;
let str_body = response.body_mut().read_to_string()?;
Ok(json::parse(&str_body)?)
/*
let mut request = reqwest::Request::new(Method::POST, Url::from_str(CLIENT_URI.as_str()).unwrap());
*(request.body_mut()) = Some(Body::from(request_data.to_string()));
request.headers_mut().insert("X-API-Key", HeaderValue::from_str(&API_KEY).unwrap());
request.headers_mut().insert("Content-Type", HeaderValue::from_str("application/json").unwrap());
let response = self.0.execute(request).await?;
Ok(json::parse(&response.text().await?)?)
*/
}
pub async fn get_nex_password(&mut self, pid: PID) -> Result<[u8; 16]> {
let req = self
.do_request(object! {
"query": r"query($pid: Int!){
userByPid(pid: $pid){
nexPassword
}
}",
"variables": {
"pid": pid
}
})
let client = NexAccountServiceClient::connect(NEX_ACCOUNT_URL.as_str())
.await?;
Ok(Self(client))
}
let Some(val) = req
.entries()
.find(|v| v.0 == "data")
.ok_or(SomethingHappened)?
.1
.entries()
.find(|v| v.0 == "userByPid")
.ok_or(SomethingHappened)?
.1
.entries()
.find(|v| v.0 == "nexPassword")
.ok_or(SomethingHappened)?
.1
.as_str()
else {
return Err(SomethingHappened);
};
pub async fn get_nex_key(&mut self, pid: PID) -> Result<[u8; 16]> {
let prekey = self.0.get_nex_key_by_pid(Pid{pid}).await?.into_inner();
Ok(val.as_bytes().try_into().map_err(|_| SomethingHappened)?)
let nexkey: [u8; 16] = prekey.key.try_into().map_err(|_| Error::SomethingHappened)?;
Ok(nexkey)
}
pub async fn get_user_level(&mut self, pid: PID) -> Result<i32> {
let req = self
.do_request(object! {
"query": r"query($pid: Int!){
userByPid(pid: $pid){
accountLevel
}
}",
"variables": {
"pid": pid
}
})
.await?;
// let req = self
// .do_request(object! {
// "query": r"query($pid: Int!){
// userByPid(pid: $pid){
// accountLevel
// }
// }",
// "variables": {
// "pid": pid
// }
// })
// .await?;
//
// let Some(val) = req
// .entries()
// .find(|v| v.0 == "data")
// .ok_or(SomethingHappened)?
// .1
// .entries()
// .find(|v| v.0 == "userByPid")
// .ok_or(SomethingHappened)?
// .1
// .entries()
// .find(|v| v.0 == "accountLevel")
// .ok_or(SomethingHappened)?
// .1
// .as_i32()
// else {
// return Err(SomethingHappened);
// };
let Some(val) = req
.entries()
.find(|v| v.0 == "data")
.ok_or(SomethingHappened)?
.1
.entries()
.find(|v| v.0 == "userByPid")
.ok_or(SomethingHappened)?
.1
.entries()
.find(|v| v.0 == "accountLevel")
.ok_or(SomethingHappened)?
.1
.as_i32()
else {
return Err(SomethingHappened);
};
Ok(val)
// everyone is tester until this is implemented
Ok(0)
}
pub async fn get_pid_from_token(&mut self, token: String) -> Result<PID> {
let req = self
.do_request(object! {
"query":
r"query($token: String!){
token(tokenData: $token){
pid
}
}",
"variables": {
"token": token
}
})
.await?;
// this breaks switch nex servers and should be fixed eventually
let Some(val) = req
.entries()
.find(|v| v.0 == "data")
.ok_or(SomethingHappened)?
.1
.entries()
.find(|v| v.0 == "token")
.ok_or(SomethingHappened)?
.1
.entries()
.find(|v| v.0 == "pid")
.ok_or(SomethingHappened)?
.1
.as_i32()
else {
return Err(SomethingHappened);
};
Ok(val)
}
// pub async fn get_pid_from_token(&mut self, token: String) -> Result<PID> {
// let req = self
// .do_request(object! {
// "query":
// r"query($token: String!){
// token(tokenData: $token){
// pid
// }
// }",
// "variables": {
// "token": token
// }
// })
// .await?;
// // this breaks switch nex servers and should be fixed eventually
// let Some(val) = req
// .entries()
// .find(|v| v.0 == "data")
// .ok_or(SomethingHappened)?
// .1
// .entries()
// .find(|v| v.0 == "token")
// .ok_or(SomethingHappened)?
// .1
// .entries()
// .find(|v| v.0 == "pid")
// .ok_or(SomethingHappened)?
// .1
// .as_i32()
// else {
// return Err(SomethingHappened);
// };
//
// Ok(val)
// }
/*pub async fn get_user_data(&mut self , pid: u32) -> Result<GetUserDataResponse>{
let req = Request::new(GetUserDataRequest{

View file

@ -28,13 +28,13 @@ pub const SESSION_KEY_LENGTH: usize = SessionLengthTy::USIZE;
type Md5Hmac = Hmac<md5::Md5>;
pub fn derive_key(pid: PID, password: &[u8]) -> [u8; 16] {
let iteration_count = 65000 + pid % 1024;
pub fn derive_key(pid: PID, key: &[u8]) -> [u8; 16] {
let iteration_count = pid % 1024;
// we do one iteration out here to ensure the key is always 16 bytes
let mut key: [u8; 16] = {
let mut md5 = Md5::new();
md5.update(password);
md5.update(key);
md5.finalize().try_into().unwrap()
};

View file

@ -83,7 +83,7 @@ async fn get_login_data_by_pid(pid: PID) -> Option<(PID, Box<[u8]>)> {
return None;
};
let Ok(passwd) = client.get_nex_password(pid).await else {
let Ok(passwd) = client.get_nex_key(pid).await else {
return None;
};
@ -128,7 +128,7 @@ impl AuthHandler {
return Err(ErrorCode::Core_Exception);
};
let Ok(passwd) = client.get_nex_password(pid).await else {
let Ok(passwd) = client.get_nex_key(pid).await else {
warn!("unable to get nex password for pid: {}:", pid);
return Err(ErrorCode::Core_Exception);
};