diag(fifa17): probe LoadOfflineSeasons async completions (result string capture)
Adds passive field-logging detours on the FutCompetitionServiceImpl::LoadOfflineSeasons
async chain resolved by static RE:
final completion FUN_1800ffe90 -> logs the exact status string delivered to the
AS LoadSeasons_Complete callback ("SUCCESS" vs error string at result+8);
stage-1 completion FUN_180106240 -> logs whether the first async stage's
status (+0x1c) is ok or CACHE_PACKNAMES_FAILED.
Read-only; safe bounded C-string reader (rd_cstr).
This commit is contained in:
@@ -35,6 +35,23 @@ unsafe fn rd_i32(addr: usize) -> Option<i32> {
|
|||||||
unsafe fn rd_u8(addr: usize) -> Option<u8> {
|
unsafe fn rd_u8(addr: usize) -> Option<u8> {
|
||||||
readable_range(addr, 1).then(|| core::ptr::read_volatile(addr as *const u8))
|
readable_range(addr, 1).then(|| core::ptr::read_volatile(addr as *const u8))
|
||||||
}
|
}
|
||||||
|
/// Read a NUL-terminated string safely (bounded, only reads mapped bytes).
|
||||||
|
unsafe fn rd_cstr(addr: usize, max: usize) -> String {
|
||||||
|
if addr == 0 || !readable_range(addr, 1) {
|
||||||
|
return String::from("<unreadable>");
|
||||||
|
}
|
||||||
|
let mut out = Vec::new();
|
||||||
|
let mut i = 0;
|
||||||
|
while i < max && readable_range(addr + i, 1) {
|
||||||
|
let b = core::ptr::read_volatile((addr + i) as *const u8);
|
||||||
|
if b == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
out.push(b);
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
String::from_utf8_lossy(&out).into_owned()
|
||||||
|
}
|
||||||
|
|
||||||
/// Generic passive detour: overwrite the first `copy_len` bytes of `target` (which
|
/// Generic passive detour: overwrite the first `copy_len` bytes of `target` (which
|
||||||
/// MUST be whole, position-independent instructions) with an absolute jump to
|
/// MUST be whole, position-independent instructions) with an absolute jump to
|
||||||
@@ -291,6 +308,73 @@ unsafe fn install_detour_reloc(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FutCompetitionServiceImpl::LoadOfflineSeasons FINAL completion (FUN_1800ffe90):
|
||||||
|
// delivers the result to the AS callback LoadSeasons_Complete via
|
||||||
|
// FUN_18019fb30->slot0x20(vm,"_global",cbref, "SUCCESS" | errString). param_1 = the
|
||||||
|
// completion ctx (cbref at +0x18), param_2 = result obj (byte0=ok flag; +8 = error
|
||||||
|
// 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 {
|
||||||
|
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("<result unreadable>")),
|
||||||
|
};
|
||||||
|
let cbref = if readable_range(ctx + 0x18, 8) {
|
||||||
|
core::ptr::read_volatile((ctx + 0x18) as *const usize)
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
|
write_log(&format!(
|
||||||
|
"SEASONS_LOAD_CALLBACK: final kind={kind} result={s:?} flag={flag:?} ctx={ctx:#x} cbref={cbref:#x}\n"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let t = FINAL_COMPLETION_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(ctx, result, r8, r9)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadOfflineSeasons STAGE-1 async completion (FUN_180106240): fails with
|
||||||
|
// "CACHE_PACKNAMES_FAILED" when result==0 or *(i32)(result+0x1c)!=0; else chains
|
||||||
|
// the next async stage. Logs whether the first async stage succeeded. Passive.
|
||||||
|
static STAGE1_COMPLETION_TRAMP: AtomicUsize = AtomicUsize::new(0);
|
||||||
|
unsafe extern "system" fn stage1_completion_wrapper(param1: usize, result: usize, r8: usize, r9: usize) -> usize {
|
||||||
|
let n = REPORTS.fetch_add(1, Ordering::Relaxed);
|
||||||
|
if n < 64 {
|
||||||
|
if result == 0 {
|
||||||
|
write_log("SEASONS_STAGE1: result=NULL -> CACHE_PACKNAMES_FAILED\n");
|
||||||
|
} else {
|
||||||
|
let status = rd_i32(result + 0x1c);
|
||||||
|
let verdict = if status == Some(0) { "ok(chain next)" } else { "CACHE_PACKNAMES_FAILED" };
|
||||||
|
write_log(&format!(
|
||||||
|
"SEASONS_STAGE1: result={result:#x} status(+0x1c)={} -> {verdict}\n",
|
||||||
|
status.map(|x| x.to_string()).unwrap_or_else(|| "??".into()),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let t = STAGE1_COMPLETION_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(param1, result, r8, r9)
|
||||||
|
}
|
||||||
|
|
||||||
unsafe fn worker() {
|
unsafe fn worker() {
|
||||||
let mut base = 0usize;
|
let mut base = 0usize;
|
||||||
for _ in 0..600u32 {
|
for _ in 0..600u32 {
|
||||||
@@ -346,6 +430,16 @@ unsafe fn worker() {
|
|||||||
&[0x40, 0x55, 0x56, 0x57, 0x48, 0x83, 0xec, 0x30, 0x48, 0xc7, 0x44, 0x24, 0x20, 0xfe, 0xff, 0xff, 0xff],
|
&[0x40, 0x55, 0x56, 0x57, 0x48, 0x83, 0xec, 0x30, 0x48, 0xc7, 0x44, 0x24, 0x20, 0xfe, 0xff, 0xff, 0xff],
|
||||||
load_offline_async_wrapper as *const () as usize, &LOAD_OFFLINE_ASYNC_TRAMP,
|
load_offline_async_wrapper as *const () as usize, &LOAD_OFFLINE_ASYNC_TRAMP,
|
||||||
);
|
);
|
||||||
|
install_detour(
|
||||||
|
base, 0xffe90, "LoadOfflineSeasons_final_completion", 16,
|
||||||
|
&[0x48, 0x89, 0x5c, 0x24, 0x08, 0x57, 0x48, 0x83, 0xec, 0x30, 0x80, 0x3a, 0x00, 0x48, 0x8b, 0xda],
|
||||||
|
final_completion_wrapper as *const () as usize, &FINAL_COMPLETION_TRAMP,
|
||||||
|
);
|
||||||
|
install_detour(
|
||||||
|
base, 0x106240, "LoadOfflineSeasons_stage1_completion", 15,
|
||||||
|
&[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,
|
||||||
|
);
|
||||||
write_log("SEASON_TRACE: all season-native traces armed\n");
|
write_log("SEASON_TRACE: all season-native traces armed\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user