wip: checkpoint FIFA 17 hook diagnostics
This commit is contained in:
@@ -5,27 +5,27 @@ use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::sync::OnceLock;
|
||||
|
||||
const AF_INET: u16 = 2;
|
||||
const PORT_HTTPS_NBO: u16 = 0xBB01; // 443 big-endian
|
||||
const PORT_BRIDGE_NBO: u16 = 0xFB20; // 8443 big-endian
|
||||
const PORT_BLAZE_REDIRECTOR_NBO: u16 = 0x3927; // 10041 big-endian
|
||||
const PORT_BLAZE_MAIN_NBO: u16 = 0x8FA4; // 42127 big-endian
|
||||
// EA App LSX. anadius handles :3216 in-process before it reaches the host TCP
|
||||
// stack (keyed on port 3216 specifically), so redirecting FIFA's LSX connect to a
|
||||
// *different* host port (:3217) slips past that interception and lands on the
|
||||
// native openfut-bridge LSX server. This is the load-bearing redirect that routes
|
||||
// LSX to our bridge; without it FIFA uses anadius's in-process emu instead.
|
||||
const PORT_HTTPS_NBO: u16 = 0xBB01; // 443 big-endian
|
||||
const PORT_BRIDGE_NBO: u16 = 0xFB20; // 8443 big-endian
|
||||
const PORT_BLAZE_REDIRECTOR_NBO: u16 = 0x3927; // 10041 big-endian
|
||||
const PORT_BLAZE_MAIN_NBO: u16 = 0x8FA4; // 42127 big-endian
|
||||
// EA App LSX. anadius handles :3216 in-process before it reaches the host TCP
|
||||
// stack (keyed on port 3216 specifically), so redirecting FIFA's LSX connect to a
|
||||
// *different* host port (:3217) slips past that interception and lands on the
|
||||
// native openfut-bridge LSX server. This is the load-bearing redirect that routes
|
||||
// LSX to our bridge; without it FIFA uses anadius's in-process emu instead.
|
||||
#[allow(dead_code)] // unused when built with the `capture_baseline` feature
|
||||
const PORT_LSX_NBO: u16 = 0x900C; // 3216 big-endian (EA App LSX)
|
||||
const PORT_LSX_NBO: u16 = 0x900C; // 3216 big-endian (EA App LSX)
|
||||
#[allow(dead_code)]
|
||||
const PORT_LSX_TARGET_NBO: u16 = 0x910C; // 3217 big-endian (bridge LSX target)
|
||||
const PORT_LSX_TARGET_NBO: u16 = 0x910C; // 3217 big-endian (bridge LSX target)
|
||||
const ADDR_LOOPBACK_NBO: u32 = 0x0100_007F; // 127.0.0.1 big-endian
|
||||
|
||||
#[repr(C)]
|
||||
struct SockaddrIn {
|
||||
sin_family: u16,
|
||||
sin_port: u16,
|
||||
sin_addr: u32,
|
||||
sin_zero: [u8; 8],
|
||||
sin_port: u16,
|
||||
sin_addr: u32,
|
||||
sin_zero: [u8; 8],
|
||||
}
|
||||
|
||||
const AF_INET6: u16 = 23; // Windows AF_INET6 value (we run under the Win ABI in Wine)
|
||||
@@ -34,10 +34,10 @@ const AF_INET6: u16 = 23; // Windows AF_INET6 value (we run under the Win ABI in
|
||||
/// address bytes in network order. 28 bytes total.
|
||||
#[repr(C)]
|
||||
struct SockaddrIn6 {
|
||||
sin6_family: u16,
|
||||
sin6_port: u16,
|
||||
sin6_family: u16,
|
||||
sin6_port: u16,
|
||||
sin6_flowinfo: u32,
|
||||
sin6_addr: [u8; 16],
|
||||
sin6_addr: [u8; 16],
|
||||
sin6_scope_id: u32,
|
||||
}
|
||||
|
||||
@@ -46,8 +46,7 @@ struct SockaddrIn6 {
|
||||
/// existing IPv4 listener on :8443 — no separate IPv6 listener needed. The game's own
|
||||
/// EA dials already use v4-mapped addresses (`::ffff:x.x.x.x`), so its sockets are not
|
||||
/// `IPV6_V6ONLY` and will accept this target.
|
||||
const V4MAPPED_LOOPBACK: [u8; 16] =
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 127, 0, 0, 1];
|
||||
const V4MAPPED_LOOPBACK: [u8; 16] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 127, 0, 0, 1];
|
||||
|
||||
// Address of ws2_32!connect (set at hook installation)
|
||||
static CONNECT_ADDR: AtomicUsize = AtomicUsize::new(0);
|
||||
@@ -57,18 +56,26 @@ static mut ORIGINAL_BYTES: [u8; 14] = [0u8; 14];
|
||||
|
||||
// For WSAConnect IAT fallback
|
||||
type WsaConnectFn = unsafe extern "system" fn(
|
||||
s: usize, name: *const u8, namelen: i32,
|
||||
caller: *const (), callee: *const (),
|
||||
sqos: *const (), gqos: *const ()) -> i32;
|
||||
s: usize,
|
||||
name: *const u8,
|
||||
namelen: i32,
|
||||
caller: *const (),
|
||||
callee: *const (),
|
||||
sqos: *const (),
|
||||
gqos: *const (),
|
||||
) -> i32;
|
||||
static REAL_WSA: OnceLock<WsaConnectFn> = OnceLock::new();
|
||||
pub fn set_real_wsa_connect(f: WsaConnectFn) { let _ = REAL_WSA.set(f); }
|
||||
pub fn set_real_wsa_connect(f: WsaConnectFn) {
|
||||
let _ = REAL_WSA.set(f);
|
||||
}
|
||||
|
||||
unsafe fn write_hook(target: *mut u8, dest: u64) {
|
||||
use windows_sys::Win32::System::Memory::{VirtualProtect, PAGE_EXECUTE_READWRITE};
|
||||
let mut old: u32 = 0;
|
||||
VirtualProtect(target as _, 14, PAGE_EXECUTE_READWRITE, &mut old);
|
||||
// FF 25 00 00 00 00 JMP [rip+0]
|
||||
target.write(0xFF); target.add(1).write(0x25);
|
||||
target.write(0xFF);
|
||||
target.add(1).write(0x25);
|
||||
(target.add(2) as *mut u32).write(0u32);
|
||||
(target.add(6) as *mut u64).write(dest);
|
||||
VirtualProtect(target as _, 14, old, &mut old);
|
||||
@@ -78,7 +85,7 @@ unsafe fn restore_original(target: *mut u8) {
|
||||
use windows_sys::Win32::System::Memory::{VirtualProtect, PAGE_EXECUTE_READWRITE};
|
||||
let mut old: u32 = 0;
|
||||
VirtualProtect(target as _, 14, PAGE_EXECUTE_READWRITE, &mut old);
|
||||
core::ptr::copy_nonoverlapping(ORIGINAL_BYTES.as_ptr(), target, 14);
|
||||
core::ptr::copy_nonoverlapping(core::ptr::addr_of!(ORIGINAL_BYTES) as *const u8, target, 14);
|
||||
VirtualProtect(target as _, 14, old, &mut old);
|
||||
}
|
||||
|
||||
@@ -103,26 +110,30 @@ pub(crate) unsafe fn redirect_if_ea(name: *const u8, namelen: i32) -> Option<([u
|
||||
// SAFE: family is AF_INET and namelen >= 8 == the sockaddr_in fields we read.
|
||||
let sa = &*(name as *const SockaddrIn);
|
||||
let new_port_nbo = match sa.sin_port {
|
||||
PORT_HTTPS_NBO => PORT_BRIDGE_NBO,
|
||||
PORT_HTTPS_NBO => PORT_BRIDGE_NBO,
|
||||
#[cfg(not(feature = "capture_baseline"))]
|
||||
PORT_LSX_NBO => PORT_LSX_TARGET_NBO,
|
||||
PORT_LSX_NBO => PORT_LSX_TARGET_NBO,
|
||||
PORT_BLAZE_REDIRECTOR_NBO => PORT_BLAZE_REDIRECTOR_NBO,
|
||||
PORT_BLAZE_MAIN_NBO => PORT_BLAZE_MAIN_NBO,
|
||||
_ => return None,
|
||||
PORT_BLAZE_MAIN_NBO => PORT_BLAZE_MAIN_NBO,
|
||||
_ => return None,
|
||||
};
|
||||
// sin_addr is network order; to_le_bytes gives memory order = the dotted
|
||||
// quad, so b[0].b[1].b[2].b[3] is correct (the old code printed it reversed).
|
||||
let o = sa.sin_addr.to_le_bytes();
|
||||
crate::write_log(&format!(
|
||||
"connect_hook: v4 {}.{}.{}.{}:{} → 127.0.0.1:{}\n",
|
||||
o[0], o[1], o[2], o[3], u16::from_be(sa.sin_port),
|
||||
o[0],
|
||||
o[1],
|
||||
o[2],
|
||||
o[3],
|
||||
u16::from_be(sa.sin_port),
|
||||
u16::from_be(new_port_nbo)
|
||||
));
|
||||
// SAFE: buf is 28 bytes, larger than the 16-byte sockaddr_in we write.
|
||||
let out = &mut *(buf.as_mut_ptr() as *mut SockaddrIn);
|
||||
out.sin_family = AF_INET;
|
||||
out.sin_port = new_port_nbo;
|
||||
out.sin_addr = ADDR_LOOPBACK_NBO;
|
||||
out.sin_port = new_port_nbo;
|
||||
out.sin_addr = ADDR_LOOPBACK_NBO;
|
||||
Some((buf, 16))
|
||||
}
|
||||
AF_INET6 => {
|
||||
@@ -133,23 +144,27 @@ pub(crate) unsafe fn redirect_if_ea(name: *const u8, namelen: i32) -> Option<([u
|
||||
let sa6 = &*(name as *const SockaddrIn6);
|
||||
// LSX is IPv4-only (anadius keys on it), so it is intentionally omitted here.
|
||||
let new_port_nbo = match sa6.sin6_port {
|
||||
PORT_HTTPS_NBO => PORT_BRIDGE_NBO,
|
||||
PORT_HTTPS_NBO => PORT_BRIDGE_NBO,
|
||||
PORT_BLAZE_REDIRECTOR_NBO => PORT_BLAZE_REDIRECTOR_NBO,
|
||||
PORT_BLAZE_MAIN_NBO => PORT_BLAZE_MAIN_NBO,
|
||||
_ => return None,
|
||||
PORT_BLAZE_MAIN_NBO => PORT_BLAZE_MAIN_NBO,
|
||||
_ => return None,
|
||||
};
|
||||
let a = sa6.sin6_addr;
|
||||
crate::write_log(&format!(
|
||||
"connect_hook: v6 [{:02x}{:02x}:..:{:02x}{:02x}]:{} → ::ffff:127.0.0.1:{}\n",
|
||||
a[0], a[1], a[14], a[15], u16::from_be(sa6.sin6_port),
|
||||
a[0],
|
||||
a[1],
|
||||
a[14],
|
||||
a[15],
|
||||
u16::from_be(sa6.sin6_port),
|
||||
u16::from_be(new_port_nbo)
|
||||
));
|
||||
// SAFE: buf is exactly 28 bytes == sizeof(sockaddr_in6).
|
||||
let out = &mut *(buf.as_mut_ptr() as *mut SockaddrIn6);
|
||||
out.sin6_family = AF_INET6;
|
||||
out.sin6_port = new_port_nbo;
|
||||
out.sin6_family = AF_INET6;
|
||||
out.sin6_port = new_port_nbo;
|
||||
out.sin6_flowinfo = 0;
|
||||
out.sin6_addr = V4MAPPED_LOOPBACK;
|
||||
out.sin6_addr = V4MAPPED_LOOPBACK;
|
||||
out.sin6_scope_id = 0;
|
||||
Some((buf, 28))
|
||||
}
|
||||
@@ -174,7 +189,13 @@ pub unsafe extern "system" fn hooked_connect(s: usize, name: *const u8, namelen:
|
||||
use windows_sys::Win32::Networking::WinSock::{getsockopt, SOL_SOCKET, SO_TYPE};
|
||||
let mut ty: i32 = -1;
|
||||
let mut len: i32 = 4;
|
||||
getsockopt(s, SOL_SOCKET as i32, SO_TYPE, &mut ty as *mut i32 as *mut u8, &mut len);
|
||||
getsockopt(
|
||||
s,
|
||||
SOL_SOCKET as i32,
|
||||
SO_TYPE,
|
||||
&mut ty as *mut i32 as *mut u8,
|
||||
&mut len,
|
||||
);
|
||||
ty
|
||||
};
|
||||
crate::write_log(&format!(
|
||||
@@ -190,11 +211,11 @@ pub unsafe extern "system" fn hooked_connect(s: usize, name: *const u8, namelen:
|
||||
let (call_name, call_len) = if let Some((buf, len)) = redirect_if_ea(name, namelen) {
|
||||
restore_original(addr);
|
||||
let r = {
|
||||
let f: unsafe extern "system" fn(usize, *const u8, i32) -> i32
|
||||
= core::mem::transmute(addr);
|
||||
let f: unsafe extern "system" fn(usize, *const u8, i32) -> i32 =
|
||||
core::mem::transmute(addr);
|
||||
f(s, buf.as_ptr(), len)
|
||||
};
|
||||
write_hook(addr, hooked_connect as u64);
|
||||
write_hook(addr, hooked_connect as *const () as u64);
|
||||
return r;
|
||||
} else {
|
||||
(name, namelen)
|
||||
@@ -202,18 +223,19 @@ pub unsafe extern "system" fn hooked_connect(s: usize, name: *const u8, namelen:
|
||||
|
||||
restore_original(addr);
|
||||
let r = {
|
||||
let f: unsafe extern "system" fn(usize, *const u8, i32) -> i32
|
||||
= core::mem::transmute(addr);
|
||||
let f: unsafe extern "system" fn(usize, *const u8, i32) -> i32 = core::mem::transmute(addr);
|
||||
f(s, call_name, call_len)
|
||||
};
|
||||
write_hook(addr, hooked_connect as u64);
|
||||
write_hook(addr, hooked_connect as *const () as u64);
|
||||
if namelen >= 8 {
|
||||
let sa = &*(call_name as *const SockaddrIn);
|
||||
if sa.sin_family == AF_INET {
|
||||
let err = if r != 0 {
|
||||
use windows_sys::Win32::Networking::WinSock::WSAGetLastError;
|
||||
WSAGetLastError()
|
||||
} else { 0 };
|
||||
} else {
|
||||
0
|
||||
};
|
||||
crate::write_log(&format!("connect_hook: result={r} wsa_err={err}\n"));
|
||||
}
|
||||
}
|
||||
@@ -221,9 +243,13 @@ pub unsafe extern "system" fn hooked_connect(s: usize, name: *const u8, namelen:
|
||||
}
|
||||
|
||||
pub unsafe extern "system" fn hooked_wsa_connect(
|
||||
s: usize, name: *const u8, namelen: i32,
|
||||
caller: *const (), callee: *const (),
|
||||
sqos: *const (), gqos: *const (),
|
||||
s: usize,
|
||||
name: *const u8,
|
||||
namelen: i32,
|
||||
caller: *const (),
|
||||
callee: *const (),
|
||||
sqos: *const (),
|
||||
gqos: *const (),
|
||||
) -> i32 {
|
||||
// Milestone-0 transport watch (self-gates on OPENFUT_TRANSPORT_WATCH).
|
||||
crate::transport_watch::note_connect("WSAConnect", name, namelen, s);
|
||||
@@ -240,17 +266,23 @@ pub unsafe fn install_inline_connect_hook() -> bool {
|
||||
use windows_sys::Win32::System::LibraryLoader::{GetModuleHandleA, GetProcAddress};
|
||||
|
||||
let ws2 = GetModuleHandleA(b"ws2_32.dll\0".as_ptr());
|
||||
if ws2.is_null() { return false; }
|
||||
if ws2.is_null() {
|
||||
return false;
|
||||
}
|
||||
let connect_fn = match GetProcAddress(ws2, b"connect\0".as_ptr()) {
|
||||
Some(f) => f as *mut u8,
|
||||
None => return false,
|
||||
};
|
||||
|
||||
// Save original 14 bytes
|
||||
core::ptr::copy_nonoverlapping(connect_fn, ORIGINAL_BYTES.as_mut_ptr(), 14);
|
||||
core::ptr::copy_nonoverlapping(
|
||||
connect_fn,
|
||||
core::ptr::addr_of_mut!(ORIGINAL_BYTES) as *mut u8,
|
||||
14,
|
||||
);
|
||||
CONNECT_ADDR.store(connect_fn as usize, Ordering::Relaxed);
|
||||
|
||||
// Overwrite first 14 bytes with absolute indirect JMP to our hook
|
||||
write_hook(connect_fn, hooked_connect as u64);
|
||||
write_hook(connect_fn, hooked_connect as *const () as u64);
|
||||
true
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user