//! FIFA 17 injection path (feature = "fifa17"). //! //! This is a *separate, minimal* entry point from the FIFA-23 `install_hooks`. //! FIFA 17 is a different game with different in-memory structures, so we run NONE //! of the FIFA-23 connect/LSX/origin_spy/dial logic here — that would at best //! no-op and at worst crash. For now this proves the version.dll hijack actually //! loads us into FIFA17.exe and dumps the module map, which we need to locate //! DirtySDK/ProtoSSL's cert-verify function (the next milestone: patch it so the //! secure Blaze redirector's TLS handshake succeeds against our bridge cert). //! //! Everything here is read-only except the (not-yet-enabled) cert-verify patch. use crate::write_log; use windows_sys::Win32::Foundation::{CloseHandle, INVALID_HANDLE_VALUE}; use windows_sys::Win32::System::Diagnostics::ToolHelp::{ CreateToolhelp32Snapshot, Module32FirstW, Module32NextW, MODULEENTRY32W, TH32CS_SNAPMODULE, TH32CS_SNAPMODULE32, }; use windows_sys::Win32::System::LibraryLoader::GetModuleHandleA; /// Read the SizeOfImage from a module's in-memory PE headers. unsafe fn size_of_image(base: usize) -> u32 { if base == 0 { return 0; } // DOS header -> e_lfanew (i32 @ 0x3c) -> PE header. SizeOfImage is in the // optional header at offset 0x50 from the PE signature (same for PE32/PE32+). let e_lfanew = *((base + 0x3c) as *const i32); let pe = base + e_lfanew as usize; // sanity: 'PE\0\0' if *(pe as *const u32) != 0x0000_4550 { return 0; } *((pe + 24 + 0x38) as *const u32) // opt header +0x38 = SizeOfImage } fn wide_to_string(w: &[u16]) -> String { let end = w.iter().position(|&c| c == 0).unwrap_or(w.len()); String::from_utf16_lossy(&w[..end]) } /// Enumerate loaded modules (name, base, size) via ToolHelp and log them. unsafe fn dump_modules() { let snap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, 0); if snap == INVALID_HANDLE_VALUE { write_log("fifa17: module snapshot FAILED\n"); return; } let mut me: MODULEENTRY32W = core::mem::zeroed(); me.dwSize = core::mem::size_of::() as u32; if Module32FirstW(snap, &mut me) != 0 { loop { let name = wide_to_string(&me.szModule); let base = me.modBaseAddr as usize; let size = me.modBaseSize; write_log(&format!( "fifa17: module {name:<28} base={base:#018x} size={size:#x}\n" )); me.dwSize = core::mem::size_of::() as u32; if Module32NextW(snap, &mut me) == 0 { break; } } } else { write_log("fifa17: Module32FirstW FAILED\n"); } CloseHandle(snap); } /// Worker that runs AFTER DllMain returns (loader lock released). ToolHelp and /// other loader-touching calls are unsafe under the loader lock, so we defer them /// to this thread. This is what fixed the "game exits right after DllMain" issue. unsafe extern "system" fn worker(_: *mut core::ffi::c_void) -> u32 { write_log("=== fifa17 hook: worker thread start ===\n"); let main_base = GetModuleHandleA(core::ptr::null()) as usize; let img = size_of_image(main_base); write_log(&format!( "fifa17: main exe base={main_base:#018x} SizeOfImage={img:#x}\n" )); dump_modules(); write_log("fifa17: worker complete (injection healthy)\n"); // Every SBC detour is deferred and inert unless its exact environment gate is `1`. crate::sbc_hook::install(); crate::sbc_trace::install(); crate::sbc_dispatch::install(); crate::sbc_request_trace::install(); 0 } /// Minimal FIFA-17 install. Keep DllMain itself trivial: only spawn a worker /// thread and return immediately, so we never touch the loader lock from here. pub unsafe fn install() { use windows_sys::Win32::System::Threading::CreateThread; write_log("=== fifa17 hook: DllMain ATTACH (spawning worker) ===\n"); let h = CreateThread( core::ptr::null(), 0, Some(worker), core::ptr::null(), 0, core::ptr::null_mut(), ); if h == 0 as _ { write_log("fifa17: CreateThread FAILED\n"); } }