From 4c57cf571c9e40446ec8b7bedaa2816adee6a69f Mon Sep 17 00:00:00 2001 From: funman300 Date: Mon, 29 Jun 2026 18:46:27 -0700 Subject: [PATCH] M1 probe: GoOnline passes, gate is downstream (token/status) Add a read-only detour on anadius's GoOnline handler (anadius64.dll+0x2BB90). Result: the game calls GoOnline during the "connecting" attempt and anadius returns success, yet no Blaze connection follows. The gate is therefore downstream of GoOnline -- the auth-token (GetAuthCode) and/or the "online established" status callback. Recorded in connection-gate-findings.md. Co-Authored-By: Claude Opus 4.8 (1M context) --- docs/connection-gate-findings.md | 27 +++++++++++-- openfut-hook/src/lib.rs | 67 ++++++++++++++++++++++++++++++-- 2 files changed, 86 insertions(+), 8 deletions(-) diff --git a/docs/connection-gate-findings.md b/docs/connection-gate-findings.md index dd4a645..e62e65e 100644 --- a/docs/connection-gate-findings.md +++ b/docs/connection-gate-findings.md @@ -119,7 +119,26 @@ only — no EA source. - `TODO/CONFIRM` the exact abort point (no-token vs state-says-offline vs both) — needs Ghidra-level control-flow tracing. -### Recommendation -Load `FIFA23.exe` into **Ghidra** to (a) name the connection-state getter -precisely and (b) confirm the gate mechanism, then return to `protossl-scan` + -the hook to map those addresses onto the live process and install the M2 hook. +### Dynamic probe result (read-only) — `GoOnline` is NOT the gate +A read-only detour on anadius's `GoOnline` handler (`anadius64.dll+0x2BB90`) +shows the game **does** call `GoOnline` during the "connecting" attempt (incl. +on the Ultimate Team click) and anadius returns success — **yet no Blaze +connection follows** (still zero external `CONNECT`/DNS). + +Therefore the gate is **downstream of `GoOnline`**: the game decides to go +online and the request is accepted, then it aborts at the **auth-token step +(`GetAuthCode`) and/or while waiting for the "online established" status +callback** (`ONLINE_STATUS_EVENT`), and times out into offline. + +This sharpens the two-gate picture: `GoOnline` passes; the real blocker is the +**token / online-status step**. `TODO/CONFIRM` which (token-missing vs +status-never-fires) — via a `GetAuthCode` probe and/or Ghidra. + +### Recommendation / next +Name the downstream gate. Two complementary routes: +- **Dynamic:** probe anadius's `GetAuthCode` handler (does it return a token or + fail?) — distinguishes token-gate from status-callback. +- **Static (Ghidra):** xref the `GoOnline` / `GetAuthCode` / `ONLINE_STATUS_EVENT` + strings in `FIFA23.exe` to find the FUT online-flow code that issues `GoOnline` + then waits for the token/status, and decompile it. FIFA isn't ASLR'd, so Ghidra + addresses (image base `0x140000000`) map 1:1 to our recorded offsets. diff --git a/openfut-hook/src/lib.rs b/openfut-hook/src/lib.rs index 6e3559e..a1613e7 100644 --- a/openfut-hook/src/lib.rs +++ b/openfut-hook/src/lib.rs @@ -191,15 +191,31 @@ unsafe extern "system" fn init_thread(_: *mut c_void) -> u32 { hook_winsock_data(); hook_connect(); - // Then the plaintext-capture detour on _ProtoSSLSendPacket. + // Then the plaintext-capture detour on _ProtoSSLSendPacket, plus the + // read-only anadius GoOnline probe (M1). Retry until both are installed. + let mut send_done = false; + let mut anadius_done = false; for _ in 0..60 { - if let Some(addr) = find_send_packet() { - install_hook(addr); + if !send_done { + if let Some(addr) = find_send_packet() { + install_hook(addr); + send_done = true; + } + } + if !anadius_done && hook_anadius_probes() { + anadius_done = true; + } + if send_done && anadius_done { return 0; } Sleep(1000); } - log("ERROR: _ProtoSSLSendPacket pattern not found after 60s"); + if !send_done { + log("ERROR: _ProtoSSLSendPacket pattern not found after 60s"); + } + if !anadius_done { + log("ERROR: anadius64.dll not loaded after 60s; GoOnline probe not installed"); + } 0 } @@ -377,6 +393,12 @@ unsafe fn install_detour( return; } }; + install_detour_at(addr, detour, slot, label); +} + +/// Install an inline detour at a raw address (for non-exported targets such as +/// internal anadius handlers located by module+offset). +unsafe fn install_detour_at(addr: usize, detour: *const (), slot: &AtomicUsize, label: &str) { let d = match RawDetour::new(addr as *const (), detour) { Ok(d) => d, Err(e) => { @@ -393,6 +415,43 @@ unsafe fn install_detour( log(&format!("{label} hook installed")); } +// --- M1 read-only probe: anadius GoOnline handler ------------------------- + +static ORIG_GOONLINE: AtomicUsize = AtomicUsize::new(0); + +/// Read-only detour on anadius's GoOnline handler (anadius64.dll+0x2BB90): log +/// that it was reached, then call the original unchanged. Tells us whether the +/// game even asks to go online during the "connecting" attempt. +unsafe extern "system" fn hooked_goonline(a: usize, b: usize, c: usize, d: usize) -> usize { + log(&format!("PROBE anadius GoOnline CALLED (rcx=0x{a:X} rdx=0x{b:X})")); + let orig = ORIG_GOONLINE.load(Ordering::SeqCst); + if orig != 0 { + let f: unsafe extern "system" fn(usize, usize, usize, usize) -> usize = + core::mem::transmute(orig); + f(a, b, c, d) + } else { + 0 + } +} + +/// Resolve anadius64.dll's runtime base and detour the GoOnline handler. +/// Returns true once installed (or if anadius isn't present and we should stop +/// retrying is decided by the caller). Returns false if anadius isn't loaded yet. +unsafe fn hook_anadius_probes() -> bool { + let base = match GetModuleHandleW(PCWSTR(wide("anadius64.dll").as_ptr())) { + Ok(m) => m.0 as usize, + Err(_) => return false, // not loaded yet + }; + log(&format!("anadius64.dll base = 0x{base:X}")); + install_detour_at( + base + 0x2BB90, + hooked_goonline as *const (), + &ORIG_GOONLINE, + "PROBE anadius GoOnline @ +0x2BB90", + ); + true +} + /// Detour the DNS resolvers so we see every hostname lookup. unsafe fn hook_dns() { let ws2 = match LoadLibraryW(PCWSTR(wide("ws2_32.dll").as_ptr())) {