From a1885e4f4a8430ee02a546ac498175c22ffdc1bf Mon Sep 17 00:00:00 2001 From: Alexandros Mandravillis Date: Wed, 16 Sep 2026 17:25:12 +0300 Subject: [PATCH] bios: replace the stale SCPH5552 profile bios/SCPH5552.toml on master predates the address-model schema. It describes the image with bios_vectors and bios_aliases and has no address_model copy windows, no install_slots and no runtime_exports, while bios/SCPH1001.toml uses all three and the recompiler reads them. It also carries no [program.image] pin, so the declared-identity gate cannot refuse a wrong revision. Replace it with a profile in the current schema. Its structure now matches SCPH1001.toml key for key: - [program.image] pins SHA-256 1faaa18f... (v3.0 01/06/97, CRC32 D786F0B9). - address_model and runtime_exports carry over from SCPH1001 unchanged. - The five install slots from SCPH1001 carry over with len and resume. - rom keeps master's region-qualified name; bios_rom_alias.h resolves either convention by model token. The carry-over rests on a direct comparison of the two dumps, each matching its declared identity: ROM 0x00000-0x17FFF (boot, Kernel Part 1 and 2) is byte-identical, and every install slot maps through Kernel Part 2 into identical bytes. The shell at 0x18000-0x42FFF differs, which is why the seed corpus stays kernel-only; every seed in it lies below ROM 0x18000. The install slots were measured on a live SCPH-1001 boot and are not re-measured on SCPH-5552. The profile records that evidence level beside them. This changes one file and adds no BIOS support. Savestate isolation and the netplay handshake for further retail images are separate work. --- bios/SCPH5552.toml | 177 ++++++++++++++++++++++++++++++++++++--------- 1 file changed, 143 insertions(+), 34 deletions(-) diff --git a/bios/SCPH5552.toml b/bios/SCPH5552.toml index c137b0b2..a943496e 100644 --- a/bios/SCPH5552.toml +++ b/bios/SCPH5552.toml @@ -1,44 +1,153 @@ -# SCPH-5552 (EUR, v3.0 01/06/97) -# Boot + kernel are byte-identical to SCPH1001 (534 seeds kept). -# Shell (ROM 0x18000+, 568 seeds) differs — runs via dirty-RAM interpreter. +# bios/SCPH5552.toml — BIOS build profile for the retail Sony SCPH-5502/5552 +# (Europe) v3.0 01/06/97 image ("System ROM Version 3.0 01/06/97 E", +# SHA-256 1faaa18fa820a0225e488d9f086296b8e6c46df739666093987ff7d8fd352c09). +# +# Derived from bios/SCPH1001.toml. ROM 0x00000-0x17FFF (boot + Kernel Part 1/2) +# is byte-identical to the US SCPH-1001 v2.2 image, so every kernel anchor +# below (copy windows, install slots, deliver_event_ret, shell entry) carries +# over unchanged. Verified by direct comparison of the two dumps, each matching +# its declared identity (SCPH-1001 CRC32 37157331, SCPH-5552 CRC32 D786F0B9): +# 0x00000-0x0FFFF and 0x10000-0x17FFF identical; the shell at 0x18000-0x42FFF +# differs in 155874 bytes. +# +# Because the shell DIFFERS from the US image, this profile has no shell seeds: +# recompiler/seeds/phase2_ghidra_seeds_SCPH5552.json is the SCPH1001 corpus +# filtered through tools/filter_bios_seeds.py to the kernel region, and every +# seed in it lies below ROM 0x18000. The shell window runs through the dirty-RAM +# interpreter (build with -DPSX_SHELLWIN_INTERP=ON) until an EU-shell seed +# corpus exists. +# +# The .BIN image itself stays untracked (bios/* gitignore rule) — profiles are +# the only tracked contents of this directory. [program] -name = "SCPH5552 BIOS (Europe)" -id = "SCPH-5552" -rom = "bios/EUR-PSX-SCPH5552.bin" +name = "Sony SCPH-5552 BIOS (Europe, v3.0 01/06/97)" +id = "SCPH-5552" +rom = "bios/EUR-PSX-SCPH5552.bin" load_address = "0xBFC00000" -entry_pc = "0xBFC00000" -text_size = "0x80000" +entry_pc = "0xBFC00000" +text_size = "0x80000" + +# Declared identity. The emitter refuses to generate from any other image +# (main_bios.cpp's declared-identity gate). Without this pin a different +# revision generates silently: the kernel seeds above name v3.0 function starts +# only because the kernel matches SCPH-1001 v2.2 byte for byte, and a dump that +# differs there would break that premise without any error. +# +# This is v3.0, 1997-01-06: CRC32 D786F0B9, 512 KiB. +[program.image] +sha256 = "1faaa18fa820a0225e488d9f086296b8e6c46df739666093987ff7d8fd352c09" +license = "proprietary" +redistributable = false [recompiler] -seeds = "recompiler/seeds/phase2_ghidra_seeds_SCPH5552.json" -out_dir = "generated" +seeds = "recompiler/seeds/phase2_ghidra_seeds_SCPH5552.json" +out_dir = "generated" out_stem = "SCPH5552" -strict = true +strict = true -# SCPH5552 shares SCPH1001's A0/B0/C0 dispatch tables and kernel layout. -# The BIOS vector table ROM addresses and aliases are identical. -[[recompiler.bios_vectors]] -ram_addr = "0xA0" -index_reg = 9 -table_rom_addr = "0xBFC04300" -table_count = 0xC1 -table_ram_addr = "0x200" +# The BIOS's boot-time bulk copies of CODE out of ROM into RAM. Single source +# of truth for every relocation window in the recompiler AND in the C it +# emits (the generated dispatch's normalize(), the kernel-bless guard, the +# game/shell-overlap tests). Each entry is a CLAIM that the copy is +# byte-verbatim; the runtime's kernel-bless memcmp enforces it for the +# bless window. All bounds are [lo, hi) with hi EXCLUSIVE. +# +# dispatch_key = "ram": functions in the window are keyed by RAM address +# (normalize folds ROM -> RAM). +# dispatch_key = "rom": functions keep their ROM key (normalize folds the +# RAM alias back to ROM). +[recompiler.address_model] +normalize_mask = "0x1FFFFFFF" -[[recompiler.bios_vectors]] -ram_addr = "0xB0" -index_reg = 9 -table_rom_addr = "0xBFC10874" -table_count = 0x44 -table_ram_addr = "0x874" +[[recompiler.address_model.copy]] +name = "Kernel Part 2" +rom_lo = "0x1FC10000" +rom_hi = "0x1FC18000" +ram_lo = "0x00000500" +runtime_base = "0x00000500" # KUSEG — the kernel executes uncached-region code cached at 0x500 +dispatch_key = "ram" +kernel_bless = true -[[recompiler.bios_vectors]] -ram_addr = "0xC0" -index_reg = 9 -table_rom_addr = "0xBFC10674" -table_count = 0x1E -table_ram_addr = "0x674" +[[recompiler.address_model.copy]] +name = "Shell" +rom_lo = "0x1FC18000" +rom_hi = "0x1FC43000" +ram_lo = "0x00030000" +runtime_base = "0x80030000" # KSEG0 — LoadRunShell jumps to 0x80030000 +dispatch_key = "rom" +kernel_bless = false +# NOTE (do not "fix" without evidence): runtime/src/bios_hle.c documents the +# actual LoadRunShell copy as 0x67FF0 bytes (ROM 0xBFC18000 -> RAM 0x30000.. +# 0x97FF0). Every pre-profile window constant covered only 0x2B000 of that, +# and dirty_ram_interp.c tracks live RAM in [0x5B000,0x8F000) as an open +# class-B bug. Widening this window is a REAL BEHAVIOR CHANGE requiring its +# own evidence-backed commit — it is not a parameterization cleanup. -[[recompiler.bios_aliases]] -ram_addr = "0xCF0" -target_key = "0x641C" +# Kernel-RAM RANGES the BIOS and the game's Psy-Q libapi patchers overwrite at +# runtime (CLAUDE.md rule 18; docs/dynamic_handler_install.md). The emitter +# plants a compare-against-ROM hook at each range start so the guest's patched +# instructions execute; the runtime excludes the range from the kernel-bless +# memcmp and resumes native at the range end, so only the patched words +# interpret. +# +# `resume` says how the compiled body picks up again: "jalr" for the classic +# 4-word lui/addiu/jalr/nop stub (the call returns to ram_addr+0x10), +# "fallthrough" when the patched words ARE the function, "none" when the patch +# jumps out and never comes back. +# +# EVIDENCE LEVEL — read before relying on these. The five ranges were measured +# on a live SCPH-1001 boot (bios/SCPH1001.toml, Breath of Fire III, Psy-Q SDK +# 3.70). They are carried here, NOT re-measured on a live SCPH-5552 boot. The +# basis is that each range maps through Kernel Part 2 (ROM 0x10000 -> RAM +# 0x500) into bytes verified identical between the two images, so the same +# libapi patchers land on the same code. That is a strong inference, not an +# observation. Re-measure on a 5552 boot before treating a divergence here as a +# recompiler bug. + +[[recompiler.install_slots]] +ram_addr = "0x00000C88" # exception handler: register-save prologue +len = "0x30" # shifted two words + an inserted mfc0 v0,Cause +resume = "fallthrough" # the patched words are the function itself + +[[recompiler.install_slots]] +ram_addr = "0x00000CF0" # SIO data-byte handler dispatch slot +len = "0x10" # lui/addiu/jalr/nop over ROM zeros +resume = "jalr" # the stub's call returns to 0x0D00 + +[[recompiler.install_slots]] +ram_addr = "0x00004964" # pad driver clear routine, 11 words NOP'd +len = "0x2C" # (RemovePatchPad / ChgclrPAD family) +resume = "fallthrough" + +[[recompiler.install_slots]] +ram_addr = "0x00004D98" # _patch_card2 payload: jalr into boot EXE +len = "0x14" # 0x8017EB6C; the ra landing word is NOP'd too, +resume = "fallthrough" # so the body resumes past it, not at +0x10 + +[[recompiler.install_slots]] +ram_addr = "0x00006444" # _patch_card payload: the kernel's card +len = "0x10" # handler replaced by jr into boot EXE +resume = "none" # 0x8017EAA0 — a jr never returns here + +# NOT declared: RAM 0x0500, one word of kernel_trampoline_0 cleared to zero +# (ROM `lui t2, 0`). It differs on every boot, but what writes it is not yet +# identified, and a range declared on a guess would tell the bless verifier to +# stop checking a word it should be checking. Rule 14: unknown is acceptable, +# guessing is not. Undeclared means that body keeps interpreting, exactly as +# it does today. + +# Per-image anchors couriered into the generated C (psx_bios_image) for the +# runtime HLE tier. Values are FACTS about this image, not tunables: the +# shell entry is where LoadRunShell (ROM 0xBFC06FF0) jumps after its copy; +# deliver_event_ret is the instruction after the kernel DeliverEvent loop's +# `jalr v0` (kernel RAM 0x1718), the $ra a delivered event callback returns +# through. deliver_event_ret lies in the verified-identical kernel (RAM 0x1720, +# ROM 0x11220). shell_entry_phys is shell RAM, whose contents differ, but the +# address is LoadRunShell's jump target and LoadRunShell itself (ROM 0x06FF0) +# is in the identical region, so the target is the same. Omit a key on a +# BIOS that has no such anchor — the runtime then treats that HLE feature as +# structurally unavailable instead of firing on a wrong address. +[recompiler.runtime_exports] +shell_entry_phys = "0x00030000" +deliver_event_ret = "0x80001720"