9aecc658ad
The FUT store flashes a Browse-Packs overview on first open: the store screen ctor leaves screen+0x290 (CATEGORY_ID) at 0, and the resolver FUN_1800147f0 treats 0 as list-all, so the first render draws the group overview before the movie posts a tab ordinal. Detour the store render FUN_18007dab0 (RVA 0x7dab0): when the incoming category is 0, substitute the first present group ordinal (1) so the first frame lands on a real tab. Provably crash-safe: it writes 1 only after FUN_180014420(_, 1) (the resolver's own ordinal->group lookup, whose first arg is dead) returns non-NULL, which is exactly the resolver's non-crash precondition; the positive-invalid NULL deref at 0x14882 is thus unreachable. No group yet -> category left 0 -> Browse, still safe. Promoted like the SBC dispatch: build-armed (CLAMP_PROMOTED), no env. Signature-gated on both the detoured render and the called lookup, image-validated, installed under thread suspension, fail-closed. Only the overview flash is addressed; the empty-My-Packs entry dialog is movie-side (packed .apt) and out of CardsDLL reach (see Vault Store Resolver Guard 2026-08-19). fmt/clippy -D warnings clean both feature sets, 26 hook tests pass, x86_64-pc-windows-gnu release builds.
110 lines
4.3 KiB
Rust
110 lines
4.3 KiB
Rust
//! 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::<MODULEENTRY32W>() 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::<MODULEENTRY32W>() 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");
|
|
// The promoted SBC dispatch repair (and the evidence traces it decides on) arms
|
|
// itself from the build; its safety is the runtime signature/evidence gate. The
|
|
// remaining legacy experiment modules stay inert unless their env gate is `1`.
|
|
crate::sbc_hook::install();
|
|
crate::sbc_trace::install();
|
|
crate::sbc_dispatch::install();
|
|
crate::sbc_request_trace::install();
|
|
crate::store_entry::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");
|
|
}
|
|
}
|