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.
This commit is contained in:
funman300
2026-08-19 02:45:43 +00:00
parent c3addde9b1
commit 94feaec63f
3 changed files with 39 additions and 21 deletions
+33 -19
View File
@@ -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 AG 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() });
}