fix port binding

This commit is contained in:
Maple 2026-03-24 15:48:56 +01:00
commit 785341e883
43 changed files with 1543 additions and 431 deletions

18
prudplite/Cargo.toml Normal file
View file

@ -0,0 +1,18 @@
[package]
name = "prudplite"
version = "0.1.0"
edition = "2024"
[features]
nx = []
v4-3-11 = []
[dependencies]
rnex-core = { path = "../rnex-core", version = "0.1.1" }
tokio = { version = "1.47.0", features = ["full"] }
bytemuck = { version = "1.23.1", features = ["derive"] }
proxy-common = {path = "../proxy-common"}
tokio-tungstenite = {version = "0.28.0", features = ["rustls", "rustls-tls-native-roots"]}
log = "0.4.25"
futures-util = "0.3.31"
v-byte-helpers = { git = "https://github.com/RusticMaple/VByteMacros", version = "0.1.1" }

View file

@ -0,0 +1,14 @@
use rnex_core::PID;
use crate::crypto::Crypto;
pub struct Insecure;
impl Crypto for Insecure {
fn new_connection(&self, data: &[u8]) -> Option<(PID, Vec<u8>)> {
Some((100, vec![]))
}
fn new() -> Self {
Self
}
}

View file

@ -0,0 +1,9 @@
use rnex_core::PID;
pub mod insecure;
pub mod secure;
pub trait Crypto: 'static + Send + Sync {
fn new_connection(&self, data: &[u8]) -> Option<(PID, Vec<u8>)>;
fn new() -> Self;
}

View file

@ -0,0 +1,27 @@
use rnex_core::{
PID, executables::common::SECURE_SERVER_ACCOUNT, nex::account::Account,
prudp::ticket::read_secure_connection_data, rmc::structures::RmcSerialize,
};
use crate::crypto::Crypto;
pub struct Secure(&'static Account);
impl Crypto for Secure {
fn new_connection(&self, data: &[u8]) -> Option<(PID, Vec<u8>)> {
let (_, pid, check_value) = read_secure_connection_data(data, &self.0)?;
let check_value_response = check_value + 1;
let data = bytemuck::bytes_of(&check_value_response);
let mut response = Vec::new();
data.serialize(&mut response).ok()?;
Some((pid, response))
}
fn new() -> Self {
Self(&SECURE_SERVER_ACCOUNT)
}
}

309
prudplite/src/lib.rs Normal file
View file

@ -0,0 +1,309 @@
pub mod crypto;
mod packet;
use std::{collections::HashMap, net::SocketAddr, sync::Arc};
use crate::{
crypto::{Crypto, insecure::Insecure, secure::Secure},
packet::{LiteHeader, LitePacket, PacketSpecificData, StreamTypes, create_packet_from},
};
use futures_util::{SinkExt, StreamExt};
use log::{error, info, warn};
use proxy_common::{ProxyStartupParam, new_backend_connection};
use rnex_core::{
PID,
prudp::{
socket_addr::PRUDPSockAddr,
types_flags::{
TypesFlags,
flags::{ACK, NEED_ACK, RELIABLE},
types::{CONNECT, DATA, DISCONNECT, SYN},
},
virtual_port::VirtualPort,
},
util::SplittableBufferConnection,
};
use tokio::net::{TcpListener, TcpStream};
use tokio_tungstenite::{
WebSocketStream,
tungstenite::{
Bytes, Message, client::IntoClientRequest, http::header::ACCESS_CONTROL_REQUEST_METHOD,
},
};
struct ConnectionState {
param: Arc<ProxyStartupParam>,
active: bool,
websocket: WebSocketStream<TcpStream>,
pid: PID,
backend_conn: SplittableBufferConnection,
addr: PRUDPSockAddr,
incoming_reliable: HashMap<u16, LitePacket<Bytes>>,
client_reliable_counter: u16,
server_reliable_counter: u16,
}
impl ConnectionState {
pub async fn handle_incoming_prudp(&mut self, packet: LitePacket<Bytes>, sorted: bool) {
let Some(header) = packet.header() else {
warn!("invalid data on connection");
return;
};
if (header.types_flags.get_flags() & NEED_ACK) != 0 {
let data = create_packet_from(
LiteHeader {
stream_types: StreamTypes::new(
self.param.virtual_port.get_stream_type(),
self.addr.virtual_port.get_stream_type(),
),
source_port: self.param.virtual_port.get_port_number(),
destination_port: self.addr.virtual_port.get_port_number(),
fragment_id: header.fragment_id,
types_flags: TypesFlags::default()
.types(header.types_flags.get_types())
.flags(ACK),
sequence_id: header.sequence_id,
..Default::default()
},
&[],
&[],
);
let data: Bytes = data.into();
if header.types_flags.get_types() == DISCONNECT {
self.websocket.send(Message::Binary(data.clone())).await;
self.websocket.send(Message::Binary(data.clone())).await;
}
self.websocket.send(Message::Binary(data)).await;
}
if (header.types_flags.get_flags() & ACK) != 0 {
// we can just safely ignore acks, we ARE sending over tcp after all already guarantees that our packets will arrive
// we can however not guarantee the order of incoming client packets so we should still take care of that
// (the client might be doing some funny things which we dont know of)
return;
}
if (header.types_flags.get_flags() & RELIABLE != 0) & !sorted {
self.incoming_reliable.insert(header.sequence_id, packet);
if self.incoming_reliable.len() > 5 {
self.active = false;
warn!("client is spamming out of order reliable packets, throwing out");
}
return;
}
match header.types_flags.get_types() {
DATA => {
if header.fragment_id != 0 {
warn!("fragmented packets arent yet supported");
return;
}
let Some(payload) = packet.payload() else {
return;
};
self.backend_conn.send(payload.into()).await;
}
PING => {}
v => {
info!("unimplemented packet type: {}", v);
}
}
}
pub async fn process_reliable(&mut self) {
while let Some(v) = self.incoming_reliable.remove(&self.client_reliable_counter) {
self.handle_incoming_prudp(v, true).await;
self.client_reliable_counter += 1;
}
}
pub async fn handle_connection(&mut self) {
while self.active {
tokio::select! {
v = self.websocket.next() => {
match v {
Some(Ok(Message::Binary(v))) => {
self.handle_incoming_prudp(LitePacket::new(v), false).await;
}
_ => {
info!("client disconnected or errored out");
return;
}
}
}
v = self.backend_conn.recv() => {
}
}
}
}
}
pub async fn websocket_thread_unconnected<C: Crypto>(
param: Arc<ProxyStartupParam>,
crypto: Arc<C>,
conn: TcpStream,
addr: SocketAddr,
) {
let mut websocket = match tokio_tungstenite::accept_async(conn).await {
Ok(v) => v,
Err(e) => {
error!("error accepting websocket connection: {}", e);
return;
}
};
while let Some(Ok(v)) = websocket.next().await {
match v {
Message::Binary(b) => {
let packet = LitePacket::new(b);
let Some(header) = packet.header() else {
error!("got malformed message, disconnecting");
return;
};
match header.types_flags.get_types() {
SYN => {
let Some(supported) = packet.packet_specific_iter() else {
error!("got malformed message, disconnecting");
return;
};
let Some(PacketSpecificData::SupportedFunctions(s)) = supported
.into_iter()
.find(|v| matches!(v, PacketSpecificData::SupportedFunctions(_)))
else {
error!("got malformed message, disconnecting");
return;
};
let data = create_packet_from(
LiteHeader {
destination_port: header.source_port,
source_port: param.virtual_port.get_port_number(),
stream_types: StreamTypes::new(
param.virtual_port.get_stream_type(),
header.stream_types.source(),
),
fragment_id: 0,
sequence_id: 0,
types_flags: TypesFlags::default().types(SYN).flags(ACK),
..Default::default()
},
&[
PacketSpecificData::SupportedFunctions(s & 0xFF),
PacketSpecificData::ConnectionSignature([0; 16]),
],
&[],
);
websocket.send(Message::Binary(data.into())).await;
}
CONNECT => {
let Some(supported) = packet.packet_specific_iter() else {
error!("got malformed message, disconnecting");
return;
};
let Some(PacketSpecificData::SupportedFunctions(s)) = supported
.into_iter()
.find(|v| matches!(v, PacketSpecificData::SupportedFunctions(_)))
else {
error!("got malformed message, disconnecting");
return;
};
let Some(data) = packet.payload() else {
error!("got malformed message, disconnecting");
return;
};
let Some((pid, data)) = crypto.new_connection(data) else {
error!("invalid login data");
return;
};
let data = create_packet_from(
LiteHeader {
destination_port: header.source_port,
source_port: param.virtual_port.get_port_number(),
stream_types: StreamTypes::new(
param.virtual_port.get_stream_type(),
header.stream_types.source(),
),
fragment_id: 0,
sequence_id: 0,
types_flags: TypesFlags::default().types(CONNECT).flags(ACK),
..Default::default()
},
&[
PacketSpecificData::SupportedFunctions(s & 0xFF),
PacketSpecificData::ConnectionSignature([0; 16]),
],
&data,
);
websocket.send(Message::Binary(data.into())).await;
let addr = PRUDPSockAddr::new(
addr,
VirtualPort::new(header.source_port, header.stream_types.source()),
);
let Some(backend_conn) = new_backend_connection(&param, addr, pid).await
else {
error!("unable to connect to backend");
return;
};
let mut connection = ConnectionState {
active: true,
addr,
pid,
backend_conn,
client_reliable_counter: 2,
server_reliable_counter: 1,
param,
incoming_reliable: HashMap::new(),
websocket,
};
connection.handle_connection().await;
break;
}
v => {
error!(
"invalid packet type for unconnected client {}, disconnecting",
v,
);
}
}
}
v => {
error!("non binary message({:?}) , disconnecting", v);
return;
}
}
}
}
pub async fn start_proxy<C: Crypto>(param: ProxyStartupParam) {
let param = Arc::new(param);
let crypto = Arc::new(C::new());
let listener = TcpListener::bind(param.self_private)
.await
.expect("unable to bind to port");
while let Ok((connection, addr)) = listener.accept().await {
let param = param.clone();
let crypto = crypto.clone();
tokio::spawn(websocket_thread_unconnected(
param, crypto, connection, addr,
));
}
}
pub async fn start_secure(param: ProxyStartupParam) {
start_proxy::<Secure>(param).await;
}
pub async fn start_insecure(param: ProxyStartupParam) {
start_proxy::<Insecure>(param).await;
}

45
prudplite/src/main.rs Normal file
View file

@ -0,0 +1,45 @@
use futures_util::{SinkExt, StreamExt};
use rnex_core::prudp::types_flags::{TypesFlags, flags::NEED_ACK, types::SYN};
use tokio_tungstenite::tungstenite::{Message, client::IntoClientRequest, http::header};
use crate::packet::{LiteHeader, LitePacket, PacketSpecificData, StreamTypes, create_packet_from};
mod packet;
const KEY: &str = "4eb18d39";
const URL: &str = "wss://g2DF33D01-lp1.s.n.srv.nintendo.net";
#[tokio::main]
async fn main() {
let login = URL.into_client_request().unwrap();
let (mut stream, response) = tokio_tungstenite::connect_async(login).await.unwrap();
println!("response: {:?}", response);
let packet = create_packet_from(
LiteHeader {
stream_types: StreamTypes::new(10, 10),
source_port: 1,
destination_port: 1,
fragment_id: 0,
types_flags: TypesFlags::default().types(SYN).flags(NEED_ACK),
sequence_id: 0,
..Default::default()
},
&[PacketSpecificData::SupportedFunctions(0x8)],
&[],
);
println!("sending ack");
stream.send(Message::Binary(packet.into())).await.unwrap();
println!("waiting for response");
let packet = stream.next().await.unwrap();
let Message::Binary(packet) = packet.unwrap() else {
panic!()
};
let packet = LitePacket::new(packet);
let header = packet.header().unwrap();
println!("{:?}", header);
}

223
prudplite/src/packet.rs Normal file
View file

@ -0,0 +1,223 @@
use std::{
fmt::Debug,
io::{self, Cursor, Read, Write},
};
use bytemuck::{Pod, Zeroable, bytes_of};
use futures_util::Stream;
use rnex_core::prudp::types_flags::TypesFlags;
use v_byte_helpers::{IS_BIG_ENDIAN, ReadExtensions};
#[derive(Pod, Zeroable, Copy, Clone, Default, Debug)]
#[repr(C)]
pub struct LiteHeader {
pub magic: u8,
pub packet_specific_length: u8,
pub payload_size: u16,
pub stream_types: StreamTypes,
pub source_port: u8,
pub destination_port: u8,
pub fragment_id: u8,
pub types_flags: TypesFlags,
pub sequence_id: u16,
}
pub enum PacketSpecificData {
SupportedFunctions(u32),
ConnectionSignature([u8; 16]),
LiteSignature([u8; 16]),
}
impl PacketSpecificData {
fn consume(reader: &mut impl Read) -> io::Result<Self> {
let mut option_id = 0;
reader.read_exact(&mut [option_id])?;
let mut size = 0;
reader.read_exact(&mut [size])?;
match option_id {
0 => {
if size != 4 {
Err(io::Error::other(
"invalid option size for supported functions",
))
} else {
Ok(Self::SupportedFunctions(reader.read_le_u32()?))
}
}
1 => {
if size != 16 {
Err(io::Error::other(
"invalid option size for connection signature",
))
} else {
Ok(Self::ConnectionSignature(
reader.read_struct(IS_BIG_ENDIAN)?,
))
}
}
0x80 => {
if size != 16 {
Err(io::Error::other("invalid option size for lite signature"))
} else {
Ok(Self::LiteSignature(reader.read_struct(IS_BIG_ENDIAN)?))
}
}
_ => Err(io::Error::other("invalid option id")),
}
}
fn write_size(&self) -> usize {
2 + match self {
PacketSpecificData::SupportedFunctions(_) => 4,
Self::ConnectionSignature(_) => 16,
Self::LiteSignature(_) => 16,
}
}
fn write_self(&self, writer: &mut impl Write) -> io::Result<()> {
match self {
PacketSpecificData::SupportedFunctions(v) => {
writer.write_all(&[0, 4])?;
writer.write_all(&v.to_le_bytes())?;
}
Self::ConnectionSignature(v) => {
writer.write_all(&[1, 16])?;
writer.write_all(&v[..])?;
}
Self::LiteSignature(v) => {
writer.write_all(&[0x80, 16])?;
writer.write_all(&v[..])?;
}
}
Ok(())
}
}
pub struct LitePacket<T: AsRef<[u8]>>(T);
pub struct PacketSpecificIter<'a>(Cursor<&'a [u8]>);
impl<'a> Iterator for PacketSpecificIter<'a> {
type Item = PacketSpecificData;
fn next(&mut self) -> Option<Self::Item> {
PacketSpecificData::consume(&mut self.0).ok()
}
}
impl<T: AsRef<[u8]>> LitePacket<T> {
pub fn new(inner: T) -> Self {
Self(inner)
}
pub fn header(&self) -> Option<&LiteHeader> {
bytemuck::try_from_bytes(self.0.as_ref().get(..size_of::<LiteHeader>())?).ok()
}
pub fn header_mut(&mut self) -> Option<&mut LiteHeader>
where
T: AsMut<[u8]>,
{
bytemuck::try_from_bytes_mut(self.0.as_mut().get_mut(..size_of::<LiteHeader>())?).ok()
}
pub fn payload(&self) -> Option<&[u8]> {
let header = self.header()?;
self.0
.as_ref()
.get(size_of::<LiteHeader>() + header.packet_specific_length as usize..)
}
pub fn payload_mut(&mut self) -> Option<&mut [u8]>
where
T: AsMut<[u8]>,
{
let len = self.header()?.packet_specific_length;
self.0
.as_mut()
.get_mut(size_of::<LiteHeader>() + len as usize..)
}
pub fn packet_specific_raw(&self) -> Option<&[u8]> {
let header = self.header()?;
self.0.as_ref().get(
size_of::<LiteHeader>()
..size_of::<LiteHeader>() + header.packet_specific_length as usize,
)
}
pub fn packet_specific_raw_mut(&mut self) -> Option<&mut [u8]>
where
T: AsMut<[u8]>,
{
let len = self.header()?.packet_specific_length;
self.0
.as_mut()
.get_mut(size_of::<LiteHeader>()..size_of::<LiteHeader>() + len as usize)
}
pub fn packet_specific_iter(&self) -> Option<PacketSpecificIter> {
self.packet_specific_raw()
.map(Cursor::new)
.map(PacketSpecificIter)
}
}
pub fn create_packet_from(
header: LiteHeader,
specific_data: &[PacketSpecificData],
data: &[u8],
) -> Vec<u8> {
let specific_size: usize = specific_data.iter().map(|v| v.write_size()).sum();
let mut packet = LitePacket::new(vec![
0u8;
size_of::<LiteHeader>() + specific_size + data.len()
]);
*packet.header_mut().expect("packet malformed in creation") = LiteHeader {
magic: 0x80,
packet_specific_length: specific_size as u8,
payload_size: data.len() as u16,
..header
};
let mut cursor = Cursor::new(
packet
.packet_specific_raw_mut()
.expect("packet malformed in creation"),
);
for specific in specific_data {
specific.write_self(&mut cursor).unwrap();
}
packet
.payload_mut()
.expect("packet malformed in creation")
.copy_from_slice(data);
packet.0
}
#[derive(Pod, Zeroable, Copy, Clone, Default)]
#[repr(transparent)]
pub struct StreamTypes(u8);
impl Debug for StreamTypes {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "({},{})", self.source(), self.destination())
}
}
impl StreamTypes {
pub fn new(source_stream: u8, dest_stream: u8) -> Self {
Self((source_stream & 0xF << 4) & dest_stream & 0xF)
}
pub fn source(&self) -> u8 {
self.0 >> 4
}
pub fn destination(&self) -> u8 {
self.0 & 0xF
}
}