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:
+16
-207
@@ -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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user