diff --git a/openfut-hook/src/season_trace.rs b/openfut-hook/src/season_trace.rs index 4643066..42a6b15 100644 --- a/openfut-hook/src/season_trace.rs +++ b/openfut-hook/src/season_trace.rs @@ -12,7 +12,7 @@ //! flow. Targets are chosen so their copied prologues are position-independent //! (no rip-relative / rel32 in the copied bytes). -use core::sync::atomic::{AtomicUsize, Ordering}; +use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use windows_sys::Win32::System::Diagnostics::Debug::FlushInstructionCache; use windows_sys::Win32::System::LibraryLoader::GetModuleHandleA; @@ -28,6 +28,8 @@ use crate::sbc_trace::{ use crate::write_log; static REPORTS: AtomicUsize = AtomicUsize::new(0); +/// One-shot guard for the staging-only CACHE_PACKNAMES_FAILED -> SUCCESS bypass. +static BYPASS_DONE: AtomicBool = AtomicBool::new(false); unsafe fn rd_i32(addr: usize) -> Option { readable_range(addr, 4).then(|| core::ptr::read_volatile(addr as *const i32)) @@ -315,30 +317,47 @@ unsafe fn install_detour_reloc( // string ptr when byte0==0). Logs the EXACT status string delivered. Passive. static FINAL_COMPLETION_TRAMP: AtomicUsize = AtomicUsize::new(0); unsafe extern "system" fn final_completion_wrapper(ctx: usize, result: usize, r8: usize, r9: usize) -> usize { + // Read the delivered status: byte0==0 => failure with an error string at +8. + let flag = rd_u8(result); + let errstr = if flag == Some(0) { + let p = if readable_range(result + 8, 8) { + core::ptr::read_volatile((result + 8) as *const usize) + } else { + 0 + }; + rd_cstr(p, 96) + } else { + String::new() + }; let n = REPORTS.fetch_add(1, Ordering::Relaxed); if n < 64 { - let flag = rd_u8(result); - let (kind, s) = match flag { - Some(0) => { - let p = if readable_range(result + 8, 8) { - core::ptr::read_volatile((result + 8) as *const usize) - } else { - 0 - }; - ("ERROR", rd_cstr(p, 96)) - } - Some(_) => ("SUCCESS", String::from("SUCCESS")), - None => ("??", String::from("")), - }; let cbref = if readable_range(ctx + 0x18, 8) { core::ptr::read_volatile((ctx + 0x18) as *const usize) } else { 0 }; + let kind = match flag { + Some(0) => "ERROR", + Some(_) => "SUCCESS", + None => "??", + }; + let shown = if flag == Some(0) { errstr.as_str() } else { "SUCCESS" }; write_log(&format!( - "SEASONS_LOAD_CALLBACK: final kind={kind} result={s:?} flag={flag:?} ctx={ctx:#x} cbref={cbref:#x}\n" + "SEASONS_LOAD_CALLBACK: final kind={kind} result={shown:?} flag={flag:?} ctx={ctx:#x} cbref={cbref:#x}\n" )); } + // Guarded one-shot bypass (staging diagnostic only): rewrite the pack-names + // failure to SUCCESS so the offline-season load advances to + // LoadCurrentOfflineSeason. Fires only for the exact CACHE_PACKNAMES failure, + // once per process; verified by the error string before touching memory. + if flag == Some(0) + && errstr.contains("CACHE_PACKNAMES") + && readable_range(result, 1) + && !BYPASS_DONE.swap(true, Ordering::AcqRel) + { + core::ptr::write_volatile(result as *mut u8, 1u8); // take the SUCCESS branch + write_log("SEASONS_BYPASS: forced CACHE_PACKNAMES_FAILED -> SUCCESS (one-shot, staging)\n"); + } let t = FINAL_COMPLETION_TRAMP.load(Ordering::Acquire); if t == 0 { return 0; @@ -375,6 +394,25 @@ unsafe extern "system" fn stage1_completion_wrapper(param1: usize, result: usize original(param1, result, r8, r9) } +// WEBFILE_DL download start FUN_18017ff90(url, ctx): param_1 (rcx) is the C-string +// URL of the pack-names/cards-tournament-list web file. Passive capture. Its +// prologue has a rip-relative `MOV R8,[DAT_1802e6580]`, so it uses the relocating +// installer (disp32 at copied offset 7, instruction end 11). +static URL_CAPTURE_TRAMP: AtomicUsize = AtomicUsize::new(0); +unsafe extern "system" fn url_capture_wrapper(rcx: usize, rdx: usize, r8: usize, r9: usize) -> usize { + let n = REPORTS.fetch_add(1, Ordering::Relaxed); + if n < 64 { + write_log(&format!("SEASONS_WEBFILE_URL: url={:?}\n", rd_cstr(rcx, 256))); + } + let t = URL_CAPTURE_TRAMP.load(Ordering::Acquire); + if t == 0 { + return 0; + } + let original: unsafe extern "system" fn(usize, usize, usize, usize) -> usize = + core::mem::transmute(t); + original(rcx, rdx, r8, r9) +} + unsafe fn worker() { let mut base = 0usize; for _ in 0..600u32 { @@ -440,6 +478,12 @@ unsafe fn worker() { &[0x48, 0x8b, 0xc4, 0x55, 0x48, 0x8d, 0x68, 0xa1, 0x48, 0x81, 0xec, 0xc0, 0x00, 0x00, 0x00], stage1_completion_wrapper as *const () as usize, &STAGE1_COMPLETION_TRAMP, ); + install_detour_reloc( + base, 0x17ff90, "start_webfile_dl_url", 14, + &[0x48, 0x83, 0xec, 0x38, 0x4c, 0x8b, 0x05, 0xe5, 0x65, 0x16, 0x00, 0x4c, 0x8b, 0xd1], + 7, 11, + url_capture_wrapper as *const () as usize, &URL_CAPTURE_TRAMP, + ); write_log("SEASON_TRACE: all season-native traces armed\n"); }