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:
@@ -79,7 +79,9 @@ unsafe extern "system" fn worker(_: *mut core::ffi::c_void) -> u32 {
|
|||||||
));
|
));
|
||||||
dump_modules();
|
dump_modules();
|
||||||
write_log("fifa17: worker complete (injection healthy)\n");
|
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_hook::install();
|
||||||
crate::sbc_trace::install();
|
crate::sbc_trace::install();
|
||||||
crate::sbc_dispatch::install();
|
crate::sbc_dispatch::install();
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
//! Guarded FIFA 17 SBC completion dispatch and passive event tracing.
|
//! Guarded FIFA 17 SBC completion dispatch and passive event tracing.
|
||||||
//!
|
//!
|
||||||
//! `OPENFUT_SBC_DISPATCH=1` permits one narrowly-scoped repair per native
|
//! The repair is a PROMOTED feature: it is armed by the build itself, never by an
|
||||||
//! deserializer generation. The default and `OPENFUT_SBC_DISPATCH_TRACE=1` paths
|
//! environment variable (see [`REPAIR_PROMOTED`]). Safety lives in the runtime
|
||||||
//! are behavior-preserving.
|
//! evidence gate, not in a flag.
|
||||||
|
|
||||||
use core::ffi::c_void;
|
use core::ffi::c_void;
|
||||||
use core::sync::atomic::{AtomicBool, AtomicU64, AtomicUsize, Ordering};
|
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 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;
|
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 REPAIR_ENABLED: AtomicBool = AtomicBool::new(false);
|
||||||
static COMPLETION_TRAMPOLINE: AtomicUsize = AtomicUsize::new(0);
|
static COMPLETION_TRAMPOLINE: AtomicUsize = AtomicUsize::new(0);
|
||||||
static EVENT_TRAMPOLINE: AtomicUsize = AtomicUsize::new(0);
|
static EVENT_TRAMPOLINE: AtomicUsize = AtomicUsize::new(0);
|
||||||
@@ -691,22 +715,12 @@ unsafe fn worker() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn install() {
|
pub(crate) fn install() {
|
||||||
let repair =
|
// Promoted: armed by the build. No environment variable participates in the
|
||||||
crate::sbc_trace::env_enabled(std::env::var("OPENFUT_SBC_DISPATCH").ok().as_deref());
|
// decision, so every launch path behaves identically.
|
||||||
let trace = repair
|
REPAIR_ENABLED.store(REPAIR_PROMOTED, Ordering::Release);
|
||||||
|| crate::sbc_trace::env_enabled(
|
crate::write_log(
|
||||||
std::env::var("OPENFUT_SBC_DISPATCH_TRACE").ok().as_deref(),
|
"SBC_DISPATCH: repair ARMED (promoted); strict native evidence gate enabled\n",
|
||||||
);
|
);
|
||||||
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"
|
|
||||||
});
|
|
||||||
std::thread::spawn(|| unsafe { worker() });
|
std::thread::spawn(|| unsafe { worker() });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1148,7 +1148,9 @@ fn install_notifier(enabled: bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn install() {
|
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 =
|
let dispatch_trace =
|
||||||
dispatch_repair || env_enabled(std::env::var("OPENFUT_SBC_DISPATCH_TRACE").ok().as_deref());
|
dispatch_repair || env_enabled(std::env::var("OPENFUT_SBC_DISPATCH_TRACE").ok().as_deref());
|
||||||
let enabled =
|
let enabled =
|
||||||
|
|||||||
Reference in New Issue
Block a user