From 696ffbd1f66510b68a351ee4d8f75599dba89f88 Mon Sep 17 00:00:00 2001 From: Matthew Stanley <1379tech@gmail.com> Date: Sat, 9 May 2026 13:15:33 -0700 Subject: [PATCH] Persist BIOS memory-card copies + dev/prod memcard layout The COPY/DELETE flows were silently no-oping the SIO write protocol: 60 dispatches per copy were missing the 3-instruction predicate fn at 0xBFC0989C (the WRITE-command sibling of 0xBFC09890), so the BIOS's write-handler fork test returned junk via uninitialized v0 and the BIOS skipped sending 0x57 over SIO entirely. The in-memory card cache appeared updated (visual UI worked, leave/re-enter showed copies) but nothing reached memcard_write_sector or the .mcd files. Also reorganize memcard directory resolution: production builds put cards next to the exe; dev builds (CMake build tree detected) keep them in the project root next to bios/ so a runtime rebuild doesn't delete them. --memcard-dir still overrides. --- generated/SCPH1001_dispatch.c | 8 +++-- generated/SCPH1001_full.c | 23 +++++++++++++- recompiler/seeds/phase2_ghidra_seeds.json | 9 ++++-- runtime/src/main.cpp | 38 +++++++++++++---------- 4 files changed, 56 insertions(+), 22 deletions(-) diff --git a/generated/SCPH1001_dispatch.c b/generated/SCPH1001_dispatch.c index 3c5f7cf5..601e1d3a 100644 --- a/generated/SCPH1001_dispatch.c +++ b/generated/SCPH1001_dispatch.c @@ -1,7 +1,7 @@ /* AUTO-GENERATED by psxrecomp-bios --emit-full. DO NOT EDIT. * * BIOS SHA256: 71af94d1e47a68c11e8fdb9f8368040601514a42a5a399cda48c7d3bff1e99d3 - * Dispatch entries: 4390 + * Dispatch entries: 4391 */ #include "cpu_state.h" @@ -1176,6 +1176,7 @@ extern void func_1FC09720_cont_BFC09834(CPUState* cpu); extern void func_1FC09720_cont_BFC09848(CPUState* cpu); extern void func_1FC09720_cont_BFC0985C(CPUState* cpu); extern void func_1FC09890(CPUState* cpu); +extern void func_1FC0989C(CPUState* cpu); extern void func_1FC098A8(CPUState* cpu); extern void func_1FC098A8_cont_BFC098C0(CPUState* cpu); extern void func_1FC098A8_cont_BFC098E0(CPUState* cpu); @@ -4416,7 +4417,7 @@ typedef struct { PsxRecompFunc func; } DispatchEntry; -static const DispatchEntry dispatch_table[4390] = { +static const DispatchEntry dispatch_table[4391] = { { 0x00000500u, func_00000500 }, { 0x00000510u, func_00000510 }, { 0x00000520u, func_00000520 }, @@ -5575,6 +5576,7 @@ static const DispatchEntry dispatch_table[4390] = { { 0x1FC09848u, func_1FC09720_cont_BFC09848 }, { 0x1FC0985Cu, func_1FC09720_cont_BFC0985C }, { 0x1FC09890u, func_1FC09890 }, + { 0x1FC0989Cu, func_1FC0989C }, { 0x1FC098A8u, func_1FC098A8 }, { 0x1FC098C0u, func_1FC098A8_cont_BFC098C0 }, { 0x1FC098E0u, func_1FC098A8_cont_BFC098E0 }, @@ -8839,7 +8841,7 @@ void psx_dispatch(CPUState* cpu, uint32_t addr) { fntrace_record(cpu, addr); cpu->pc = 0; uint32_t phys = normalize(addr); - int lo = 0, hi = 4390 - 1; + int lo = 0, hi = 4391 - 1; int found = 0; while (lo <= hi) { int mid = (lo + hi) / 2; diff --git a/generated/SCPH1001_full.c b/generated/SCPH1001_full.c index e49da8c9..8941ee43 100644 --- a/generated/SCPH1001_full.c +++ b/generated/SCPH1001_full.c @@ -1,7 +1,7 @@ /* AUTO-GENERATED by psxrecomp-bios --emit-full. DO NOT EDIT. * * BIOS SHA256: 71af94d1e47a68c11e8fdb9f8368040601514a42a5a399cda48c7d3bff1e99d3 - * Functions: 1275 + * Functions: 1276 */ #include "cpu_state.h" @@ -187,6 +187,7 @@ void func_1FC09540(CPUState* cpu); void func_1FC09598(CPUState* cpu); void func_1FC09720(CPUState* cpu); void func_1FC09890(CPUState* cpu); +void func_1FC0989C(CPUState* cpu); void func_1FC098A8(CPUState* cpu); void func_1FC09914(CPUState* cpu); void func_1FC0994C(CPUState* cpu); @@ -43585,6 +43586,26 @@ label_BFC09890: } } +void func_1FC0989C(CPUState* cpu) { + debug_server_log_call_entry(0x1FC0989Cu); + int psx_delay_BFC098A0 = 0; +label_BFC0989C: +#ifdef PSX_ENABLE_BLOCK_CYCLES + psx_advance_cycles(3u); +#endif + psx_check_interrupts(cpu); + /* 0xBFC0989C: 38820051 xori $v0, $a0, 0x51 */ + cpu->gpr[2] = cpu->gpr[4] ^ 0x51u; + /* 0xBFC098A0: 03E00008 jr $ra */ + psx_delay_BFC098A0 = 1; + /* 0xBFC098A4: 0002102B sltu $v0, $zero, $v0 */ + cpu->gpr[2] = (uint32_t)(cpu->gpr[0] < cpu->gpr[2] ? 1 : 0); + if (psx_delay_BFC098A0) { + psx_delay_BFC098A0 = 0; + return; + } +} + void func_1FC098A8(CPUState* cpu) { debug_server_log_call_entry(0x1FC098A8u); int psx_delay_BFC098B8 = 0; diff --git a/recompiler/seeds/phase2_ghidra_seeds.json b/recompiler/seeds/phase2_ghidra_seeds.json index 351518a4..1f65291e 100644 --- a/recompiler/seeds/phase2_ghidra_seeds.json +++ b/recompiler/seeds/phase2_ghidra_seeds.json @@ -796,8 +796,13 @@ }, { "address": "0xBFC09890", - "label": "card_copy_dispatch_miss_2026_05_05_safe", - "rationale": "Runtime dispatch miss: 120 hits during COPY. BANDAID per user permission. Bisect 2026-05-05 confirmed safe alone, but pair with 0xBFC0989C causes freeze and 0xBFC0989C alone causes crash (misaligned dispatch from caller 0xBFC09790). Suspected mid-function label, not true function entry. NOT seeding 0xBFC0989C." + "label": "card_cmd_is_not_read", + "rationale": "3-instruction predicate function: xori $v0,$a0,0x52; jr $ra; sltu $v0,$0,$v0. Returns ($a0 != 0x52 READ). Sibling of 0xBFC0989C." + }, + { + "address": "0xBFC0989C", + "label": "card_cmd_is_not_write_2026_05_09", + "rationale": "3-instruction predicate function: xori $v0,$a0,0x51; jr $ra; sltu $v0,$0,$v0. Returns ($a0 != 0x51 WRITE). Sibling of 0xBFC09890. Discovery missed it because it sits between 0xBFC09898 (end of 0xBFC09890) and 0xBFC098A8 (next discovered fn). Called via JALR at 0xBFC09788 in func_1FC09598 — without this seed, JALR dispatches to unknown, write predicate returns junk via uninitialized v0, BIOS COPY silently no-ops the SIO write protocol (60 misses per copy, no 0x57 ever sent). Earlier bisect (2026-05-05) said this crashed; that was before the predicate-init fix and dispatch-miss class fixes. Retry 2026-05-09." }, { "address": "0xBFC098A8", diff --git a/runtime/src/main.cpp b/runtime/src/main.cpp index c872b5c7..48fc2a9e 100644 --- a/runtime/src/main.cpp +++ b/runtime/src/main.cpp @@ -92,27 +92,33 @@ static std::filesystem::path resolve_memcard_dir(const char* requested, const char* argv0) { namespace fs = std::filesystem; std::error_code ec; + + // 1. Explicit --memcard-dir always wins. if (requested && requested[0]) return fs::path(requested); - fs::path absolute_bios = fs::absolute(bios_path, ec); - if (ec) absolute_bios = bios_path; - fs::path project_root = absolute_bios.parent_path().parent_path(); - if (fs::exists(project_root / "card1.mcd", ec) || - fs::exists(project_root / "card2.mcd", ec)) - return project_root; - - fs::path cwd = fs::current_path(); - if (fs::exists(cwd / "card1.mcd", ec) || fs::exists(cwd / "card2.mcd", ec)) - return cwd; - + // 2. Locate exe directory (production layout anchor). + fs::path exe_dir; if (argv0 && argv0[0]) { - fs::path exe_dir = fs::absolute(argv0, ec).parent_path(); - if (fs::exists(exe_dir / "card1.mcd", ec) || - fs::exists(exe_dir / "card2.mcd", ec)) - return exe_dir; + exe_dir = fs::absolute(argv0, ec).parent_path(); + if (ec) exe_dir.clear(); + } + if (exe_dir.empty()) exe_dir = fs::current_path(); + + // 3. Dev-build detection: exe lives inside a CMake build tree. Keep + // cards in the project root (next to bios/) so a runtime rebuild + // never blows them away. + bool is_dev_build = fs::exists(exe_dir / "CMakeFiles", ec) || + fs::exists(exe_dir / "CMakeCache.txt", ec) || + fs::exists(exe_dir.parent_path() / "CMakeCache.txt", ec); + if (is_dev_build) { + fs::path absolute_bios = fs::absolute(bios_path, ec); + if (ec) absolute_bios = bios_path; + fs::path project_root = absolute_bios.parent_path().parent_path(); + return project_root; } - return cwd; + // 4. Production: cards sit next to the exe. + return exe_dir; } static void shutdown_runtime(void) {