refactor(launcher): retire FIFA 23; keep the hook game-generic by feature

FIFA 23 is not in development and was never a valid template for FIFA 17
(different game, different in-memory layout). Remove it as a build target and
as scaffolding, while preserving the per-game feature architecture so future
games plug in as new modules — never by copying retired reverse-engineering.

Hook (openfut-hook):
- Delete install_hooks_fifa23 and every FIFA23-only module: config, hooks,
  transport_watch, ssl_patch, origin_spy, tls_bypass, dial_notification, probe
  (+ probe feature), recv_hook (+ capture_baseline feature), plus the orphan
  FIFA23 LSX/Origin files lsx.rs and ea_stub.rs. ~3.6k lines; git + Vault retain
  the research.
- lib.rs is now game-generic: a per-game feature selects that game's module and
  install_hooks dispatches to it. No game feature => compile_error!("select a
  game, e.g. --features fifa17"). --features fifa17 remains the build invariant.
- Drop the crate-wide  blanket (it existed only
  to hide the compiled-but-unused FIFA23 modules). Replace with narrow, justified
  #[allow(dead_code)] on the three FIFA17 SBC RE-scaffolding items it was masking,
  so the candidate stays behavior-identical.
- connect_hook: the redirect is now always the config-driven path (openfut-common
  target from openfut.cfg); the hardcoded-loopback rewrite and its dead consts are
  gone. Removed the FIFA23-era transport_watch diagnostics from the shared
  connect/WSAConnect/ConnectEx detours. Deleted unused iat::patch_iat_in.

Launcher:
- fifa_game_dir no longer defaults to a hardcoded '.../FIFA 23' Steam path; it is
  empty by default, matching the launcher's own rule that it never invents a path
  to somebody's game install (like openfut_server_host and game_profile).
- Generalise the remaining 'FIFA 23' doc literals in config.rs / setup.rs.

Proof: fifa17 clippy -D warnings clean; no-game build fails with the documented
compile_error; launcher 75 tests pass unchanged; launcher + hook cross-build
x86_64-pc-windows-gnu; cargo fmt --check clean; zero FIFA23 symbols/literals
remain. FIFA17 armed-module set unchanged (redirect + SBC/store/season).
This commit is contained in:
funman300
2026-08-20 20:56:52 +00:00
parent 55ffbd8c7e
commit 00ad631034
21 changed files with 59 additions and 3956 deletions
+16 -207
View File
@@ -1,25 +1,21 @@
// The `fifa17` feature compiles this shared crate but activates only the FIFA-17
// injection path (fifa17.rs + sbc_*): install_hooks() routes to fifa17::install()
// and the FIFA-23 hook modules are reached solely via install_hooks_fifa23(), which
// is itself `#[cfg(not(feature = "fifa17"))]`. Those modules are therefore compiled
// but unused under `fifa17` (the linker strips them from the cdylib). Scope the
// resulting dead-code/unused-import lints to that feature so both builds stay
// `-D warnings` clean without dropping code the default (FIFA-23) build needs.
#![cfg_attr(feature = "fifa17", allow(dead_code, unused_imports))]
// openfut-hook: the version.dll proxy that injects OpenFUT's client-side
// compatibility hooks into an EA FUT client.
//
// GAME-GENERIC BY FEATURE: each supported game is its own module, selected by a
// per-game Cargo feature (currently only `fifa17`). `install_hooks` dispatches to
// the selected game's `install()`. Generic infrastructure — the version proxy,
// the connect/WSAConnect/ConnectEx redirect, IAT primitives, and the shared
// `openfut-common` config — stays game-neutral. Add a future game with its own
// `mod <game>;` behind a feature plus a dispatch arm; never by copying a retired
// game's reverse-engineering.
#[cfg(not(any(feature = "fifa17")))]
compile_error!("select a game, e.g. --features fifa17");
mod config;
mod connect_hook;
mod connectex_hook;
mod dial_notification;
#[cfg(feature = "fifa17")]
mod fifa17;
mod hooks;
mod iat;
mod origin_spy;
#[cfg(feature = "probe")]
mod probe;
#[cfg(feature = "capture_baseline")]
mod recv_hook;
#[cfg(feature = "fifa17")]
mod sbc_dispatch;
#[cfg(feature = "fifa17")]
@@ -30,16 +26,12 @@ mod sbc_request_trace;
mod sbc_trace;
#[cfg(feature = "fifa17")]
mod season_trace;
mod ssl_patch;
#[cfg(feature = "fifa17")]
mod store_entry;
mod tls_bypass;
mod transport_watch;
mod version_proxy;
use windows_sys::Win32::{
Foundation::{BOOL, HMODULE, TRUE},
Networking::WinSock::ADDRINFOA,
System::SystemServices::DLL_PROCESS_ATTACH,
};
@@ -54,21 +46,6 @@ pub(crate) fn write_log(msg: &str) {
}
}
/// Force the log to stable storage. `write_log` already opens+closes the file per line,
/// so nothing is buffered *inside our process* (a process crash can't lose a written
/// line). `sync_all` additionally flushes the OS cache to disk, for durability even
/// across a full system crash. We call this right before the dial trigger's call so the
/// pre-call log line is guaranteed on disk if the call faults.
#[allow(dead_code)]
pub(crate) fn flush_log() {
if let Ok(f) = std::fs::OpenOptions::new()
.append(true)
.open(r"C:\openfut_hook.log")
{
let _ = f.sync_all();
}
}
/// # Safety
///
/// This is the DLL entry point invoked by the Windows loader; it MUST NOT be
@@ -88,177 +65,9 @@ pub unsafe extern "system" fn DllMain(module: HMODULE, reason: u32, _: *mut ())
TRUE
}
unsafe fn install_hooks(module: HMODULE) {
// FIFA 17 path: run ONLY the minimal, FIFA-17-safe logic and skip every
// FIFA-23-specific hook below (they assume FIFA 23's memory layout).
/// Dispatch to the selected game's install path. Exactly one game feature must be
/// enabled (enforced by the crate-level `compile_error!` above).
unsafe fn install_hooks(_module: HMODULE) {
#[cfg(feature = "fifa17")]
{
let _ = module;
fifa17::install();
}
#[cfg(not(feature = "fifa17"))]
install_hooks_fifa23(module)
}
#[cfg(not(feature = "fifa17"))]
unsafe fn install_hooks_fifa23(module: HMODULE) {
write_log("openfut_hook: DllMain fired\n");
// Milestone-0 transport watch: arm (or note disarmed) from env once, up front, so
// the getaddrinfo/connect/ConnectEx detours below can log Blaze-flavored activity.
transport_watch::arm_from_env();
let ip = config::read_redirect_ip(module);
hooks::set_redirect_ip(ip);
let ga = iat::resolve(b"ws2_32.dll\0", b"getaddrinfo\0");
if !ga.is_null() {
let f: unsafe extern "system" fn(
*const u8,
*const u8,
*const ADDRINFOA,
*mut *mut ADDRINFOA,
) -> i32 = std::mem::transmute(ga);
hooks::set_real(f);
let n = iat::patch_iat(ga, hooks::hooked_getaddrinfo as *const ());
let m = iat::patch_iat_in(
b"EAWebKit.dll\0",
ga,
hooks::hooked_getaddrinfo as *const (),
);
write_log(&format!("openfut_hook: getaddrinfo IAT patched {n}+{m}\n"));
}
if ssl_patch::patch_main_exe_cert_verify() {
write_log("ssl: main exe cert-verify patched\n");
} else {
write_log("ssl: main exe cert-verify NOT FOUND\n");
}
if ssl_patch::patch_eawebkit_cert_verify() {
write_log("ssl: EAWebKit cert-verify patched\n");
} else {
write_log("ssl: EAWebKit cert-verify deferred\n");
}
if connect_hook::install_inline_connect_hook() {
write_log("connect: inline-hooked\n");
} else {
write_log("connect: hook FAILED\n");
}
let wp = iat::resolve(b"ws2_32.dll\0", b"WSAConnect\0");
if !wp.is_null() {
let f: unsafe extern "system" fn(
usize,
*const u8,
i32,
*const (),
*const (),
*const (),
*const (),
) -> i32 = std::mem::transmute(wp);
connect_hook::set_real_wsa_connect(f);
iat::patch_iat(wp, connect_hook::hooked_wsa_connect as *const ());
write_log("connect: WSAConnect IAT patched\n");
}
if connectex_hook::install_wsaioctl_hook() {
write_log("connectex: WSAIoctl inline-hooked\n");
} else {
write_log("connectex: WSAIoctl hook FAILED\n");
}
// RE instrumentation: passive logging detours on FIFA's in-process online-flow
// functions (GoOnline, GetInternetConnectedState, event deserializers) to see
// where FIFA stalls after our pushed LSX events. Deferred until anadius loads.
#[cfg(feature = "probe")]
{
probe::install_probes_deferred();
write_log("probe: deferred install scheduled\n");
}
// recv/send hooks removed — LSX is now handled by the native openfut-bridge
// LSX server (port 3216), so in-process interception is no longer needed.
//
// Except in the `capture_baseline` build: with the LSX redirect off, FIFA talks
// to anadius directly, and these hooks log anadius's real LSX request/response
// frames (pass-through, no emulation) so we can diff them against our bridge.
#[cfg(feature = "capture_baseline")]
{
if recv_hook::install_recv_hook() {
write_log("CAP: recv inline-hooked\n");
} else {
write_log("CAP: recv hook FAILED\n");
}
if recv_hook::install_send_hook() {
write_log("CAP: send inline-hooked\n");
} else {
write_log("CAP: send hook FAILED\n");
}
}
macro_rules! hook_iat {
($dll:expr, $sym:expr, $setter:ident, $handler:expr, $ty:ty) => {{
let ptr = iat::resolve($dll, $sym);
if !ptr.is_null() {
let f: $ty = std::mem::transmute(ptr);
origin_spy::$setter(f);
iat::patch_iat(ptr, $handler as *const ());
"ok"
} else {
"miss"
}
}};
}
let ra = hook_iat!(
b"advapi32.dll\0",
b"RegQueryValueExA\0",
set_real_reg_a,
origin_spy::hooked_reg_query_a,
unsafe extern "system" fn(isize, *const u8, *mut u32, *mut u32, *mut u8, *mut u32) -> i32
);
let rw = hook_iat!(
b"advapi32.dll\0",
b"RegQueryValueExW\0",
set_real_reg_w,
origin_spy::hooked_reg_query_w,
unsafe extern "system" fn(isize, *const u16, *mut u32, *mut u32, *mut u8, *mut u32) -> i32
);
let ma = hook_iat!(
b"kernel32.dll\0",
b"OpenMutexA\0",
set_real_mutex_a,
origin_spy::hooked_open_mutex_a,
unsafe extern "system" fn(u32, i32, *const u8) -> isize
);
let mw = hook_iat!(
b"kernel32.dll\0",
b"OpenMutexW\0",
set_real_mutex_w,
origin_spy::hooked_open_mutex_w,
unsafe extern "system" fn(u32, i32, *const u16) -> isize
);
write_log(&format!(
"origin_spy: RegA={ra} RegW={rw} MutexA={ma} MutexW={mw}\n"
));
let cv = iat::resolve(b"crypt32.dll\0", b"CertVerifyCertificateChainPolicy\0");
if !cv.is_null() {
let f: unsafe extern "system" fn(*const u8, *const (), *const (), *mut u32) -> BOOL =
std::mem::transmute(cv);
tls_bypass::set_real(f);
iat::patch_iat(cv, tls_bypass::hooked_cert_verify_chain_policy as *const ());
iat::patch_iat_in(
b"EAWebKit.dll\0",
cv,
tls_bypass::hooked_cert_verify_chain_policy as *const (),
);
iat::patch_iat_in(
b"winhttp.dll\0",
cv,
tls_bypass::hooked_cert_verify_chain_policy as *const (),
);
iat::patch_iat_in(
b"wininet.dll\0",
cv,
tls_bypass::hooked_cert_verify_chain_policy as *const (),
);
}
fifa17::install();
}