M2: confirm worker-thread + event-driven gate (architectural wall)
Add a bounded manual stack-scan in the GoOnline detour (FIFA-frame finder). Result: zero FIFA23.exe frames, sp ~2.4KB below stack top => GoOnline runs on an anadius worker thread (queued), not FIFA's thread. Three-way corroboration that the gate is an async "online established" event anadius (offline-only) never pushes; FIFA waits/retries. No handler-response flip can cross it. Documented in connection-gate-findings.md. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+66
-1
@@ -31,7 +31,7 @@ use windows::Win32::System::ProcessStatus::{GetModuleInformation, MODULEINFO};
|
||||
use windows::Win32::System::SystemInformation::GetLocalTime;
|
||||
use windows::Win32::System::SystemServices::DLL_PROCESS_ATTACH;
|
||||
use windows::Win32::System::Threading::{
|
||||
CreateThread, GetCurrentProcess, Sleep, THREAD_CREATION_FLAGS,
|
||||
CreateThread, GetCurrentProcess, GetCurrentThreadStackLimits, Sleep, THREAD_CREATION_FLAGS,
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -185,6 +185,7 @@ unsafe extern "system" fn hooked(
|
||||
unsafe extern "system" fn init_thread(_: *mut c_void) -> u32 {
|
||||
// Connection capture first. Listen on the LSX port (gate 1, so the launcher
|
||||
// bootstrap succeeds) and on the redirect port (for external TLS/Blaze).
|
||||
store_exe_range();
|
||||
start_listener(LSX_PORT, "LSX");
|
||||
start_listener(LOCAL_PORT, "BLZ");
|
||||
hook_dns();
|
||||
@@ -418,6 +419,69 @@ unsafe fn install_detour_at(addr: usize, detour: *const (), slot: &AtomicUsize,
|
||||
// --- M1 read-only probe: anadius GoOnline handler -------------------------
|
||||
|
||||
static ORIG_GOONLINE: AtomicUsize = AtomicUsize::new(0);
|
||||
static EXE_BASE: AtomicUsize = AtomicUsize::new(0);
|
||||
static EXE_SIZE: AtomicUsize = AtomicUsize::new(0);
|
||||
static STACK_LOGGED: AtomicUsize = AtomicUsize::new(0);
|
||||
|
||||
/// Record FIFA23.exe's base + size so we can recognise its frames in a backtrace.
|
||||
unsafe fn store_exe_range() {
|
||||
if let Ok(h) = GetModuleHandleW(PCWSTR::null()) {
|
||||
let mut mi = MODULEINFO::default();
|
||||
if GetModuleInformation(
|
||||
GetCurrentProcess(),
|
||||
h,
|
||||
&mut mi,
|
||||
core::mem::size_of::<MODULEINFO>() as u32,
|
||||
)
|
||||
.is_ok()
|
||||
{
|
||||
EXE_BASE.store(h.0 as usize, Ordering::SeqCst);
|
||||
EXE_SIZE.store(mi.SizeOfImage as usize, Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Scan the raw stack for values that land in FIFA23.exe (the game-side
|
||||
/// online-flow return addresses that called into GoOnline). Unwind-free, so it
|
||||
/// survives the detour trampolines that break RtlCaptureStackBackTrace.
|
||||
/// One-shot to avoid log spam.
|
||||
unsafe fn log_fifa_callstack(tag: &str) {
|
||||
if STACK_LOGGED.swap(1, Ordering::SeqCst) != 0 {
|
||||
return;
|
||||
}
|
||||
let base = EXE_BASE.load(Ordering::SeqCst);
|
||||
let size = EXE_SIZE.load(Ordering::SeqCst);
|
||||
if base == 0 || size == 0 {
|
||||
log(&format!("{tag} stack scan skipped (exe range unknown)"));
|
||||
return;
|
||||
}
|
||||
// Address of a local ~= current rsp; the stack grows down, so callers'
|
||||
// return addresses sit at HIGHER addresses. Scan upward, but NEVER past the
|
||||
// committed stack top (reading beyond it faults — that crashed the game).
|
||||
let mut low: usize = 0;
|
||||
let mut high: usize = 0;
|
||||
GetCurrentThreadStackLimits(&mut low, &mut high);
|
||||
let probe: usize = 0;
|
||||
let sp = &probe as *const usize as usize;
|
||||
let end = high; // scan the whole rest of the stack (committed, safe)
|
||||
let mut line = format!(
|
||||
"{tag} stack[low=0x{low:X} high=0x{high:X} sp=0x{sp:X}] FIFA23.exe refs:"
|
||||
);
|
||||
let mut count = 0;
|
||||
let mut p = sp;
|
||||
while p + 8 <= end {
|
||||
let val = *(p as *const usize);
|
||||
if val >= base && val < base + size {
|
||||
line.push_str(&format!(" +0x{:X}", val - base));
|
||||
count += 1;
|
||||
if count >= 40 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
p += 8;
|
||||
}
|
||||
log(&line);
|
||||
}
|
||||
|
||||
/// M2 flip on anadius's GoOnline handler (anadius64.dll+0x2BB90). The original
|
||||
/// handler is `mov rcx,rdx; lea r8,[+0xADD73]; lea rdx,[+0xADE64 = "0"]; call
|
||||
@@ -425,6 +489,7 @@ static ORIG_GOONLINE: AtomicUsize = AtomicUsize::new(0);
|
||||
/// "0" (offline). We replicate it but pass "1" (+0xAF530 = the connected value),
|
||||
/// so GoOnline reports online, then return success (al=1).
|
||||
unsafe extern "system" fn hooked_goonline(_a: usize, b: usize, _c: usize, _d: usize) -> usize {
|
||||
log_fifa_callstack("GoOnline");
|
||||
let base = ANADIUS_BASE.load(Ordering::SeqCst);
|
||||
if base != 0 {
|
||||
log("FLIP GoOnline -> reporting online (\"1\")");
|
||||
|
||||
Reference in New Issue
Block a user