From 94feaec63f84ceab4747e00df36c7ecd1c20685d Mon Sep 17 00:00:00 2001 From: funman300 Date: Wed, 19 Aug 2026 02:45:43 +0000 Subject: [PATCH] Promote the FIFA17 SBC dispatch repair: armed by the build, not by env Retail Gates A-G passed on the pinned CardsDLL build (4706a881), and the repair has been live-proven repeatedly, so it is now a promoted feature. Arming it from OPENFUT_SBC_DISPATCH meant any launch that did not export it (Steam, the launcher Launch button, a bare umu-run) silently lost the SBC screen to the known response-to-deserializer dispatch defect, leaving a harness script as the only working entry point. REPAIR_PROMOTED is now a build constant with a compile-time contract, and both install sites derive from it: sbc_dispatch::install always arms, and sbc_trace::install derives the parser/notifier/controller-registration traces from it because those traces ARE the repair decision inputs, not optional diagnostics. Promotion weakens no check. Safety stays in the runtime evidence gate rather than a flag: the worker still validates the exact CardsDLL signatures before installing a detour, and decide() still requires the transport sentinel status, the pinned category-response vtable captured while the response object was provably live, balanced parser counts on the one parser thread, this generation notifier having entered AND returned, the captured controller/model identity, and one repair per deserializer generation. An unrecognised build leaves native execution untouched. Rollback is a file swap (restore the previous version.dll via the hook harness backup), the documented client rollback path, deliberately not an env kill-switch. fmt clean, strict clippy clean on default and fifa17 features, 22 tests pass, release cross-build to x86_64-pc-windows-gnu produces artifact 3641d581. --- openfut-hook/src/fifa17.rs | 4 ++- openfut-hook/src/sbc_dispatch.rs | 52 ++++++++++++++++++++------------ openfut-hook/src/sbc_trace.rs | 4 ++- 3 files changed, 39 insertions(+), 21 deletions(-) diff --git a/openfut-hook/src/fifa17.rs b/openfut-hook/src/fifa17.rs index 0e19754..312eeb3 100644 --- a/openfut-hook/src/fifa17.rs +++ b/openfut-hook/src/fifa17.rs @@ -79,7 +79,9 @@ unsafe extern "system" fn worker(_: *mut core::ffi::c_void) -> u32 { )); dump_modules(); write_log("fifa17: worker complete (injection healthy)\n"); - // Every SBC detour is deferred and inert unless its exact environment gate is `1`. + // 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(); diff --git a/openfut-hook/src/sbc_dispatch.rs b/openfut-hook/src/sbc_dispatch.rs index c2838d7..3c88ae0 100644 --- a/openfut-hook/src/sbc_dispatch.rs +++ b/openfut-hook/src/sbc_dispatch.rs @@ -1,8 +1,8 @@ //! Guarded FIFA 17 SBC completion dispatch and passive event tracing. //! -//! `OPENFUT_SBC_DISPATCH=1` permits one narrowly-scoped repair per native -//! deserializer generation. The default and `OPENFUT_SBC_DISPATCH_TRACE=1` paths -//! are behavior-preserving. +//! The repair is a PROMOTED feature: it is armed by the build itself, never by an +//! environment variable (see [`REPAIR_PROMOTED`]). Safety lives in the runtime +//! evidence gate, not in a flag. use core::ffi::c_void; use core::sync::atomic::{AtomicBool, AtomicU64, AtomicUsize, Ordering}; @@ -44,6 +44,30 @@ const EVENT_SIGNATURE: [u8; EVENT_COPY_LEN] = [ type CompletionFn = unsafe extern "system" fn(*mut c_void, *mut c_void) -> usize; type EventDispatchFn = unsafe extern "system" fn(*mut c_void, u32, *mut c_void) -> usize; +/// The guarded native dispatch repair is PROMOTED: armed by the build, never by an +/// environment variable. Retail Gates A–G passed on the pinned CardsDLL build, so a +/// deployed hook must repair the SBC completion on every launch path (Steam, the +/// launcher, or a bare `umu-run`) with nothing to export. +/// +/// Promotion does NOT weaken any check — every guard stays in the runtime evidence +/// gate rather than in a flag. `worker` still validates the exact CardsDLL +/// signatures before installing a detour, and [`decide`] still requires the +/// transport sentinel status, the pinned category-response vtable captured while +/// the response object was provably live, balanced parser counts on the one parser +/// thread, this generation's notifier having entered AND returned, the captured +/// controller/model identity, and one repair per deserializer generation. Anything +/// unrecognised leaves native execution untouched. +/// +/// Rollback is a file swap (restore the previous `version.dll`) — the documented +/// client rollback path — deliberately not an env kill-switch. +pub(crate) const REPAIR_PROMOTED: bool = true; + +/// Compile-time contract: the repair stays armed by the build. Flipping this back to +/// an env gate would silently cost a normal launch (Steam or the launcher) its SBC +/// screen, which is exactly the regression promotion removed — so it must be a +/// deliberate, visible change here rather than a missing variable at runtime. +const _: () = assert!(REPAIR_PROMOTED); + static REPAIR_ENABLED: AtomicBool = AtomicBool::new(false); static COMPLETION_TRAMPOLINE: AtomicUsize = AtomicUsize::new(0); static EVENT_TRAMPOLINE: AtomicUsize = AtomicUsize::new(0); @@ -691,22 +715,12 @@ unsafe fn worker() { } pub(crate) fn install() { - let repair = - crate::sbc_trace::env_enabled(std::env::var("OPENFUT_SBC_DISPATCH").ok().as_deref()); - let trace = repair - || crate::sbc_trace::env_enabled( - std::env::var("OPENFUT_SBC_DISPATCH_TRACE").ok().as_deref(), - ); - REPAIR_ENABLED.store(repair, Ordering::Release); - if !trace { - crate::write_log("SBC_DISPATCH: disabled\n"); - return; - } - crate::write_log(if repair { - "SBC_DISPATCH: repair ARMED; strict native evidence gate enabled\n" - } else { - "SBC_DISPATCH: passive trace requested; repair disabled\n" - }); + // Promoted: armed by the build. No environment variable participates in the + // decision, so every launch path behaves identically. + REPAIR_ENABLED.store(REPAIR_PROMOTED, Ordering::Release); + crate::write_log( + "SBC_DISPATCH: repair ARMED (promoted); strict native evidence gate enabled\n", + ); std::thread::spawn(|| unsafe { worker() }); } diff --git a/openfut-hook/src/sbc_trace.rs b/openfut-hook/src/sbc_trace.rs index cf5482b..18c8ee1 100644 --- a/openfut-hook/src/sbc_trace.rs +++ b/openfut-hook/src/sbc_trace.rs @@ -1148,7 +1148,9 @@ fn install_notifier(enabled: bool) { } pub(crate) fn install() { - let dispatch_repair = env_enabled(std::env::var("OPENFUT_SBC_DISPATCH").ok().as_deref()); + // The repair's evidence traces (parser, notifier, controller registration) are + // its decision inputs, so they follow the promoted repair, not an env var. + let dispatch_repair = crate::sbc_dispatch::REPAIR_PROMOTED; let dispatch_trace = dispatch_repair || env_enabled(std::env::var("OPENFUT_SBC_DISPATCH_TRACE").ok().as_deref()); let enabled =