progress
Some checks failed
Build and Test / super-mario-maker (push) Failing after 10m30s
Build and Test / fast-racing-neo (push) Failing after 11m40s
Build and Test / friends (push) Failing after 11m56s
Build and Test / wii-sports-club (push) Failing after 12m9s
Build and Test / splatoon (push) Failing after 12m14s
Build and Test / puyopuyo (push) Failing after 12m23s
Build and Test / wii-u-chat (push) Failing after 12m23s
Build and Test / mario-tennis (push) Failing after 12m23s
Build and Test / sonic-transformed (push) Failing after 12m23s
Build and Test / splatoon-testfire (push) Failing after 12m30s
Build and Test / minecraft-wiiu (push) Failing after 12m37s
Some checks failed
Build and Test / super-mario-maker (push) Failing after 10m30s
Build and Test / fast-racing-neo (push) Failing after 11m40s
Build and Test / friends (push) Failing after 11m56s
Build and Test / wii-sports-club (push) Failing after 12m9s
Build and Test / splatoon (push) Failing after 12m14s
Build and Test / puyopuyo (push) Failing after 12m23s
Build and Test / wii-u-chat (push) Failing after 12m23s
Build and Test / mario-tennis (push) Failing after 12m23s
Build and Test / sonic-transformed (push) Failing after 12m23s
Build and Test / splatoon-testfire (push) Failing after 12m30s
Build and Test / minecraft-wiiu (push) Failing after 12m37s
This commit is contained in:
parent
7dd502aa7a
commit
4ff0a5efcc
187 changed files with 4526 additions and 5132 deletions
17
rnex-util/Cargo.toml
Normal file
17
rnex-util/Cargo.toml
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
[package]
|
||||
name = "rnex-util"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
tokio = { version = "1.52.3", features = ["io-util", "macros", "rt", "sync"] }
|
||||
tracing = "0.1.44"
|
||||
chrono = "0.4.39"
|
||||
bytemuck = { version = "1.25.0", features = ["derive"] }
|
||||
md-5 = "0.11.0"
|
||||
|
||||
[features]
|
||||
nx = []
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
47
rnex-util/src/account.rs
Normal file
47
rnex-util/src/account.rs
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
use md5::{Digest, Md5};
|
||||
|
||||
use crate::PID;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Account {
|
||||
pub pid: PID,
|
||||
pub username: String,
|
||||
pub nex_key: [u8; 16],
|
||||
}
|
||||
|
||||
impl Account {
|
||||
pub fn new(pid: PID, username: &str, passwd: &str) -> Self {
|
||||
let iteration_count = 65000 + 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(passwd);
|
||||
md5.finalize().into()
|
||||
};
|
||||
|
||||
for _ in 1..iteration_count {
|
||||
let mut md5 = Md5::new();
|
||||
md5.update(key);
|
||||
key = md5.finalize().into();
|
||||
}
|
||||
|
||||
Self {
|
||||
nex_key: key,
|
||||
username: username.into(),
|
||||
pid,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_raw_key(pid: PID, username: &str, nex_key: [u8; 16]) -> Self {
|
||||
Self {
|
||||
username: username.into(),
|
||||
pid,
|
||||
nex_key,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_login_data(&self) -> (PID, [u8; 16]) {
|
||||
(self.pid, self.nex_key)
|
||||
}
|
||||
}
|
||||
102
rnex-util/src/date_time.rs
Normal file
102
rnex-util/src/date_time.rs
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
use std::fmt::Display;
|
||||
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
use chrono::{Datelike, NaiveDate, Timelike, Utc};
|
||||
use tracing::error;
|
||||
|
||||
#[derive(Pod, Zeroable, Copy, Clone, Debug, Eq, PartialEq, Default)]
|
||||
#[repr(transparent)]
|
||||
pub struct DateTime(pub u64);
|
||||
|
||||
impl Display for DateTime {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}.{}.{} {}:{}:{}",
|
||||
self.get_year(),
|
||||
self.get_month(),
|
||||
self.get_days(),
|
||||
self.get_hours(),
|
||||
self.get_minutes(),
|
||||
self.get_seconds()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl DateTime {
|
||||
// this is the time which smm returned as the expriy date, we use it as a
|
||||
// date so far into the future that it might as well just be never more generally
|
||||
pub const PRACTICALLY_NEVER: Self = Self::new(0, 0, 0, 31, 12, 9999);
|
||||
pub fn from_naive(dt: chrono::NaiveDateTime) -> Self {
|
||||
use chrono::Datelike;
|
||||
use chrono::Timelike;
|
||||
Self::new(
|
||||
dt.second() as u64,
|
||||
dt.minute() as u64,
|
||||
dt.hour() as u64,
|
||||
dt.day() as u64,
|
||||
dt.month() as u64,
|
||||
dt.year() as u64,
|
||||
)
|
||||
}
|
||||
|
||||
pub const fn new(second: u64, minute: u64, hour: u64, day: u64, month: u64, year: u64) -> Self {
|
||||
Self(second | (minute << 6) | (hour << 12) | (day << 17) | (month << 22) | (year << 26))
|
||||
}
|
||||
|
||||
pub fn now() -> Self {
|
||||
let now = chrono::Utc::now();
|
||||
Self::new(
|
||||
now.second() as u64,
|
||||
now.minute() as u64,
|
||||
now.hour() as u64,
|
||||
now.day() as u64,
|
||||
now.month() as u64,
|
||||
now.year() as u64,
|
||||
)
|
||||
}
|
||||
|
||||
pub const fn get_seconds(&self) -> u8 {
|
||||
(self.0 & 0b11_1111) as u8
|
||||
}
|
||||
|
||||
pub const fn get_minutes(&self) -> u8 {
|
||||
((self.0 >> 6) & 0b11_1111) as u8
|
||||
}
|
||||
pub const fn get_hours(&self) -> u8 {
|
||||
((self.0 >> 12) & 0b1_1111) as u8
|
||||
}
|
||||
pub const fn get_days(&self) -> u8 {
|
||||
((self.0 >> 17) & 0b11_1111) as u8
|
||||
}
|
||||
pub const fn get_month(&self) -> u8 {
|
||||
((self.0 >> 22) & 0b1111) as u8
|
||||
}
|
||||
pub const fn get_year(&self) -> u64 {
|
||||
(self.0 >> 26) & 0xFFFF_FFFF
|
||||
}
|
||||
pub fn to_regular_time(&self) -> chrono::DateTime<Utc> {
|
||||
let date = match NaiveDate::from_ymd_opt(
|
||||
self.get_year() as i32,
|
||||
self.get_month() as u32,
|
||||
self.get_days() as u32,
|
||||
) {
|
||||
Some(v) => v,
|
||||
None => {
|
||||
error!("invalid datetime...: {}", self);
|
||||
Default::default()
|
||||
}
|
||||
};
|
||||
|
||||
chrono::NaiveDateTime::new(
|
||||
date,
|
||||
chrono::NaiveTime::from_hms_opt(
|
||||
self.get_hours() as u32,
|
||||
self.get_minutes() as u32,
|
||||
self.get_seconds() as u32,
|
||||
)
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
.and_utc()
|
||||
}
|
||||
}
|
||||
182
rnex-util/src/lib.rs
Normal file
182
rnex-util/src/lib.rs
Normal file
|
|
@ -0,0 +1,182 @@
|
|||
#![allow(async_fn_in_trait)]
|
||||
pub use tracing;
|
||||
pub mod account;
|
||||
pub mod date_time;
|
||||
pub mod result;
|
||||
pub mod station_url;
|
||||
|
||||
use std::ops::Deref;
|
||||
use std::sync::{Arc, Weak};
|
||||
use std::vec;
|
||||
use tokio::io::{self, AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
|
||||
use tokio::sync::Notify;
|
||||
use tokio::sync::mpsc::{Receiver, Sender, channel};
|
||||
use tokio::task;
|
||||
use tracing::{error, info};
|
||||
|
||||
#[cfg(feature = "nx")]
|
||||
pub type PID = i64;
|
||||
#[cfg(not(feature = "nx"))]
|
||||
pub type PID = i32;
|
||||
|
||||
pub trait UnitPacketRead: AsyncRead + Unpin {
|
||||
async fn read_buffer(&mut self) -> Result<Vec<u8>, io::Error> {
|
||||
let mut len_raw: [u8; _] = [0; size_of::<usize>()];
|
||||
|
||||
self.read_exact(&mut len_raw).await?;
|
||||
|
||||
let len = usize::from_le_bytes(len_raw);
|
||||
|
||||
let mut vec = vec![0u8; len as _];
|
||||
|
||||
self.read_exact(&mut vec).await?;
|
||||
|
||||
Ok(vec)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsyncRead + Unpin> UnitPacketRead for T {}
|
||||
pub trait UnitPacketWrite: AsyncWrite + Unpin {
|
||||
async fn send_buffer(&mut self, data: &[u8]) -> Result<(), io::Error> {
|
||||
let len_data = data.len().to_le_bytes();
|
||||
self.write_all(&len_data[..]).await?;
|
||||
self.write_all(data).await?;
|
||||
|
||||
self.flush().await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsyncWrite + Unpin> UnitPacketWrite for T {}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct SendingBufferConnection(Sender<Vec<u8>>, Arc<Notify>);
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SplittableBufferConnection(SendingBufferConnection, Receiver<Vec<u8>>);
|
||||
|
||||
impl AsRef<SendingBufferConnection> for SplittableBufferConnection {
|
||||
fn as_ref(&self) -> &SendingBufferConnection {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for SplittableBufferConnection {
|
||||
type Target = SendingBufferConnection;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Send + Unpin + AsyncWrite + AsyncRead + 'static> From<T> for SplittableBufferConnection {
|
||||
fn from(value: T) -> Self {
|
||||
Self::new(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl SplittableBufferConnection {
|
||||
fn new<T: Send + Unpin + AsyncWrite + AsyncRead + 'static>(stream: T) -> Self {
|
||||
let (outside_send, inside_recv) = channel::<Vec<u8>>(10);
|
||||
let (inside_send, outside_recv) = channel::<Vec<u8>>(10);
|
||||
|
||||
let notify = Arc::new(Notify::new());
|
||||
|
||||
{
|
||||
let notify = notify.clone();
|
||||
task::spawn(async move {
|
||||
let sender = inside_send;
|
||||
let mut recver = inside_recv;
|
||||
let mut stream = stream;
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
data = recver.recv() => {
|
||||
let Some(data) = data else {
|
||||
break;
|
||||
};
|
||||
|
||||
if let Err(e) = stream.send_buffer(&data[..]).await{
|
||||
error!("error sending data to backend: {e}");
|
||||
break;
|
||||
}
|
||||
},
|
||||
data = stream.read_buffer() => {
|
||||
let data = match data{
|
||||
Ok(d) => d,
|
||||
Err(e) => {
|
||||
error!("error reveiving data from backend: {e}");
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
if let Err(e) = sender.send(data).await{
|
||||
error!("a send error occurred {e}");
|
||||
return;
|
||||
}
|
||||
},
|
||||
() = notify.notified() => {
|
||||
info!("shutting down connection");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Err(e) = stream.shutdown().await {
|
||||
error!("failed to shut down stream: {e}");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Self(SendingBufferConnection(outside_send, notify), outside_recv)
|
||||
}
|
||||
}
|
||||
|
||||
impl SendingBufferConnection {
|
||||
pub async fn send(&self, buffer: Vec<u8>) -> Option<()> {
|
||||
self.0.send(buffer).await.ok()
|
||||
}
|
||||
#[must_use]
|
||||
pub fn is_alive(&self) -> bool {
|
||||
!self.0.is_closed()
|
||||
}
|
||||
pub async fn disconnect(&self) {
|
||||
while !self.0.is_closed() {
|
||||
self.1.notify_waiters();
|
||||
tokio::task::yield_now().await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SplittableBufferConnection {
|
||||
pub async fn recv(&mut self) -> Option<Vec<u8>> {
|
||||
self.1.recv().await
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn duplicate_sender(&self) -> SendingBufferConnection {
|
||||
self.0.clone()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WeakVec<T>(Vec<Weak<T>>);
|
||||
|
||||
impl<T> WeakVec<T> {
|
||||
pub fn new() -> Self {
|
||||
Self(vec![])
|
||||
}
|
||||
|
||||
pub fn from_vec(vec: Vec<Weak<T>>) -> Self {
|
||||
Self(vec)
|
||||
}
|
||||
|
||||
pub fn push(&mut self, val: Weak<T>) {
|
||||
self.0.retain(|v| v.upgrade().is_some());
|
||||
|
||||
self.0.push(val);
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = Arc<T>> {
|
||||
self.0.iter().filter_map(Weak::upgrade)
|
||||
}
|
||||
}
|
||||
23
rnex-util/src/result.rs
Normal file
23
rnex-util/src/result.rs
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
use std::error::Error;
|
||||
use tracing::error;
|
||||
|
||||
pub trait ResultExtension {
|
||||
type Output;
|
||||
|
||||
fn display_err_or_some(self) -> Option<Self::Output>;
|
||||
}
|
||||
|
||||
impl<T, U: Error> ResultExtension for Result<T, U> {
|
||||
type Output = T;
|
||||
|
||||
fn display_err_or_some(self) -> Option<Self::Output> {
|
||||
match self {
|
||||
Ok(v) => Some(v),
|
||||
Err(e) => {
|
||||
error!("{e}");
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
160
rnex-util/src/station_url.rs
Normal file
160
rnex-util/src/station_url.rs
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
use std::{
|
||||
fmt::{Debug, Display, Formatter},
|
||||
net::IpAddr,
|
||||
};
|
||||
|
||||
use tracing::error;
|
||||
|
||||
use crate::PID;
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Type {
|
||||
UDP,
|
||||
PRUDP,
|
||||
PRUDPS,
|
||||
}
|
||||
|
||||
pub mod nat_types {
|
||||
pub const BEHIND_NAT: u8 = 1;
|
||||
pub const PUBLIC: u8 = 2;
|
||||
}
|
||||
|
||||
#[derive(Clone, Eq, PartialEq)]
|
||||
pub enum UrlOptions {
|
||||
Address(IpAddr),
|
||||
Port(u16),
|
||||
StreamType(u8),
|
||||
StreamID(u8),
|
||||
ConnectionID(u32),
|
||||
ProbeInit(u32),
|
||||
PrincipalID(PID),
|
||||
NatType(u8),
|
||||
NatMapping(u8),
|
||||
NatFiltering(u8),
|
||||
UPNP(u8),
|
||||
RVConnectionID(u32),
|
||||
Platform(u8),
|
||||
PMP(u8),
|
||||
PID(u32),
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
pub struct StationUrl {
|
||||
pub url_type: Type,
|
||||
pub options: Vec<UrlOptions>,
|
||||
}
|
||||
|
||||
impl StationUrl {
|
||||
pub fn read_options(options: &str) -> Option<Vec<UrlOptions>> {
|
||||
let mut options_out = Vec::new();
|
||||
|
||||
for option in options.split(';') {
|
||||
if option == "" {
|
||||
continue;
|
||||
}
|
||||
let mut option_parts = option.split('=');
|
||||
let option_name = option_parts.next()?.to_ascii_lowercase();
|
||||
let option_value = option_parts.next()?;
|
||||
|
||||
use UrlOptions::*;
|
||||
|
||||
match option_name.as_ref() {
|
||||
"address" => options_out.push(Address(option_value.parse().ok()?)),
|
||||
"port" => options_out.push(Port(option_value.parse().ok()?)),
|
||||
"natf" => options_out.push(NatFiltering(option_value.parse().ok()?)),
|
||||
"natm" => options_out.push(NatMapping(option_value.parse().ok()?)),
|
||||
"sid" => options_out.push(StreamID(option_value.parse().ok()?)),
|
||||
"upnp" => options_out.push(UPNP(option_value.parse().ok()?)),
|
||||
"type" => options_out.push(NatType(option_value.parse().ok()?)),
|
||||
"stream" => options_out.push(StreamType(option_value.parse().ok()?)),
|
||||
"RVCID" => options_out.push(RVConnectionID(option_value.parse().ok()?)),
|
||||
"rvcid" => options_out.push(RVConnectionID(option_value.parse().ok()?)),
|
||||
"CID" => options_out.push(ConnectionID(option_value.parse().ok()?)),
|
||||
"cid" => options_out.push(ConnectionID(option_value.parse().ok()?)),
|
||||
"pl" => options_out.push(Platform(option_value.parse().ok()?)),
|
||||
"pmp" => options_out.push(PMP(option_value.parse().ok()?)),
|
||||
"pid" => options_out.push(PID(option_value.parse().ok()?)),
|
||||
"PID" => options_out.push(PID(option_value.parse().ok()?)),
|
||||
"probeinit" => options_out.push(ProbeInit(option_value.parse().ok()?)),
|
||||
_ => {
|
||||
error!("unimplemented option type, skipping: {}", option_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some(options_out)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&str> for StationUrl {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: &str) -> Result<Self, ()> {
|
||||
let (url_type, options) = value.split_at(value.find(":/").ok_or(())?);
|
||||
|
||||
let options = &options[2..];
|
||||
|
||||
use Type::*;
|
||||
|
||||
let url_type = match url_type {
|
||||
"udp" => UDP,
|
||||
"prudp" => PRUDP,
|
||||
"prudps" => PRUDPS,
|
||||
_ => return Err(()),
|
||||
};
|
||||
|
||||
let options = Self::read_options(options).ok_or(())?;
|
||||
|
||||
Ok(Self { url_type, options })
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for StationUrl {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
use Type::*;
|
||||
let url_type_str = match self.url_type {
|
||||
UDP => "udp:/",
|
||||
PRUDP => "prudp:/",
|
||||
PRUDPS => "prudps:/",
|
||||
};
|
||||
write!(f, "{}", url_type_str)?;
|
||||
|
||||
use UrlOptions::*;
|
||||
for option in &self.options {
|
||||
match option {
|
||||
Address(v) => write!(f, "address={}", v)?,
|
||||
Port(v) => write!(f, "port={}", v)?,
|
||||
StreamType(v) => write!(f, "stream={}", v)?,
|
||||
StreamID(v) => write!(f, "sid={}", v)?,
|
||||
ConnectionID(v) => write!(f, "CID={}", v)?,
|
||||
PrincipalID(v) => write!(f, "PID={}", v)?,
|
||||
NatType(v) => write!(f, "type={}", v)?,
|
||||
NatMapping(v) => write!(f, "natm={}", v)?,
|
||||
NatFiltering(v) => write!(f, "natf={}", v)?,
|
||||
UPNP(v) => write!(f, "upnp={}", v)?,
|
||||
RVConnectionID(v) => write!(f, "RVCID={}", v)?,
|
||||
Platform(v) => write!(f, "pl={}", v)?,
|
||||
PMP(v) => write!(f, "pmp={}", v)?,
|
||||
PID(v) => write!(f, "PID={}", v)?,
|
||||
ProbeInit(v) => write!(f, "probeinit={}", v)?,
|
||||
}
|
||||
write!(f, ";")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Into<String> for &'a StationUrl {
|
||||
fn into(self) -> String {
|
||||
let url = self.to_string();
|
||||
|
||||
url[0..url.len() - 1].into()
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for StationUrl {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
let str: String = self.into();
|
||||
f.write_str(&str)
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue