Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
4a01b7b
Create ko.ts
sampmoder Feb 14, 2026
a1c6255
Create kr.ts
sampmoder Feb 14, 2026
7180c79
Update index.ts
sampmoder Feb 14, 2026
76fef91
Delete src/locales/translations/ko.ts
sampmoder Feb 14, 2026
372ef35
Merge pull request #375 from sampmoder/add-korean-lang
AmyrAhmady Feb 27, 2026
c035655
Add IPv6-aware launcher query backend
Knogle Mar 2, 2026
a066fcf
Add IPv6 endpoint parsing helpers
Knogle Mar 2, 2026
84fbaa8
Use IPv6 endpoint parsing in launcher dialogs
Knogle Mar 2, 2026
f4e7688
Handle IPv6 addresses in launcher join flow
Knogle Mar 2, 2026
fe8407c
Add launcher query probe skeleton
Knogle Mar 2, 2026
8f9e1bd
Implement launcher query probe
Knogle Mar 2, 2026
8b8bfa5
Add launcher connect probe skeleton
Knogle Mar 2, 2026
38b6916
Implement launcher connect probe
Knogle Mar 2, 2026
9ca779b
Handle cookie challenge in launcher connect probe
Knogle Mar 2, 2026
e925de1
Clean launcher connect probe warning
Knogle Mar 2, 2026
5902805
launcher: add socket trace injector and ipv6 probe tooling
Knogle Mar 5, 2026
7acd486
launcher: add ipv6 probe fallback and dualstack trace flag
Knogle Mar 5, 2026
7ba3719
launcher: force ipv6 shim remote target and tighten trace fallback
Knogle Mar 5, 2026
679b988
launcher: stage trace runtime and fail fast on optional shim
Knogle Mar 5, 2026
f01096a
launcher: resolve trace DLL only from launcher dir
Knogle Mar 5, 2026
57c13ea
launcher: hook dynamic winsock resolution in trace shim
Knogle Mar 5, 2026
8a614d0
launcher: drop loadlibrary hooks from trace shim
Knogle Mar 5, 2026
5bfc211
launcher: bind dualstack shim sockets to native v6 any
Knogle Mar 5, 2026
2148fa9
launcher: hook wsarecvfrom in trace shim
Knogle Mar 5, 2026
1325554
launcher: hook recv and wsarecv in trace shim
Knogle Mar 5, 2026
6d8d809
launcher: clarify trace shim dependency failures
Knogle Mar 5, 2026
f8ed006
launcher: log and bound DLL injection waits
Knogle Mar 6, 2026
63a27e0
launcher: statically link trace shim C++ runtime
Knogle Mar 6, 2026
1496ea7
launcher: patch ordinal winsock hooks using import export resolution
Knogle Mar 6, 2026
ba41c8e
launcher: hook mswsock WSARecvEx in trace shim
Knogle Mar 6, 2026
2a1398c
launcher: add IPv6 local UDP proxy with legacy RakNet remap
Knogle Mar 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
388 changes: 265 additions & 123 deletions src-tauri/Cargo.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ windows-sys = { version = "0.52.0", features = [
"Win32_System_JobObjects"
] }
winreg = "0.52.0"
dll-syringe = "0.15.2"
dll-syringe = "0.17.1"
windows = { version = "0.39.0", features = [
"Win32_System_Console",
"Win32_Foundation",
Expand Down
2 changes: 1 addition & 1 deletion src-tauri/build.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
fn main() {
tauri_build::build()
tauri_build::build()
}
163 changes: 163 additions & 0 deletions src-tauri/src/bin/omp_connect_probe.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
use std::env;
use std::net::{IpAddr, SocketAddr, ToSocketAddrs, UdpSocket};
use std::time::Duration;

const ID_OPEN_CONNECTION_REQUEST: u8 = 24;
const ID_OPEN_CONNECTION_REPLY: u8 = 25;
const ID_OPEN_CONNECTION_COOKIE: u8 = 26;
const ID_CONNECTION_ATTEMPT_FAILED: u8 = 29;
const ID_NO_FREE_INCOMING_CONNECTIONS: u8 = 31;
const SAMP_PETARDED: u16 = 0x6969;

fn print_usage() {
eprintln!("Usage: omp_connect_probe <family> <host> <port>");
eprintln!(" family: ipv4 | ipv6");
}

fn parse_family(input: &str) -> Option<bool> {
match input {
"ipv4" => Some(false),
"ipv6" => Some(true),
_ => None,
}
}

fn resolve_target(host: &str, port: u16, want_ipv6: bool) -> Result<SocketAddr, String> {
if let Ok(ip) = host
.trim_start_matches('[')
.trim_end_matches(']')
.parse::<IpAddr>()
{
return Ok(SocketAddr::new(ip, port));
}

let mut addrs = (host, port)
.to_socket_addrs()
.map_err(|e| format!("failed to resolve {host}:{port}: {e}"))?;

addrs
.find(|addr| addr.is_ipv6() == want_ipv6)
.ok_or_else(|| {
format!(
"no {} address found for {}",
if want_ipv6 { "IPv6" } else { "IPv4" },
host
)
})
}

fn main() {
let args: Vec<String> = env::args().collect();
if args.len() != 4 {
print_usage();
std::process::exit(1);
}

let want_ipv6 = match parse_family(&args[1]) {
Some(v) => v,
None => {
eprintln!("invalid family: {}", args[1]);
std::process::exit(1);
}
};

let host = &args[2];
let port = match args[3].parse::<u16>() {
Ok(v) => v,
Err(e) => {
eprintln!("invalid port {}: {}", args[3], e);
std::process::exit(1);
}
};

let target = match resolve_target(host, port, want_ipv6) {
Ok(target) => target,
Err(error) => {
eprintln!("{error}");
std::process::exit(1);
}
};

let bind_addr = if want_ipv6 { "[::]:0" } else { "0.0.0.0:0" };
let socket = match UdpSocket::bind(bind_addr) {
Ok(socket) => socket,
Err(e) => {
eprintln!("bind failed on {bind_addr}: {e}");
std::process::exit(1);
}
};
socket.set_read_timeout(Some(Duration::from_secs(2))).ok();

let mut response = [0_u8; 128];
let send_request = |cookie_xor: u16| {
let request = [
ID_OPEN_CONNECTION_REQUEST,
(cookie_xor & 0xFF) as u8,
((cookie_xor >> 8) & 0xFF) as u8,
];
socket.send_to(&request, target)
};

if let Err(e) = send_request(0) {
eprintln!("send_to failed: {e}");
std::process::exit(1);
}

let mut received = match socket.recv(&mut response) {
Ok(n) => n,
Err(e) => {
eprintln!("recv failed: {e}");
std::process::exit(1);
}
};

let mut packet_id = response[0];
if packet_id == ID_OPEN_CONNECTION_COOKIE && received >= 3 {
let cookie = u16::from_le_bytes([response[1], response[2]]);
let cookie_xor = cookie ^ SAMP_PETARDED;
println!("received {} bytes", received);
println!("packet_id={}", packet_id);
println!("result=open_connection_cookie");

if let Err(e) = send_request(cookie_xor) {
eprintln!("send_to failed: {e}");
std::process::exit(1);
}

received = match socket.recv(&mut response) {
Ok(n) => n,
Err(e) => {
eprintln!("recv failed after cookie exchange: {e}");
std::process::exit(1);
}
};
packet_id = response[0];
}

println!("received {} bytes", received);
println!("packet_id={}", packet_id);

match packet_id {
ID_OPEN_CONNECTION_REPLY => {
println!("result=open_connection_reply");
}
ID_CONNECTION_ATTEMPT_FAILED => {
println!("result=connection_attempt_failed");
}
ID_NO_FREE_INCOMING_CONNECTIONS => {
println!("result=no_free_incoming_connections");
}
_ => {
println!("result=unexpected");
std::process::exit(2);
}
}

for (index, byte) in response[..received].iter().enumerate() {
if index > 0 {
print!(" ");
}
print!("{byte:02x}");
}
println!();
}
147 changes: 147 additions & 0 deletions src-tauri/src/bin/omp_query_probe.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
use std::env;
use std::net::{IpAddr, SocketAddr, ToSocketAddrs, UdpSocket};
use std::time::Duration;

const SAMP_HEADER: &[u8] = b"SAMP";
const SAMP6_HEADER: &[u8] = b"SAMP6";

fn print_usage() {
eprintln!("Usage: omp_query_probe <family> <host> <port> [opcode]");
eprintln!(" family: ipv4 | ipv6");
eprintln!(" opcode: i | o | c | r | p");
}

fn parse_family(input: &str) -> Option<bool> {
match input {
"ipv4" => Some(false),
"ipv6" => Some(true),
_ => None,
}
}

fn resolve_target(host: &str, port: u16, want_ipv6: bool) -> Result<SocketAddr, String> {
if let Ok(ip) = host
.trim_start_matches('[')
.trim_end_matches(']')
.parse::<IpAddr>()
{
return Ok(SocketAddr::new(ip, port));
}

let mut addrs = (host, port)
.to_socket_addrs()
.map_err(|e| format!("failed to resolve {host}:{port}: {e}"))?;

addrs
.find(|addr| addr.is_ipv6() == want_ipv6)
.ok_or_else(|| {
format!(
"no {} address found for {}",
if want_ipv6 { "IPv6" } else { "IPv4" },
host
)
})
}

fn build_packet(target: SocketAddr, opcode: u8) -> Vec<u8> {
let mut packet = Vec::new();
match target.ip() {
IpAddr::V4(ip) => {
packet.extend_from_slice(SAMP_HEADER);
packet.extend_from_slice(&ip.octets());
}
IpAddr::V6(ip) => {
packet.extend_from_slice(SAMP6_HEADER);
packet.extend_from_slice(&ip.octets());
}
}

packet.push((target.port() & 0xFF) as u8);
packet.push(((target.port() >> 8) & 0xFF) as u8);
packet.push(opcode);

if opcode == b'p' {
packet.extend_from_slice(&[0, 0, 0, 0]);
}

packet
}

fn main() {
let args: Vec<String> = env::args().collect();
if args.len() < 4 || args.len() > 5 {
print_usage();
std::process::exit(1);
}

let want_ipv6 = match parse_family(&args[1]) {
Some(v) => v,
None => {
eprintln!("invalid family: {}", args[1]);
std::process::exit(1);
}
};

let host = &args[2];
let port = match args[3].parse::<u16>() {
Ok(v) => v,
Err(e) => {
eprintln!("invalid port {}: {}", args[3], e);
std::process::exit(1);
}
};
let opcode = args
.get(4)
.and_then(|value| value.as_bytes().first().copied())
.unwrap_or(b'i');

let target = match resolve_target(host, port, want_ipv6) {
Ok(target) => target,
Err(error) => {
eprintln!("{error}");
std::process::exit(1);
}
};

let bind_addr = if want_ipv6 { "[::]:0" } else { "0.0.0.0:0" };
let socket = match UdpSocket::bind(bind_addr) {
Ok(socket) => socket,
Err(e) => {
eprintln!("bind failed on {bind_addr}: {e}");
std::process::exit(1);
}
};
socket.set_read_timeout(Some(Duration::from_secs(2))).ok();

let packet = build_packet(target, opcode);
if let Err(e) = socket.send_to(&packet, target) {
eprintln!("send_to failed: {e}");
std::process::exit(1);
}

let mut response = [0_u8; 2048];
let received = match socket.recv(&mut response) {
Ok(n) => n,
Err(e) => {
eprintln!("recv failed: {e}");
std::process::exit(1);
}
};

println!("received {} bytes for opcode {}", received, opcode as char);
if received >= 24 && &response[..5] == SAMP6_HEADER {
println!("magic=SAMP6 opcode={}", response[23] as char);
} else if received >= 11 && &response[..4] == SAMP_HEADER {
println!("magic=SAMP opcode={}", response[10] as char);
} else {
println!("magic=unknown");
}

for (index, byte) in response[..received].iter().enumerate() {
if index > 0 {
print!(" ");
}
print!("{byte:02x}");
}
println!();
}
Loading