feat(hook): add SBC request tracing instrumentation
- sbc_hook.rs: trace SBC submission/response flow with request IDs - sbc_request_trace.rs: capture request/response bodies for analysis - sbc_trace.rs: runtime trace buffer with structured logging Work in progress - needs validation against live FIFA 17 client
This commit is contained in:
@@ -32,6 +32,12 @@ const ABS_JUMP_LEN: usize = 14;
|
||||
const TRAMPOLINE_LEN: usize = COPY_LEN + ABS_JUMP_LEN;
|
||||
const NOTIFIER_RVA: usize = 0x17aa80;
|
||||
const NOTIFIER_COPY_LEN: usize = 15;
|
||||
const CONTROLLER_REGISTER_RVA: usize = 0x1a4a70;
|
||||
const CONTROLLER_REGISTER_COPY_LEN: usize = 15;
|
||||
const FUT_SBS_CATEGORIES_EVENT: u32 = 0x756c;
|
||||
const CONTROLLER_REGISTER_SIGNATURE: [u8; CONTROLLER_REGISTER_COPY_LEN] = [
|
||||
0x89, 0x54, 0x24, 0x10, 0x48, 0x83, 0xec, 0x28, 0x4c, 0x8d, 0x81, 0xc0, 0x00, 0x00, 0x00,
|
||||
];
|
||||
const NOTIFIER_SIGNATURE: [u8; 32] = [
|
||||
0x48, 0x89, 0x5c, 0x24, 0x08, 0x48, 0x89, 0x74, 0x24, 0x10, 0x57, 0x48, 0x83, 0xec, 0x20, 0x48,
|
||||
0x8b, 0x59, 0x58, 0x48, 0x8b, 0x71, 0x60, 0x33, 0xff, 0x48, 0x2b, 0xf3, 0xc6, 0x81, 0x88, 0x00,
|
||||
@@ -75,6 +81,7 @@ static DESERIALIZER_EXIT_M: AtomicUsize = AtomicUsize::new(0);
|
||||
static DESERIALIZER_EXIT_COUNT: AtomicUsize = AtomicUsize::new(0);
|
||||
static DESERIALIZER_EXIT_B_READY: AtomicUsize = AtomicUsize::new(usize::MAX);
|
||||
static NOTIFIER_TRAMPOLINE: AtomicUsize = AtomicUsize::new(0);
|
||||
static CONTROLLER_REGISTER_TRAMPOLINE: AtomicUsize = AtomicUsize::new(0);
|
||||
static NOTIFIER_ENTRIES: AtomicU64 = AtomicU64::new(0);
|
||||
static NOTIFIER_EXITS: AtomicU64 = AtomicU64::new(0);
|
||||
static NOTIFIER_CTX: AtomicUsize = AtomicUsize::new(0);
|
||||
@@ -521,6 +528,77 @@ unsafe fn write_notifier_entry(
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn restore_controller_register_entry(target: usize) -> bool {
|
||||
let mut old = 0u32;
|
||||
if VirtualProtect(
|
||||
target as _,
|
||||
CONTROLLER_REGISTER_COPY_LEN,
|
||||
PAGE_EXECUTE_READWRITE,
|
||||
&mut old,
|
||||
) == 0
|
||||
{
|
||||
return false;
|
||||
}
|
||||
core::ptr::copy_nonoverlapping(
|
||||
CONTROLLER_REGISTER_SIGNATURE.as_ptr(),
|
||||
target as *mut u8,
|
||||
CONTROLLER_REGISTER_COPY_LEN,
|
||||
);
|
||||
let flushed = FlushInstructionCache(
|
||||
GetCurrentProcess(),
|
||||
target as _,
|
||||
CONTROLLER_REGISTER_COPY_LEN,
|
||||
) != 0;
|
||||
let mut ignored = 0u32;
|
||||
flushed && VirtualProtect(target as _, CONTROLLER_REGISTER_COPY_LEN, old, &mut ignored) != 0
|
||||
}
|
||||
|
||||
unsafe fn write_controller_register_entry(
|
||||
target: usize,
|
||||
destination: usize,
|
||||
published: &mut bool,
|
||||
) -> Result<(), bool> {
|
||||
let mut patch = [0x90u8; CONTROLLER_REGISTER_COPY_LEN];
|
||||
patch[..ABS_JUMP_LEN].copy_from_slice(&absolute_jump(destination));
|
||||
let mut old = 0u32;
|
||||
if VirtualProtect(
|
||||
target as _,
|
||||
CONTROLLER_REGISTER_COPY_LEN,
|
||||
PAGE_EXECUTE_READWRITE,
|
||||
&mut old,
|
||||
) == 0
|
||||
{
|
||||
return Err(true);
|
||||
}
|
||||
*published = true;
|
||||
core::ptr::copy_nonoverlapping(
|
||||
patch.as_ptr(),
|
||||
target as *mut u8,
|
||||
CONTROLLER_REGISTER_COPY_LEN,
|
||||
);
|
||||
let flushed = FlushInstructionCache(
|
||||
GetCurrentProcess(),
|
||||
target as _,
|
||||
CONTROLLER_REGISTER_COPY_LEN,
|
||||
) != 0;
|
||||
let mut ignored = 0u32;
|
||||
if flushed && VirtualProtect(target as _, CONTROLLER_REGISTER_COPY_LEN, old, &mut ignored) != 0
|
||||
{
|
||||
Ok(())
|
||||
} else {
|
||||
Err(restore_controller_register_entry(target))
|
||||
}
|
||||
}
|
||||
|
||||
unsafe extern "system" fn controller_register_wrapper(controller: *mut c_void, event: u32) {
|
||||
let original: unsafe extern "system" fn(*mut c_void, u32) =
|
||||
core::mem::transmute(CONTROLLER_REGISTER_TRAMPOLINE.load(Ordering::Acquire));
|
||||
original(controller, event);
|
||||
if event == FUT_SBS_CATEGORIES_EVENT {
|
||||
crate::sbc_hook::note_sbc_controller(controller as usize);
|
||||
}
|
||||
}
|
||||
|
||||
unsafe extern "system" fn notifier_wrapper(ctx: *mut c_void) {
|
||||
NOTIFIER_ENTRIES.fetch_add(1, Ordering::Relaxed);
|
||||
let address = ctx as usize;
|
||||
@@ -552,6 +630,7 @@ unsafe extern "system" fn notifier_wrapper(ctx: *mut c_void) {
|
||||
let original: unsafe extern "system" fn(*mut c_void) =
|
||||
core::mem::transmute(NOTIFIER_TRAMPOLINE.load(Ordering::Acquire));
|
||||
original(ctx);
|
||||
crate::sbc_hook::commit_after_native_parse();
|
||||
NOTIFIER_BYTE_AFTER.store(
|
||||
address
|
||||
.checked_add(0x88)
|
||||
@@ -946,10 +1025,78 @@ unsafe fn notifier_worker() {
|
||||
crate::write_log("SBC_NOTIFIER_TRACE: report cap reached; hook remains passive\n");
|
||||
}
|
||||
|
||||
unsafe fn controller_register_worker() {
|
||||
let _pending = CodeInstallerPending;
|
||||
let mut base = 0usize;
|
||||
for _ in 0..600u32 {
|
||||
base = GetModuleHandleA(b"CardsDLL_Win64_retail.dll\0".as_ptr()) as usize;
|
||||
if base != 0 {
|
||||
break;
|
||||
}
|
||||
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||
}
|
||||
let Some(target) = target_va(base, CONTROLLER_REGISTER_RVA) else {
|
||||
crate::write_log("SBC_CONTROLLER_TRACE: target resolution failed; inactive\n");
|
||||
return;
|
||||
};
|
||||
if base == 0
|
||||
|| !valid_cards_image(base)
|
||||
|| !executable_range_in_image(base, target, CONTROLLER_REGISTER_SIGNATURE.len())
|
||||
|| core::slice::from_raw_parts(target as *const u8, CONTROLLER_REGISTER_SIGNATURE.len())
|
||||
!= CONTROLLER_REGISTER_SIGNATURE
|
||||
{
|
||||
crate::write_log("SBC_CONTROLLER_TRACE: PE/signature validation failed; inactive\n");
|
||||
return;
|
||||
}
|
||||
let Some(trampoline) = allocate_trampoline(target, CONTROLLER_REGISTER_COPY_LEN) else {
|
||||
crate::write_log("SBC_CONTROLLER_TRACE: trampoline allocation failed; inactive\n");
|
||||
return;
|
||||
};
|
||||
CONTROLLER_REGISTER_TRAMPOLINE.store(trampoline, Ordering::Release);
|
||||
let Some(_installer_gate) = acquire_patch_installer_gate() else {
|
||||
VirtualFree(trampoline as _, 0, MEM_RELEASE);
|
||||
CONTROLLER_REGISTER_TRAMPOLINE.store(0, Ordering::Release);
|
||||
crate::write_log("SBC_CONTROLLER_TRACE: installer gate timeout; inactive\n");
|
||||
return;
|
||||
};
|
||||
let mut peers = match suspend_peers(target, target) {
|
||||
Ok(peers) => peers,
|
||||
Err(_) => {
|
||||
crate::write_log(
|
||||
"SBC_CONTROLLER_TRACE: quiescence failed; terminate game if unresponsive\n",
|
||||
);
|
||||
return;
|
||||
}
|
||||
};
|
||||
let mut published = false;
|
||||
let installed = write_controller_register_entry(
|
||||
target,
|
||||
controller_register_wrapper as *const () as usize,
|
||||
&mut published,
|
||||
)
|
||||
.is_ok();
|
||||
let resumed = peers.resume_all();
|
||||
drop(_installer_gate);
|
||||
drop(_pending);
|
||||
if !installed || !resumed {
|
||||
if !published && resumed {
|
||||
VirtualFree(trampoline as _, 0, MEM_RELEASE);
|
||||
CONTROLLER_REGISTER_TRAMPOLINE.store(0, Ordering::Release);
|
||||
crate::write_log("SBC_CONTROLLER_TRACE: clean install failure; inactive\n");
|
||||
} else {
|
||||
crate::write_log("SBC_CONTROLLER_TRACE: DEGRADED state; terminate game now\n");
|
||||
}
|
||||
return;
|
||||
}
|
||||
crate::write_log("SBC_CONTROLLER_TRACE: category controller registration hook installed\n");
|
||||
}
|
||||
|
||||
fn install_notifier(enabled: bool) {
|
||||
if enabled {
|
||||
crate::write_log("SBC_NOTIFIER_TRACE: requested; deferred install starting\n");
|
||||
std::thread::spawn(|| unsafe { notifier_worker() });
|
||||
crate::write_log("SBC_CONTROLLER_TRACE: requested; deferred install starting\n");
|
||||
std::thread::spawn(|| unsafe { controller_register_worker() });
|
||||
} else {
|
||||
crate::write_log("SBC_NOTIFIER_TRACE: disabled\n");
|
||||
}
|
||||
@@ -959,7 +1106,7 @@ pub(crate) fn install() {
|
||||
let enabled = env_enabled(std::env::var("OPENFUT_SBC_TRACE").ok().as_deref());
|
||||
let notifier_enabled = env_enabled(std::env::var("OPENFUT_SBC_NOTIFIER_TRACE").ok().as_deref());
|
||||
CODE_PATCH_PENDING.store(
|
||||
enabled as usize + notifier_enabled as usize,
|
||||
enabled as usize + (notifier_enabled as usize * 2),
|
||||
Ordering::Release,
|
||||
);
|
||||
install_notifier(notifier_enabled);
|
||||
|
||||
Reference in New Issue
Block a user