diff --git a/docs/connection-gate-findings.md b/docs/connection-gate-findings.md index ca1d73b..dedf6a3 100644 --- a/docs/connection-gate-findings.md +++ b/docs/connection-gate-findings.md @@ -231,7 +231,26 @@ FIFA23.exe via anadius's detour table, then `callers` to the online-flow; or (b) find anadius's LSX event-send path and reverse the online-event format. Both are deep. `TODO/CONFIRM`. -### M2 directions (superseded — see above) +### Path A attempt: reach the FIFA-side online-flow (blocked with live toolkit) +Goal: find `EbisuSDK::GoOnline` in FIFA23.exe → `callers` → the game's online-flow +→ read what event it waits on. Every angle our live-memory toolkit offers is +blocked: +- **String xref:** FIFA23.exe contains no `"GoOnline"` string (typed SDK call, + not a string-built command). +- **Call-stack from the handler:** GoOnline runs on an anadius worker thread; a + bounded stack scan finds zero FIFA frames. +- **Detour scan (`jmpscan`):** scanning FIFA23.exe for function-entry `E9` jumps + leaving the module yields ~3875 hits — overwhelmingly false positives, because + the 505 MB image is mostly embedded *data* (not code), and the real detours + don't cleanly cluster. (A .text-section-only scan would help but the chain + after — isolate GoOnline → callers → event format → emulate — remains long and + each link is gated by SDK abstraction / anadius indirection / worker threads.) + +**Verdict:** crossing this gate to *playable* FUT is research-grade. It needs an +interactive disassembler (IDA/Ghidra GUI, human-driven) to trace the EbisuSDK +online-flow, and then a full EA-online + Blaze emulator. The live-memory toolkit +(string/xref/disasm/callers/read/jmpscan) has been exhausted for the FIFA side. +The clean-room spec (this document) is the finished, valuable artifact. - Check whether the flags at `+0xCAB1A` / `+0xCAB1B` are settable via anadius config / a hidden option (cheapest flip). - Else out-detour `GetInternetConnectedState` in our `version.dll` to force the diff --git a/tools/protossl-scan/src/main.rs b/tools/protossl-scan/src/main.rs index c4924ab..5866abc 100644 --- a/tools/protossl-scan/src/main.rs +++ b/tools/protossl-scan/src/main.rs @@ -183,6 +183,20 @@ fn main() { // callers [pid|name] // Find direct call/jmp sites that target an address — walks up the call // graph (e.g. from a connect helper to the code that gates it). + // jmpscan [module] [pid|name] + // Find E9 rel32 jumps inside a module whose target leaves the module — + // i.e. inline-detour entry points (MS Detours hooks). Default module: + // FIFA23.exe; targets reveal the detoured EbisuSDK functions. + Some("jmpscan") => { + let modname = args.get(1).cloned().unwrap_or_else(|| "FIFA23".to_string()); + let pid = resolve_pid(args.get(2).map(|s| s.as_str())); + let process = open_for_read(pid); + let modules = enumerate_modules(pid); + run_jmpscan(process, &modules, &modname); + unsafe { + let _ = CloseHandle(process); + } + } Some("callers") => { let arg = match args.get(1) { Some(a) => a.clone(), @@ -471,6 +485,64 @@ fn dump_neighbours(process: HANDLE, at: usize, modules: &[ModuleInfo]) { /// Scan app-module executable memory for near `call`/`jmp` (E8/E9 + rel32) /// instructions whose target is `target`. This walks UP the call graph — e.g. /// from a connect helper to the code that decides whether to call it. +/// Scan a module's executable memory for `E9 rel32` near-jumps whose target is +/// OUTSIDE the module — the signature of an inline detour (function entry patched +/// to jump to an external trampoline). Reports source -> target for each. +fn run_jmpscan(process: HANDLE, modules: &[ModuleInfo], modname: &str) { + let want = modname.to_ascii_lowercase(); + let want = want.strip_suffix(".dll").unwrap_or(&want); + let want = want.strip_suffix(".exe").unwrap_or(want); + let m = match modules.iter().find(|m| { + let n = m.name.to_ascii_lowercase(); + n.starts_with(want) + }) { + Some(m) => m, + None => { + println!("module '{modname}' not found"); + return; + } + }; + let base = m.base; + let end = m.base + m.size; + println!("== jmpscan {} [0x{base:X}..0x{end:X}] ==\n", m.name); + + let allow = [(base, end)]; + let mut hits: Vec<(usize, usize)> = Vec::new(); + walk_regions(process, true, 4, Some(&allow), |chunk_base, bytes| { + if bytes.len() < 5 { + return; + } + for i in 1..=bytes.len() - 5 { + // Real detours patch a function entry, which MSVC pads with int3 + // (0xCC) just before it. Requiring that preceding 0xCC filters out + // the flood of 0xE9 data bytes that aren't real instructions. + if bytes[i] != 0xE9 || bytes[i - 1] != 0xCC { + continue; + } + let rel = i32::from_le_bytes([bytes[i + 1], bytes[i + 2], bytes[i + 3], bytes[i + 4]]); + let src = chunk_base + i; + let tgt = (src + 5).wrapping_add(rel as i64 as usize); + if tgt < base || tgt >= end { + hits.push((src, tgt)); + } + } + }); + + hits.sort_unstable(); + hits.dedup(); + if hits.is_empty() { + println!(" no out-of-module E9 jumps found"); + return; + } + println!("-- {} out-of-module E9 jump(s) (detour entry candidates) --", hits.len()); + for (src, tgt) in hits.iter().take(80) { + println!(" {} -> {}", describe(*src, modules), describe(*tgt, modules)); + } + if hits.len() > 80 { + println!(" ... and {} more", hits.len() - 80); + } +} + fn run_callers(process: HANDLE, modules: &[ModuleInfo], target: usize) { println!("== protossl-scan : callers of 0x{target:X} =="); println!("({})\n", describe(target, modules));