diff --git a/.github/workflows/cli-release.yml b/.github/workflows/cli-release.yml
index 0da8c6ac..be601fa1 100644
--- a/.github/workflows/cli-release.yml
+++ b/.github/workflows/cli-release.yml
@@ -1,9 +1,18 @@
name: CLI release
+# Release packaging ONLY. Deliberately does NOT run on pull_request or on
+# pushes to master.
+#
+# It ran on both until 2026-07-25 and was a net negative: the Windows `build`
+# job failed often enough that a red check stopped carrying information, so it
+# was routinely ignored -- at which point it is pure noise plus CI minutes. A
+# check nobody trusts is worse than no check, because it still costs attention.
+#
+# What remains is the part that does real work: attaching the CLI package to a
+# published GitHub release, plus workflow_dispatch so it can be run by hand
+# before cutting one. If per-PR verification comes back, it should come back as
+# something fast and reliable enough to be believed.
on:
- pull_request:
- push:
- branches: [master]
workflow_dispatch:
release:
types: [published]
@@ -31,6 +40,11 @@ jobs:
- name: Build CLI package
shell: msys2 {0}
run: python tools/build_cli.py release
+ - name: Test boot path handling
+ shell: msys2 {0}
+ run: |
+ cmake --build recompiler/build-cli --target cli_boot_path_test --parallel 2
+ ctest --test-dir recompiler/build-cli -R '^cli_boot_path_test$' --output-on-failure
- name: Smoke test packaged CLI
shell: msys2 {0}
run: dist/psxrecomp-cli-windows-x86_64/psxrecomp.exe --help
@@ -40,6 +54,21 @@ jobs:
path: dist/psxrecomp-cli-*.zip
if-no-files-found: error
+ linux-boot-path-test:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v6
+ with:
+ submodules: recursive
+ - name: Configure boot path test
+ run: cmake -S recompiler -B recompiler/build-boot-path-test -DBUILD_TESTING=ON
+ - name: Build boot path test
+ run: cmake --build recompiler/build-boot-path-test --target cli_boot_path_test --parallel 2
+ - name: Run boot path test
+ run: >-
+ ctest --test-dir recompiler/build-boot-path-test
+ -R '^cli_boot_path_test$' --output-on-failure
+
publish-release-assets:
if: github.event_name == 'release'
needs: build
diff --git a/.gitignore b/.gitignore
index 8c859153..f239b16a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,11 @@
# BIOS ROM (copyrighted, not distributable)
-bios/
+bios/*
+!bios/*.toml
+# OpenBIOS is MIT — the one BIOS image that is legal to track (see
+# THIRD_PARTY_ATTRIBUTION.md; pin recorded in bios/OpenBIOS.toml; notice
+# shipped alongside as bios/OpenBIOS.LICENSE).
+!bios/openbios.bin
+!bios/*.LICENSE
# DuckStation — upstream source is a git submodule pointing at stenzek/duckstation.
# Our custom changes live in tools/duckstation/psxrecomp_oracle.patch (committed).
@@ -35,7 +41,7 @@ release-stage/
# Vendored upstream sources (cloned locally, not checked in)
beetle-psx/
-# Prebuilt SDL2 / dep zips (large, regenerable)
+# Explicit-fallback SDL2 / dep zips (large, regenerable)
sdl2-dev.zip
sdl2-msvc/
diff --git a/.gitmodules b/.gitmodules
index c467250f..4ba55f45 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,3 +1,7 @@
[submodule "lib/recomp-net"]
path = lib/recomp-net
url = https://github.com/TechnicallyComputers/recomp-net.git
+[submodule "recomp-ui"]
+ path = recomp-ui
+ url = https://github.com/mstan/recomp-ui.git
+ branch = master
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 0d31e8bb..ac8ab37a 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -131,10 +131,28 @@ Correctness is demonstrated, not asserted:
- **A decoder / codegen change requires a playthrough check**, not just a clean
build: cosim/lockstep first, then run the affected area.
+### Automated tests — run these first
+
+```sh
+cmake -S recompiler -B recompiler/build -G Ninja -DCMAKE_BUILD_TYPE=Release
+cmake --build recompiler/build
+cd recompiler/build && ctest --output-on-failure
+```
+
+29 tests, under five seconds, no BIOS dump or disc image required. See
+[`docs/TESTING.md`](docs/TESTING.md) for running individual tests, what the
+suite covers, and the three known-failing tests that are deliberately not
+registered.
+
+If you add a test, register it with `ctest` in the same commit. An unregistered
+test cannot fail, and a test that cannot fail is not a test.
+
### Regression checklist
-A framework change should not break a game that worked before it. When practical,
-build against the known-working game repos above and confirm, for each:
+The automated suite does not cover whether a game still runs; only playing one
+does. A framework change should not break a game that worked before it. When
+practical, build against the known-working game repos above and confirm, for
+each:
- The game **boots**.
- The **attract demo** plays without issue.
diff --git a/ENHANCEMENTS.md b/ENHANCEMENTS.md
index 5c7dfff2..88b4f0ae 100644
--- a/ENHANCEMENTS.md
+++ b/ENHANCEMENTS.md
@@ -209,8 +209,8 @@ Findings (tracked for the next work cycle):
has no sprite-tag ws hooks (ws work parked at 4:3).
**Policy gates confirmed already in-tree (no code change needed for the merge):**
-- Vulkan is hidden by default: launcher offers only Software<->OpenGL
- (launcher.cpp:347/776); PSX_ENABLE_VULKAN defaults OFF (runtime.cmake:484);
+- Vulkan is hidden by default: the shared launcher offers only
+ Software<->OpenGL; PSX_ENABLE_VULKAN defaults OFF (runtime.cmake);
runtime downgrades renderer=vulkan -> opengl when not compiled (main.cpp:2463).
Vulkan stays a dev/CLI-only backend (--renderer vulkan on a VK-enabled build).
- Widescreen carries an EXPERIMENTAL tag in the launcher.
diff --git a/ISSUES.md b/ISSUES.md
index 2dd155d5..d494c6ee 100644
--- a/ISSUES.md
+++ b/ISSUES.md
@@ -513,45 +513,6 @@ sites aren't, it's an emit gap (fix in code_generator.cpp). If
neighbors are absent too, it's a discovery gap (fix in function
discovery seeds). Either way, then close Issue #3 alongside.
-## Issue #6 — Launcher art has rough cutout edges (memory cards + controllers)
-
-**Status:** open, cosmetic — deferred
-**Date opened:** 2026-06-12
-**Phase:** Launcher initiative (UI polish)
-
-### Symptom
-
-This issue tracked cosmetic problems in the removed in-tree launcher asset
-pipeline. It is retained only as historical context.
-
-### Cause
-
-`FloodTransparent` uses a binary alpha decision (`max(R,G,B) < thresh`
-→ alpha 0, else keep). There is no feathering of the boundary band, so
-the object silhouette inherits the threshold's hard step. The memory
-card and (grey) controller bodies are closer in luminance to the dark
-mockup background than the disc is, so the same threshold leaves more
-fringe on them.
-
-### Fix options (later)
-
-- Soft alpha ramp across a luminance band (`t_lo..t_hi`) instead of a
- hard cutoff, applied to the flooded boundary pixels.
-- Or supersample: crop at 2–4× from the mockup, knock out, then
- downscale with high-quality bicubic so the edge anti-aliases.
-- Or hand-mask the four assets once in an image editor (cleanest, but
- manual).
-- Best long-term: replace the mockup-derived crops with proper source
- renders (transparent PNGs) — the `decorator: image(...)` pipeline is
- already in place, so it's a drop-in.
-
-### Notes
-
-Tooling: this historical note referred to the removed in-tree launcher asset
-pipeline. The active launcher path is downstream `recomp-ui`.
-
----
-
## Issue #7 — sljit live execution is unvalidated (pure-live save-load wedge)
**Status:** open, root-caused — fix in progress (branch `feat/sljit-backend`)
diff --git a/README.md b/README.md
index 467c3a12..2178391d 100644
--- a/README.md
+++ b/README.md
@@ -4,120 +4,83 @@
# PSXRecomp
-Generic static recompiler framework for PlayStation 1: MIPS R3000A to C to
-native x64.
+**A general-purpose static recompiler for the PlayStation 1.** It turns a PS1
+disc into a native executable — MIPS R3000A translated to C, compiled to x64,
+linked against a hardware-accurate runtime. Not an emulator: the game becomes a
+program your CPU runs directly.
+
+Titles brought up on it ship as standalone builds with widescreen, mods, live
+language switching and a launcher. They can run on either the bundled,
+open-source **OpenBIOS** or a compatible **retail BIOS** supplied by the player;
+OpenBIOS is the default, so a retail BIOS dump is not normally required. The
+framework is general-purpose: bringing up a new game is a matter of
+configuration and reverse engineering, not new engine work.
+
+
+
+  Tomba! |
+  Tomba! 2 |
+  Ape Escape |
+
+
+  Mega Man X4 |
+  Mega Man X5 |
+  Mega Man X6 |
+
+
Background on the original prototype:
[I Built a PS1 Static Recompiler With No Prior Experience (and Claude Code)](https://1379.tech/i-built-a-ps1-static-recompiler-with-no-prior-experience-and-claude-code/)
-[](https://www.youtube.com/watch?v=CID9oVhgCyY)
+## Games
-## How to use PSXRecomp
+Each game is its own repository that pins a framework commit as a submodule and
+ships its own playable release with an in-app launcher. Validation and feature
+coverage continue to improve per title.
-PSXRecomp takes a PlayStation disc image and BIOS and creates a recompilation
-project containing C source and build scripts.
+| Game | Repository | Latest build | Notes |
+|---|---|---|---|
+| *Tomba!* | [TombaRecomp](https://github.com/mstan/TombaRecomp) | [releases](https://github.com/mstan/TombaRecomp/releases/latest) | Widescreen, supersampling, save/load, mod packages. |
+| *Tomba! 2* | [Tomba2Recomp](https://github.com/mstan/Tomba2Recomp) | [releases](https://github.com/mstan/Tomba2Recomp/releases/latest) | Multi-track disc support; adaptive widescreen through 21:9. |
+| *Ape Escape* | [ApeEscapeRecomp](https://github.com/mstan/ApeEscapeRecomp) | [releases](https://github.com/mstan/ApeEscapeRecomp/releases/latest) | Widescreen to 21:9, memory-card save/load, dual-analog. |
+| *Mega Man X4* | [MegaManX4Recomp](https://github.com/mstan/MegaManX4Recomp) | [releases](https://github.com/mstan/MegaManX4Recomp/releases/latest) | Playable; 2D widescreen. |
+| *Mega Man X5* | [MegaManX5Recomp](https://github.com/mstan/MegaManX5Recomp) | [releases](https://github.com/mstan/MegaManX5Recomp/releases/latest) | Playable; 2D widescreen. |
+| *Mega Man X6* | [MegaManX6Recomp](https://github.com/mstan/MegaManX6Recomp) | [releases](https://github.com/mstan/MegaManX6Recomp/releases/latest) | Playable; stages, controller, save/load; 2D widescreen to 21:9. |
+| *Tsumu Light* | [TsumuLightRecomp](https://github.com/mstan/TsumuLightRecomp) | [releases](https://github.com/mstan/TsumuLightRecomp/releases/latest) | Japanese-only title (SLPS-02253); first consumer of [live language switching](#swap-language-on-the-fly). |
+| *Xenogears* — **community** | [OpokXeno/xenogears-recomp](https://github.com/OpokXeno/xenogears-recomp) | — | Independent project by [@OpokXeno](https://github.com/OpokXeno), who also contributed widescreen cull-site work upstream. |
-### Generate a project with the released CLI
+Each game repo carries its own build/run instructions, keyboard/controller
+mappings, and per-game settings. **This repository builds the framework and a
+standalone BIOS runtime supporting OpenBIOS and a compatible retail BIOS** —
+see [Release Package](#release-package) below.
-1. Download `psxrecomp-cli-windows-x86_64.zip` from
- [Releases](https://github.com/mstan/psxrecomp/releases).
-2. Extract the whole zip to a folder. Keep its contents together.
-3. Open PowerShell in that folder and run:
-
-```powershell
-.\psxrecomp.exe build `
- --disc "C:\Games\My Game\game.cue" `
- --bios "C:\BIOS\SCPH1001.BIN" `
- --output "C:\Projects\MyGameRecomp"
-```
-
-Use the `.cue` file when a game has one, and keep its `.bin` track files beside
-it. Single-file `.bin` and `.iso` images are also accepted.
-
-The output folder contains:
-
-- generated C source for the game and BIOS;
-- `game.toml`, which you can edit for game-specific settings;
-- `CMakeLists.txt` and build scripts; and
-- a local copy of the PSXRecomp runtime source needed by the project.
-
-The downloaded CLI is self-contained. You do not need to install Python or
-build this repository to generate a project.
-
-### Build the generated project
-
-Install CMake, Ninja, a C/C++ compiler, and SDL2 development files. Then run:
-
-```powershell
-powershell -ExecutionPolicy Bypass -File "C:\Projects\MyGameRecomp\build.ps1"
-```
-
-The generated project also includes a shell build script for macOS and Linux:
-
-```sh
-sh /path/to/MyGameRecomp/build.sh
-```
-
-The ready-made CLI release is currently for 64-bit Windows. You can build the
-CLI from source on another operating system using the instructions below.
-
-The generated project is a practical starting point, not a promise that every
-game works without game-specific fixes. PSX games can load extra code and use
-hardware in ways that require additional configuration or development.
-
-Use only disc and BIOS files you obtained legally. PSXRecomp does not include
-them. Generated game and BIOS source is derived from those files, so do not
-redistribute it.
-
-### Build the CLI from source
-
-You need Git, Python 3, CMake, Ninja, and a C++20 compiler.
-
-```sh
-git clone --recurse-submodules https://github.com/mstan/psxrecomp.git
-cd psxrecomp
-python tools/build_cli.py release
-```
-
-The ready-to-use CLI archive is written to `dist/`. To package debug binaries
-instead, run `python tools/build_cli.py debug`.
-
-On Linux/macOS source checkouts, the same development setup can be driven by:
-
-```sh
-sh tools/setup_dev.sh
-```
-
-That script builds the CLI/recompiler tools and, when BIOS/generated sources are
-available, the BIOS-only runtime. Game projects should still be generated with
-`psxrecomp build` and built from their generated `build.sh`.
-
-> **Where the project is headed.** Development so far has been **breadth-first**:
-> stand up as many games as possible and get them into a playable alpha, proving
-> the framework generalizes. That phase has largely delivered — seven titles now
-> run and ship public builds (see [Games](#games)). The project is now pivoting to
-> a **depth / optimization** phase: pushing each game toward 100% static coverage,
-> tightening timing accuracy, driving load times toward zero, and hardening the
-> renderer and audio paths. Expect the fleet to get *faster and more accurate*
-> rather than *wider* from here.
+Bringing up a title of your own? Start with
+[`CONTRIBUTING.md`](CONTRIBUTING.md) and open an issue — community projects are
+listed here alongside the rest.
## What It Is
PSXRecomp translates PS1 MIPS binaries into C, then compiles that C as a
native executable linked against a PS1 hardware runtime. The v4 architecture
-recompiles the real `SCPH1001.BIN` BIOS and runs it as the kernel — that
-**low-level (LLE) recompiled BIOS is the foundation and the correctness oracle.**
-Everything is architected LLE-first: accuracy comes first, and convenience is
-layered on top, opt-in, never underneath.
+links two recompiled low-level BIOS backends: the bundled OpenBIOS and a
+compatible retail BIOS (currently `SCPH1001.BIN`). Whichever one the player
+selects runs as the kernel; that **low-level (LLE) recompiled BIOS is the
+foundation and the correctness oracle.** Everything is architected LLE-first:
+accuracy comes first, and convenience is layered on top, opt-in, never
+underneath.
Three things sit on that foundation:
- **An optional HLE tier.** A high-level BIOS layer can be laid over the
- recompiled kernel to skip the boot sequence and service a few BIOS calls
- directly — a player-facing convenience and optimization, enabled by default
- but fully opt-out (`[runtime] bios_hle = false`). Anything it doesn't
- implement falls straight through to the recompiled BIOS, so the LLE path stays
- load-bearing and remains the oracle every accuracy check runs against.
+ selected recompiled kernel — OpenBIOS or retail BIOS — to skip the boot
+ sequence and service a few BIOS calls directly. It is a player-facing
+ convenience and optimization, enabled by default but fully opt-out
+ (`[runtime] bios_hle = false`). Anything it doesn't implement falls straight
+ through to the selected recompiled BIOS, so the LLE path stays load-bearing
+ and remains the oracle every accuracy check runs against. The boot-skip half
+ works on both linked BIOS backends — the bundled OpenBIOS and a player's
+ retail BIOS dump reach the game the same way — while the kernel-call half is
+ enabled per image and says so at startup when it isn't.
- **Capture-and-compile for overlays.** PS1 games stream code off the disc at
runtime (*overlays*) that no ahead-of-time recompiler can see. PSXRecomp
captures each overlay the moment it loads and recompiles it to native code,
@@ -138,48 +101,257 @@ repositories and link this one in as a **git submodule** to build a game binary.
[`docs/EXECUTION_MODEL.md`](docs/EXECUTION_MODEL.md) (how a game actually
runs — static / native-overlay / interpreter), then
[`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md),
-[`docs/BUILDING.md`](docs/BUILDING.md), and
+[`docs/BUILDING.md`](docs/BUILDING.md),
+[`docs/MOD_PACKAGES.md`](docs/MOD_PACKAGES.md) (versioned runtime mods),
[`CONTRIBUTING.md`](CONTRIBUTING.md).
-## Games
+## Which PlayStation BIOS does it use?
-Each game is its own repository that pins a framework commit as a submodule and
-ships its own release with an in-app launcher. All are **alpha** — playable, not
-yet fully validated end to end.
+Builds support two recompiled BIOS backends: **OpenBIOS** and a compatible
+**retail BIOS**. OpenBIOS is a free, open-source PlayStation BIOS from the
+[PCSX-Redux](https://github.com/grumpycoders/pcsx-redux) project that we're
+allowed to distribute. It is bundled and runs by default, so you usually do not
+need to provide a BIOS dump. Bring a game disc image and play.
-| Game | Repository | Latest build | Notes |
-|---|---|---|---|
-| *Tomba!* | [TombaRecomp](https://github.com/mstan/TombaRecomp) | [releases](https://github.com/mstan/TombaRecomp/releases/latest) | Most mature target; widescreen, supersampling, save/load. |
-| *Tomba! 2* | [Tomba2Recomp](https://github.com/mstan/Tomba2Recomp) | [releases](https://github.com/mstan/Tomba2Recomp/releases/latest) | Multi-track disc support, selectable 21:9. |
-| *Ape Escape* | [ApeEscapeRecomp](https://github.com/mstan/ApeEscapeRecomp) | [releases](https://github.com/mstan/ApeEscapeRecomp/releases/latest) | New launcher, widescreen, memory-card save/load. |
-| *Mega Man X4* | [MegaManX4Recomp](https://github.com/mstan/MegaManX4Recomp) | [releases](https://github.com/mstan/MegaManX4Recomp/releases/latest) | Playable; widescreen. |
-| *Mega Man X5* | [MegaManX5Recomp](https://github.com/mstan/MegaManX5Recomp) | [releases](https://github.com/mstan/MegaManX5Recomp/releases/latest) | Early bring-up. |
-| *Mega Man X6* | [MegaManX6Recomp](https://github.com/mstan/MegaManX6Recomp) | [releases](https://github.com/mstan/MegaManX6Recomp/releases/latest) | Playable; stages, controller, save/load. |
-| *Tsumu Light* | [TsumuLightRecomp](https://github.com/mstan/TsumuLightRecomp) | [releases](https://github.com/mstan/TsumuLightRecomp/releases/latest) | Japanese title (SLPS-02253); early bring-up. |
+**If you'd rather use a retail BIOS**, pick your dumped `SCPH1001.BIN` in
+settings and it will be used instead. Clear that choice to return to OpenBIOS.
-Each game repo carries its own build/run instructions, keyboard/controller
-mappings, and per-game settings. **This repository builds the framework and a
-BIOS-only runtime** — see [Release Package](#release-package) below.
+Two things worth knowing:
+
+- The retail BIOS has to be the *exact* image linked into the build, so a
+ different dump can't be swapped in. If yours doesn't match, the game says
+ which one it expects, and you can carry on with OpenBIOS.
+- **Save files work either way.** Memory cards are shared. *Savestates* are not:
+ one made with OpenBIOS won't load under the retail BIOS, or the other way
+ round, so the game won't let you mix them.
+
+Titles with a verified OpenBIOS incompatibility require the compatible retail
+BIOS instead and say so up front.
+
+> Developers: see [`docs/BIOS_SELECTION.md`](docs/BIOS_SELECTION.md) for the
+> `[runtime] openbios` setting and the selection rules.
+
+## Widescreen
+
+Not a stretch and not a crop — a genuinely **wider field of view**, computed at
+recompile time by widening the game's own projection and culling maths. 4:3
+output stays byte-identical when widescreen is off.
+
+This is framework tooling, not a per-game bolt-on. Any project built on
+PSXRecomp can enable it from a `[widescreen]` block in its `game.toml` — the
+generic cull-widening, FOV and aspect machinery lives here, and bringing up a
+new title is a matter of locating that game's cull sites rather than writing new
+rendering code.
+
+It works on both 3D and 2D engines. 2D is the harder case: a sprite engine has
+no camera to widen, so the background tile ring, streamer and packet budget all
+have to be widened in step with the renderer.
+
+
+  |
+ | Mega Man X6 — 16:9. A 2D sprite engine widened: more stage either side, no stretching. |
+
+
+
+  |
+ | Ape Escape — 21:9. A wider 3D frustum, not a zoomed one. |
+
+
+
+  |
+ | Tomba! 2 — adaptive. The view tracks the window, up to an ultrawide cap. |
+
+
+See [`WIDESCREEN.md`](WIDESCREEN.md) for the per-game configuration.
+
+## Mods
+
+Mods are **versioned packages**, not patched discs. A `.psxmod` is an
+installation, provenance and trust boundary; each package contributes any number
+of independently toggleable **features**, and enabling one never silently
+reconfigures another.
+
+Crucially, your disc image is never rewritten. The player selects a verified
+stock BIN/CUE, and resolution produces guarded native operations and sparse disc
+overlays *over* that image — so the original stays intact and a mod can be
+turned off as easily as it was turned on.
+
+
+
+
+
+Tomba! ships three today — widescreen, skip-FMV, and a warp debug menu that
+re-enables the developers' own hidden menu. Full format in
+[`docs/MOD_PACKAGES.md`](docs/MOD_PACKAGES.md).
+
+## Swap language on the fly
+
+A general localization layer captures the game's own strings out of memory and
+substitutes translated bytes as it runs. Any source language to any target — the
+mechanism has no built-in notion of a "default" language, so a Japanese-only
+release can be played in English just as readily as an English one can be played
+in another language.
+
+Switching is live. Tables are human-editable TOML under `translations/`, one
+column per language, hot-reloaded while the game is running — and the launcher
+carries a language picker. A translator can edit a line and see it in-game
+without rebuilding anything.
+
+The hard part is glyphs, not strings. Substituted text is drawn through the
+game's *own* glyph routine with per-character proportional advances calibrated
+by measuring the real ink width of each glyph tile in VRAM, plus auto-fit
+condensing so longer translations still fit a box sized for the original. That
+means no engine changes and no regeneration to add a language.
+
+See [`docs/STRING_TRANSLATION.md`](docs/STRING_TRANSLATION.md); *Tsumu Light*
+(SLPS-02253) is the first title using it.
+
+## Renderers
+
+| Backend | Status |
+|---|---|
+| **OpenGL** | **Default.** GPU-authoritative VRAM/FBO renderer; moves rasterization and supersampling onto the GPU. |
+| **Vulkan** | **Experimental.** Built when the SDK is present, opt-in at runtime; falls back to OpenGL if unavailable. |
+| **Software** | CPU rasterizer — the reference look, and the most portable fallback. |
+
+## How to use PSXRecomp
+
+PSXRecomp takes a PlayStation disc image and creates a recompilation project
+that supports the bundled OpenBIOS and a compatible retail BIOS. The CLI asks
+for a retail BIOS dump so it can generate that backend alongside OpenBIOS.
+
+### Generate a project with the released CLI
+
+1. Download `psxrecomp-cli-windows-x86_64.zip` from
+ [Releases](https://github.com/mstan/psxrecomp/releases).
+2. Extract the whole zip to a folder. Keep its contents together.
+3. Open PowerShell in that folder and run:
+
+```powershell
+.\psxrecomp.exe build `
+ --disc "C:\Games\My Game\game.cue" `
+ --bios "C:\BIOS\SCPH1001.BIN" `
+ --output "C:\Projects\MyGameRecomp"
+```
+
+Use the `.cue` file when a game has one, and keep its `.bin` track files beside
+it. Single-file `.bin` and `.iso` images are also accepted.
+
+The output folder contains:
+
+- generated C source for the game and compatible retail BIOS, with the bundled
+ OpenBIOS backend supplied by the framework;
+- `game.toml`, which you can edit for game-specific settings;
+- `CMakeLists.txt` and build scripts; and
+- a local copy of the PSXRecomp runtime source needed by the project.
+
+The downloaded CLI is self-contained. You do not need to install Python or
+build this repository to generate a project.
+
+### Build the generated project
+
+Install CMake, Ninja, and a C/C++ compiler. The build fetches its integrity-
+pinned SDL3 release automatically. Then run:
+
+```powershell
+powershell -ExecutionPolicy Bypass -File "C:\Projects\MyGameRecomp\build.ps1"
+```
+
+The generated project also includes a shell build script for macOS and Linux:
+
+```sh
+sh /path/to/MyGameRecomp/build.sh
+```
+
+The ready-made CLI release is currently for 64-bit Windows. You can build the
+CLI from source on another operating system using the instructions below.
+
+The generated project is a practical starting point, not a promise that every
+game works without game-specific fixes. PSX games can load extra code and use
+hardware in ways that require additional configuration or development.
+
+Use only disc and retail BIOS files you obtained legally. PSXRecomp does not
+include those copyrighted files; it includes only the redistributable OpenBIOS
+image. Generated game and retail BIOS source is derived from your files, so do
+not redistribute it.
+
+### Build the CLI from source
+
+You need Git, Python 3, CMake, Ninja, and a C++20 compiler.
+
+```sh
+git clone --recurse-submodules https://github.com/mstan/psxrecomp.git
+cd psxrecomp
+python tools/build_cli.py release
+```
+
+The ready-to-use CLI archive is written to `dist/`. To package debug binaries
+instead, run `python tools/build_cli.py debug`.
+
+On Linux/macOS source checkouts, the same development setup can be driven by:
+
+```sh
+sh tools/setup_dev.sh
+```
+
+That script builds the CLI/recompiler tools and, when the OpenBIOS and retail
+BIOS generated sources are available, the standalone BIOS runtime. Game
+projects should still be generated with `psxrecomp build` and built from their
+generated `build.sh`.
+
+> **Where the project is headed.** Development so far has been **breadth-first**:
+> bring up varied games as playable public builds and prove that the framework
+> generalizes. With that foundation established, the project is now focused on a
+> **depth / optimization** phase: pushing each game toward 100% static coverage,
+> tightening timing accuracy, driving load times toward zero, and hardening the
+> renderer and audio paths. Expect existing projects to get *faster and more
+> accurate* from here.
+
+## Release Package
+
+**This repository's release is a standalone BIOS runtime, not a game.** It can
+boot either the bundled OpenBIOS or a compatible retail BIOS for memory-card
+management; to play a title, grab its release from the [Games](#games) table.
+
+1. Download `PSXRecomp-v*-windows-x64.zip` from Releases.
+2. Extract it and run `PSXRecomp.exe`.
+3. It boots on the bundled OpenBIOS. To use your compatible retail BIOS dump
+ instead, pick it in settings.
+
+The package includes `bios/openbios.bin` and its MIT notice at
+`bios/OpenBIOS.LICENSE`, but no retail PS1 BIOS, game disc image, generated game
+code, or save data. If you choose a retail BIOS, that path is remembered next to
+the executable as `bios.cfg`; clear the choice (or delete that file) to go back
+to OpenBIOS.
+
+The game recomp projects use the same runtime picker contract but ship a
+**Dear ImGui launcher**: on first run it asks for the game disc image and uses
+OpenBIOS automatically. The optional retail BIOS row lets you select your own
+verified dump or clear that choice to return to OpenBIOS. The launcher also
+configures video, controls, and per-game settings. Keyboard/controller mappings
+live in each game's repo and launcher, not here.
## Philosophy — toward 100% static recompilation
The goal is simple and absolute: **a PS1 game should run as native code, not be
emulated.** Every MIPS instruction the game executes should ideally have been
translated to C and compiled ahead of time. No interpreter on the hot path, no
-HLE shims, no "good enough" approximation of the hardware — the recompiled BIOS
-*is* the kernel, and the recompiled game *is* the game.
+HLE shims, no "good enough" approximation of the hardware — the selected
+recompiled BIOS, OpenBIOS or retail BIOS, *is* the kernel, and the recompiled
+game *is* the game.
PS1 games make that goal hard in one specific way: **overlays.** Games stream
code off the disc into RAM at runtime and execute it, then overwrite it with the
next overlay. That code does not exist in the executable at build time, so a
pure ahead-of-time recompiler cannot see it. This is the frontier the project is
-working through, and it is why this is an **alpha/beta**: today a *majority* of a
-supported game runs as statically recompiled native code, but **not yet 100%.**
+working through. Today a *majority* of a supported game runs as statically
+recompiled native code, but **not yet 100%.**
How we close the gap, without ever compromising correctness:
-1. **Static first.** The main executable and the BIOS are fully recompiled
- ahead of time. This is the bulk of execution and it is always native.
+1. **Static first.** The main executable and both supported BIOS backends —
+ OpenBIOS and retail BIOS — are fully recompiled ahead of time. This is the
+ bulk of execution and it is always native.
2. **Capture → compile → cache for overlays.** As the game runs, overlays are
captured the moment they load. Offline, each is recompiled to a native DLL
keyed by its content, cached, and on later runs loaded and dispatched as
@@ -188,8 +360,8 @@ How we close the gap, without ever compromising correctness:
3. **Interpreter failover — only for code that isn't static yet.** A small
MIPS interpreter runs *runtime-installed* code (overlays/dirty RAM) that
hasn't been captured-and-compiled. It is a safety net and a coverage feeder,
- never a substitute for recompiling static code, and never on the BIOS/main-EXE
- path.
+ never a substitute for recompiling static code, and never on the selected
+ BIOS (OpenBIOS or retail BIOS) or main-EXE path.
4. **Precision over recall.** A piece of code we *haven't* compiled safely falls
back to the interpreter and gets captured for next time — under-coverage
self-heals. A piece we compile *wrong* would corrupt the machine, so the
@@ -217,83 +389,73 @@ a game is played; this branch is where that machinery is being built.
## Status
-The framework is at **alpha**: the LLE recompiled BIOS boots and hands off to
-the game across all seven targets, and each runs as majority-native code with
-the capture-and-compile pipeline filling overlays as they're reached. The
-breadth-first push is essentially done; work now is depth and optimization.
+The LLE recompiled BIOS — either bundled OpenBIOS or the compatible retail
+backend — boots and hands off to the game across supported projects. Games run
+as majority-native code, with the capture-and-compile pipeline filling overlays
+as they're reached. The breadth-first push is essentially done; work now is
+depth and optimization.
Core subsystems, framework-wide:
| Subsystem | State |
|---|---|
-| BIOS recompilation (`SCPH1001.BIN`) | Boots to shell and hands off to game; also the correctness oracle |
-| HLE BIOS tier | Optional boot-skip + service layer over the recompiled BIOS; default on, opt-out |
-| Game EXE recompilation | Title/menus/save-load/gameplay reached across the fleet |
+| BIOS recompilation (OpenBIOS + compatible retail BIOS) | Both boot to the shell and hand off to the game; the selected LLE backend is the correctness oracle |
+| HLE BIOS tier | Optional boot-skip + service layer over the selected recompiled BIOS (OpenBIOS or retail BIOS); default on, opt-out |
+| Game EXE recompilation | Title/menus/save-load/gameplay reached across supported projects |
| Overlay capture → compile → cache | `static → gcc → tcc`; coverage grows as a game is played |
| Interpreter failover | Correctness net for not-yet-native code; being compiled away by sharding |
| CD-ROM / MDEC / XA | FMVs stream and play; XA/CDDA timing stays authentic |
-| Memory cards | Save and load verified on multiple titles |
+| Memory cards | Save and load supported across game projects |
| SIO0 controllers | Digital pad + DualShock config; per-game analog/digital selection |
| GPU | Software + OpenGL + Vulkan backends; widescreen FOV/HUD work per game |
| SPU | Working; reverb/noise/sweep and exact SPU-IRQ accuracy still being tightened |
| Interrupts, COP0, timers, GTE | Working; cycle-accuracy foundation is an active depth-phase focus |
-Per-game maturity varies — see the [Games](#games) table. None of the titles is
-declared fully validated end to end; users perform the final playthrough
-validation on each release.
+Validation scope varies by game — see the [Games](#games) table and each
+project's release notes for current coverage.
Open depth-phase fronts: cycle/IRQ-phase timing accuracy, load-time-toward-zero
(data sharding), renderer parity between the software/GL/Vulkan backends, and
driving overlay coverage the last mile to 100% static.
-Running this repository's runtime without a game is useful for **BIOS-only
-memory-card management**; to build and play a title, use its game repo from the
-[Games](#games) table.
-
-## Release Package
-
-**This repository's release is BIOS-only** — it is the framework runtime, not a
-game. Use it for BIOS boot and memory-card management; to play a title, grab its
-release from the [Games](#games) table.
-
-1. Download `PSXRecomp-v*-windows-x64.zip` from Releases.
-2. Extract it and run `PSXRecomp.exe`.
-3. Select your legally obtained `SCPH1001.BIN` BIOS when prompted.
-
-The package does not include a PS1 BIOS, game disc image, generated game code,
-or save data. The selected BIOS path is saved next to the executable as
-`bios.cfg`; delete that file to pick a different BIOS later.
-
-The game recomp projects use the same runtime picker contract but ship a
-**Dear ImGui launcher**: on first run it prompts for your legally obtained BIOS
-and game disc image, then lets you configure video, controls, and per-game
-settings before launching. Keyboard/controller mappings live in each game's repo
-and launcher, not here.
+Running this repository's standalone BIOS runtime without a game is useful for
+**memory-card management under OpenBIOS or a compatible retail BIOS**; to build
+and play a title, use its game repo from the [Games](#games) table.
## Setup
Builds natively on **Windows (MSVC/MinGW)**, **macOS (Apple Silicon & Intel)**,
-and **Linux**. The BIOS thread scheduler uses host fibers — Win32 Fibers on
-Windows, `ucontext` on POSIX (`runtime/src/psx_fiber.c`) — so the recompiled
-BIOS's cooperative thread switching (the CD-boot handoff in particular) behaves
-the same on every platform.
+and **Linux**. The selected BIOS backend — OpenBIOS or retail BIOS — uses host
+fibers for its thread scheduler: Win32 Fibers on Windows and `ucontext` on POSIX
+(`runtime/src/psx_fiber.c`). This keeps the selected recompiled backend's
+cooperative thread switching, especially the CD-boot handoff, consistent on
+every platform.
Requirements at a glance (full details, dependency table, and per-platform
prerequisites in [`docs/BUILDING.md`](docs/BUILDING.md)):
- A C/C++ toolchain: MSVC or MinGW/MSYS2 (Windows), Apple Clang (macOS),
Clang/GCC (Linux). CMake 3.20+; on macOS/Linux also `ninja` and `pkg-config`.
-- SDL2 (system / bundled).
-- A legally obtained `SCPH1001.BIN` BIOS dump. Not included.
+- SDL3 3.4+ (a system package when available, otherwise fetched automatically).
+ SDL2 is available only as an explicit build fallback.
+- A retail PS1 BIOS is **optional** — builds use the bundled OpenBIOS by
+ default. Supply a legally obtained `SCPH1001.BIN` dump only if you want to
+ select the retail BIOS instead (see
+ [Which PlayStation BIOS does it use?](#which-playstation-bios-does-it-use)).
- For game projects, a legally obtained game disc/EXE dump. Not included.
-Build the framework (recompiler tool + BIOS-only runtime):
+Every runtime and game configure uses SDL3 unless you explicitly append
+`-DPSX_SDL_BACKEND=SDL2` to its CMake command. CMake prints the selected backend
+during configuration; it never silently falls back from SDL3 to SDL2.
+
+Build the framework (recompiler tool + standalone BIOS runtime with OpenBIOS and
+retail BIOS support):
```sh
git clone --recurse-submodules https://github.com/mstan/psxrecomp.git && cd psxrecomp
cmake -S recompiler -B recompiler/build -G Ninja -DCMAKE_BUILD_TYPE=Release && cmake --build recompiler/build
-cmake -S runtime -B runtime/build -G Ninja -DCMAKE_BUILD_TYPE=Release && cmake --build runtime/build --target psx-runtime
+cmake -S runtime -B runtime/build -G Ninja -DCMAKE_BUILD_TYPE=Release -DPSX_RECOMP_UI=OFF && cmake --build runtime/build --target psx-runtime
```
On Windows swap `-G Ninja` for your generator if you prefer (e.g.
@@ -307,18 +469,20 @@ generated C is optimized. Game projects generate their own
Keyboard and Xbox-style controller input work out of the box; the default
fullscreen toggle is F11 / Alt+Enter / Cmd+F. **Full button maps, controller
configuration, and rebinding live in each game's repo and in-app launcher** —
-they're game-facing, not part of the framework. The BIOS-only framework runtime
-accepts keyboard input for navigating the BIOS shell and memory-card tools.
+they're game-facing, not part of the framework. The standalone framework runtime
+accepts keyboard input for navigating the selected OpenBIOS or retail BIOS shell
+and memory-card tools.
## Architecture
-The recompiler emits C functions and dispatch tables for BIOS and game code.
-The runtime loads the BIOS/game assets into emulated PS1 memory, links the
-generated C as native code, and simulates hardware through MMIO handlers for
-GPU, DMA, timers, CD-ROM, MDEC, SIO0, memory cards, SPU, GTE, and interrupt
-delivery. The recompiled `SCPH1001.BIN` is the low-level (LLE) kernel and the
-correctness oracle; an optional HLE tier lays instant boot-skip and a few BIOS
-services on top, always falling through to the recompiled BIOS.
+The recompiler emits C functions and dispatch tables for game code and both BIOS
+backends: bundled OpenBIOS and a compatible retail BIOS. The runtime loads the
+selected BIOS and game assets into emulated PS1 memory, links the generated C as
+native code, and simulates hardware through MMIO handlers for GPU, DMA, timers,
+CD-ROM, MDEC, SIO0, memory cards, SPU, GTE, and interrupt delivery. The selected
+recompiled BIOS is the low-level (LLE) kernel and correctness oracle; an optional
+HLE tier lays instant boot-skip and a few BIOS services on top, always falling
+through to that OpenBIOS or retail BIOS kernel.
Code that can't be seen ahead of time (disc-streamed **overlays**) is captured
and compiled to native code the first time it appears (`static → gcc → tcc`
@@ -381,10 +545,11 @@ game code) is planned so discoveries can be shared safely in the future.
Contributions are welcome — AI-assisted or not — as long as they're reviewed,
tested, and keep the core game-agnostic. A few things hold this project together:
-the faithful recompiled BIOS is the baseline and oracle, generated code is never
-hand-edited (fix the recompiler and regenerate), and a change proves itself
-against the Beetle oracle / on screen rather than by assertion. Game-specific work
-lives in the game repos, which pin an exact framework commit as a submodule.
+the selected faithfully recompiled BIOS — OpenBIOS or retail BIOS — is the
+baseline and oracle, generated code is never hand-edited (fix the recompiler and
+regenerate), and a change proves itself against the Beetle oracle / on screen
+rather than by assertion. Game-specific work lives in the game repos, which pin
+an exact framework commit as a submodule.
Read [`CONTRIBUTING.md`](CONTRIBUTING.md) before opening a PR — it covers the core
rules, how to verify a change, the regression checklist across the known games,
@@ -396,13 +561,14 @@ discussion happens in the **R.A.I.D.** Discord (invite below).
PolyForm Noncommercial 1.0.0. See `LICENSE`.
-The PSX BIOS and game disc images remain copyrighted by their respective
-owners. This project distributes no BIOS images, no disc images, and no
-game assets — those are always supplied by the user from their own
-collection. Release executables (and per-game overlay caches) contain
-statically recompiled (machine-translated) builds of the original code,
-the same distribution model used by other static recompilation projects
-such as N64: Recompiled.
+Retail PS1 BIOS images and game disc images remain copyrighted by their
+respective owners and are not distributed. This project does distribute the
+from-scratch, MIT-licensed OpenBIOS image under the notice in
+[`bios/OpenBIOS.LICENSE`](bios/OpenBIOS.LICENSE). Game assets and disc data are
+always supplied by the user from their own collection. Release executables (and
+per-game overlay caches) contain statically recompiled (machine-translated)
+builds of the original code, the same distribution model used by other static
+recompilation projects such as N64: Recompiled.
---
diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md
index 4837695b..942b1ae9 100644
--- a/RELEASE_NOTES.md
+++ b/RELEASE_NOTES.md
@@ -1,17 +1,19 @@
-# PSXRecomp v0.1.0-alpha
+# PSXRecomp OpenBIOS integration
-Initial public BIOS-only framework release package.
+OpenBIOS is now a first-class, redistributable runtime backend.
Highlights:
- Packages the PSXRecomp v4 BIOS runtime as `PSXRecomp.exe`.
-- Prompts for a user-provided `SCPH1001.BIN` BIOS on first launch.
-- Saves the selected BIOS path in `bios.cfg` next to the executable.
+- Bundles PCSX-Redux OpenBIOS and its MIT notice.
+- Uses OpenBIOS when the player has not explicitly selected another BIOS.
+- Keeps a verified, user-provided `SCPH1001.BIN` as an optional runtime choice.
+- Lets launcher users clear a retail selection and return to OpenBIOS.
+- Prevents savestates created under one BIOS from loading under the other.
- Adds Xbox-style controller support through SDL/XInput.
- Adds configurable controller mapping via `input.ini`.
-- Includes the recent Tomba-driven renderer/runtime fixes for the BIOS logo,
- menu text seams, dialog/pause panel seams, terrain shading, and shaded
- textured primitives.
+- Includes the load-delay ownership fix validated in Tomba!, plus OpenBIOS
+ smoke coverage in Ape Escape, Tomba! 2, and Mega Man X6.
-This package does not include a PS1 BIOS, game disc image, generated game code,
-save data, or copyrighted Sony/game assets.
+This package does not include a retail PS1 BIOS, game disc image, generated game
+code, save data, or copyrighted Sony/game assets.
diff --git a/SCPH1001.toml b/SCPH1001.toml
new file mode 100644
index 00000000..7ae1da94
--- /dev/null
+++ b/SCPH1001.toml
@@ -0,0 +1,45 @@
+# SCPH-1001 (US, v2.2 12/04/95) — the reference BIOS.
+# Boot + kernel + shell all recompiled from ROM.
+
+[program]
+name = "SCPH1001 BIOS"
+id = "SCPH-1001"
+rom = "bios/US-PSX-SCPH1001.BIN"
+load_address = "0xBFC00000"
+entry_pc = "0xBFC00000"
+text_size = "0x80000"
+
+[recompiler]
+seeds = "recompiler/seeds/phase2_ghidra_seeds.json"
+out_dir = "generated"
+out_stem = "SCPH1001"
+strict = true
+
+# A0 table: 0xC1 entries copied from ROM 0xBFC04300 to RAM 0x200
+[[recompiler.bios_vectors]]
+ram_addr = "0xA0"
+index_reg = 9
+table_rom_addr = "0xBFC04300"
+table_count = 0xC1
+table_ram_addr = "0x200"
+
+# B0 table: 0x44 entries at kernel 0x874 (ROM 0xBFC10874)
+[[recompiler.bios_vectors]]
+ram_addr = "0xB0"
+index_reg = 9
+table_rom_addr = "0xBFC10874"
+table_count = 0x44
+table_ram_addr = "0x874"
+
+# C0 table: 0x1E entries at kernel 0x674 (ROM 0xBFC10674)
+[[recompiler.bios_vectors]]
+ram_addr = "0xC0"
+index_reg = 9
+table_rom_addr = "0xBFC10674"
+table_count = 0x1E
+table_ram_addr = "0x674"
+
+# SIO data-byte handler: 4-instruction trampoline at RAM 0xCF0 -> func 0x641C
+[[recompiler.bios_aliases]]
+ram_addr = "0xCF0"
+target_key = "0x641C"
diff --git a/SCPH101.toml b/SCPH101.toml
new file mode 100644
index 00000000..8d18728a
--- /dev/null
+++ b/SCPH101.toml
@@ -0,0 +1,44 @@
+# SCPH-101 (US PSOne, v4.5 05/25/00)
+# Boot + kernel are byte-identical to SCPH1001 (534 seeds kept).
+# Shell (ROM 0x18000+, 568 seeds) differs — runs via dirty-RAM interpreter.
+
+[program]
+name = "SCPH101 BIOS (PSOne)"
+id = "SCPH-101"
+rom = "bios/US-PSOne-SCPH101.BIN"
+load_address = "0xBFC00000"
+entry_pc = "0xBFC00000"
+text_size = "0x80000"
+
+[recompiler]
+seeds = "recompiler/seeds/phase2_ghidra_seeds_SCPH101.json"
+out_dir = "generated"
+out_stem = "SCPH101"
+strict = true
+
+# SCPH101 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"
+
+[[recompiler.bios_vectors]]
+ram_addr = "0xB0"
+index_reg = 9
+table_rom_addr = "0xBFC10874"
+table_count = 0x44
+table_ram_addr = "0x874"
+
+[[recompiler.bios_vectors]]
+ram_addr = "0xC0"
+index_reg = 9
+table_rom_addr = "0xBFC10674"
+table_count = 0x1E
+table_ram_addr = "0x674"
+
+[[recompiler.bios_aliases]]
+ram_addr = "0xCF0"
+target_key = "0x641C"
diff --git a/SCPH5552.toml b/SCPH5552.toml
new file mode 100644
index 00000000..35a4f1e2
--- /dev/null
+++ b/SCPH5552.toml
@@ -0,0 +1,44 @@
+# 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.
+
+[program]
+name = "SCPH5552 BIOS (Europe)"
+id = "SCPH-5552"
+rom = "bios/EUR-PSX-SCPH5552.bin"
+load_address = "0xBFC00000"
+entry_pc = "0xBFC00000"
+text_size = "0x80000"
+
+[recompiler]
+seeds = "seeds/phase2_ghidra_seeds_SCPH5552.json"
+out_dir = "generated"
+out_stem = "SCPH5552"
+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"
+
+[[recompiler.bios_vectors]]
+ram_addr = "0xB0"
+index_reg = 9
+table_rom_addr = "0xBFC10874"
+table_count = 0x44
+table_ram_addr = "0x874"
+
+[[recompiler.bios_vectors]]
+ram_addr = "0xC0"
+index_reg = 9
+table_rom_addr = "0xBFC10674"
+table_count = 0x1E
+table_ram_addr = "0x674"
+
+[[recompiler.bios_aliases]]
+ram_addr = "0xCF0"
+target_key = "0x641C"
diff --git a/TCP_COMMANDS.md b/TCP_COMMANDS.md
index ecc20694..f2587c08 100644
--- a/TCP_COMMANDS.md
+++ b/TCP_COMMANDS.md
@@ -1,172 +1,547 @@
-# TCP Debug Server Commands
+# TCP Debug Server Commands
+
+Protocol: **JSON over newline**, one object per line, responses on same connection.
+
+- Request shape: `{"id": N, "cmd": "", ...params}`
+- Success: `{"id": N, "ok": true, ...data}`
+- Failure: `{"id": N, "ok": false, "error": ""}`
+
+There are **two live** servers, both implementing this protocol with overlapping command sets:
+
+| Server | Port | Source |
+|---|---|---|
+| **Native** (our recompiled runtime) | `4370` | `runtime/src/debug_server.c` |
+| **Beetle PSX** (oracle) | `4380` | `runtime/src/beetle_debug_server.c` |
+
+> **DuckStation (port 4371) was retired as the oracle on 2026-05-05** and is no
+> longer built from this repository — there is no `duckstation` entry in
+> `.gitmodules`. The **D** column in the curated inventory below is historical
+> and reflects DuckStation, not Beetle. For authoritative per-command
+> native/Beetle coverage, use the generated
+> [Complete command index](#complete-command-index-generated) at the bottom of
+> this file, which is derived from the two servers' command tables.
+
+The `debug_client.py` CLI can target either, or `compare` two at once to diff state live — that's how divergence hunts work.
+
+```bash
+python tools/debug_client.py [args] # native (port 4370)
+python tools/debug_client.py --port 4380 # psx-beetle
+python tools/debug_client.py --ds [args] # duckstation (port 4371)
+python tools/debug_client.py compare # run on both, diff results
+```
+
+Commands without a bespoke CLI mapping pass through generically: extra
+args of the form `key=value` become JSON fields (ints when numeric, else
+strings), so every server command is reachable, e.g.
+`debug_client.py --port 4370 gpu_frame_dump frame=14528 count=65536`.
+
+---
+
+## Command inventory
+
+Columns: **N** = native, **D** = DuckStation oracle.
+
+| Command | N | D | Params | Description |
+|---|---|---|---|---|
+| `ping` / `frame` | ✓ | ✓ | — | Heartbeat + current frame number |
+| `get_registers` (`regs`) | ✓ | ✓ | — | All 32 GPRs + PC + HI + LO (native also: COP0 SR/Cause/EPC, I_STAT, I_MASK) |
+| `read_ram` | ✓ | ✓ | `addr`, `len` | Read bytes from PS1 address space as hex string — up to the full 2 MB in ONE response line. `dump_ram` is an alias (the old chunked multi-line variant is gone: it broke the one-request/one-response protocol and wedged the server) |
+| `write_ram` | ✓ | ✓ | `addr`, `val` | Write **one byte** to PS1 address space. Note the parameter is `val` (not `hex`), and the write is a single byte per call — this row previously documented both incorrectly |
+| `read_scratch` | | ✓ | `addr`, `len` | Read PS1 scratchpad (0x1F800000 region) |
+| `read_vram` / `vram_peek` | ✓¹ | ✓ | `x`, `y`, `w`, `h` | Read 16-bit VRAM pixels (max 128×128) |
+| `gpu_state` | ✓ | ✓ | — | Display area, display depth, draw offset, GPUSTAT, clip rect, xfer state |
+| `sio_state` | ✓ | ✓ | — | SIO registers + (native only) pad/memcard protocol + TX/RX history |
+| `irq_state` | ✓ | ✓ | — | `I_STAT`, `I_MASK` (both), plus chain state on native |
+| `dma_state` | ✓ | ✓ | — | DPCR, DICR, all 7 channel states (madr/bcr/chcr) |
+| `event_state` | | ✓ | — | EvCB table summary (stub on DS — events are BIOS-level) |
+| `overlay_state` | | ✓ | — | Current overlay info |
+| `cdrom_sector_dump` | ✓ | | `offset`, `len` | Dump bytes from the last CD-ROM sector observed by the controller, including LBA/mode metadata |
+| `cdrom_sector_history` | ✓ | | `count`, optional `lba` | Dump newest CD-ROM sector history entries, including raw XA subheader fields, CPU/audio delivery flags, and the first 128 bytes |
+| `cdrom_sector_history_clear` | ✓ | | — | Reset the CD-ROM sector history ring |
+| `watch` | ✓ | ✓ | `addr` | Set byte-level memory watchpoint (fires per-frame on change) |
+| `unwatch` | ✓ | ✓ | `addr` | Remove memory watchpoint |
+| `set_input` | ✓ | ✓ | `buttons`, optional `frames`, optional `lx`, `ly`, `rx`, `ry` | Override pad1 buttons and optional analog axes (PS1 inverted bitmask, 0 = pressed; axes 0-255). Holds until `clear_input` on both backends; pass `frames=N` (beetle) to auto-release after N frames |
+| `clear_input` | ✓ | ✓ | — | Remove input and analog axis overrides |
+| `turbo` | ✓ | | `enabled` | Enable/disable TCP-controlled frontend turbo for fast-forward validation |
+| `turbo_state` | ✓ | | — | Query TCP-controlled turbo state |
+| `pause` | ✓ | | — | **REMOVED** — still registered, but always returns an error. Query a ring buffer (`fn_entry_tail`, `wtrace_dump`, `gpu_frame_dump`) instead of synthesizing a snapshot |
+| `continue` (`c`) | ✓ | | — | **REMOVED** — nothing to resume, since `pause` is gone |
+| `step` | ✓ | | — | **REMOVED** — query a ring buffer over the window of interest instead of advancing N frames synchronously |
+| `run_to_frame` | ✓ | | — | **REMOVED** — use `frame_range` / `read_frame_ram` against the live frame ring instead |
+| `history` | ✓ | ✓ | — | Ring buffer stats (frames available) |
+| `get_frame` | ✓ | ✓ | `frame` | Full frame record from ring buffer |
+| `frame_range` | ✓ | ✓ | `start`, `end` | Range query, max 200 frames |
+| `frame_timeseries` | ✓ | ✓ | `start`, `end` | Compact timeseries, max 200 frames |
+| `set_snapshot` | ✓ | ✓ | `slot`, `addr`, `size` | Configure per-frame RAM snapshot region (slots 0-3) |
+| `get_snapshots` | ✓ | ✓ | — | Show snapshot config |
+| `screenshot` | ✓ | ✓ | `path` (optional) | Write a **PNG** of the current display to `path` (default `psx_screenshot.png` in the runtime cwd); single metadata response `{path,width,height}`. `screenshot_file` is an alias; the old inline-hex-row `screenshot` is gone (it streamed h+1 response lines per request and poisoned the connection) |
+| `first_failure` | ✓ | | — | Find first divergence point between runs (native-side tracking) |
+| `read_frame_ram` | ✓ | | `addr`, `len`, `frame` | Read RAM **as of a specific frame** (from ring buffer) |
+| `wtrace_range` | ✓ | | `lo`, `hi` | Set RAM-write trace range (ring of 262 144 writes with RA — `WRITE_TRACE_CAP`, `1 << 18`) |
+| `wtrace_dump` | ✓ | beetle | optional `addr_lo`, `addr_hi`, `count`, `newest` | Dump RAM-write trace entries as JSON. The address filter is applied server-side over the FULL ring before the emit cap — always pass it when hunting a specific buffer, otherwise you only see the oldest `count` entries of the whole ring |
+| `wtrace_clear` | ✓ | | — | Reset the trace ring |
+| `mmio_dump` | ✓ | | optional `addr`, `count`, `newest` | Dump the always-on MMIO write ring (256K entries, ALL 0x1F801xxx writes — SPU/DMA traffic rolls it in well under a minute of gameplay; for display history use `gp1_dump`) |
+| `mmio_clear` | ✓ | | — | Reset the MMIO write ring |
+| `gp1_dump` | ✓ | | optional `frame_lo`, `frame_hi`, `count`, `newest` | Dump the dedicated ALWAYS-ON GP1 (0x1F801814 display control) ring — 512K entries ≈ 15 min of gameplay (Tomba writes ~10 GP1/frame), survives the general MMIO ring's eviction. Frame filter is server-side over the full ring. Each entry: val + func/pc/cpu_pc/ra/sp/a0/a1/sr/epc/frame |
+| `pc_break` | | ✓² | `addr` | DS execute breakpoint, state captured on hit (via `pc_hit_last`) |
+| `pc_unbreak` | | ✓² | `addr` | Remove an execute breakpoint |
+| `pc_break_list` | | ✓² | — | List active execute breakpoints |
+| `pc_hit_last` | | ✓² | — | Captured state (PC, $ra, all GPRs, COP0) from most recent PC break hit |
+| `pc_hit_clear` | | ✓² | — | Clear the last-hit record |
+| `quit` | ✓ | | — | Shutdown native runtime |
+
+¹ Native `vram_peek` is the legacy name; DS calls it `read_vram`. Same semantics.
+² The `pc_*` family is specific to the DS oracle: DuckStation's CPU core honours `CPU::AddBreakpointWithCallback`, while our native runtime dispatches whole recompiled functions (no mid-function PC breaks).
+
+---
+
+## Divergence-hunt workflow
+
+When a recompiled-BIOS bug is suspected, the two servers let you find the **first** divergence instead of chasing symptoms. Standard procedure (inherited from v3's `DEBUG.md`):
+
+1. **Sync state via PC + registers, not frame number.** Frames drift after even a single timing glitch. Pause both servers; compare `get_registers` until they match.
+2. **Dump both sides fully.** Compare `get_frame`, `gpu_state`, `irq_state`, `dma_state` (DS), `dump_ram` over the same regions.
+3. **Byte-level comparison.** Tiny mismatches usually point at one subsystem. Use `debug_client.py compare ` for automatic diff.
+4. **Find the earliest mismatch**, not a later symptom. Ring-buffer queries (`frame_range`, `read_frame_ram`) help locate which frame went wrong.
+5. **Trace the write.** Use `watch` to catch the divergent store, or DS's `pc_break` on the suspect function entry. Look at `$ra` in `pc_hit_last` to identify the caller chain.
+6. **Classify.** codegen (recompiler generates wrong instruction), runtime (MMIO or kernel simulation wrong), timing (IRQ cadence), or BIOS (real-hardware quirk we didn't model).
+7. **Minimal fix** in the correct subsystem. Never hand-deliver state to hide the symptom (see CLAUDE.md §0).
+
+---
+
+## Server send budget + serve-stall telemetry
+
+The server is pumped on the **main thread**: every millisecond it spends
+sending a response is a millisecond the emulator does not run. Inline
+responses are bounded — 2 s per zero-progress chunk and **15 s total per
+response**; a client that exceeds the budget is disconnected (the runtime
+never stalls indefinitely). Responses bigger than the budget allows must
+use the `*_dump_file` variants, which write to disk instead of the socket.
+
+Cumulative serve-stall is exported as `tcp_send_stall_ms` /
+`tcp_clients_dropped` in `psx_freeze_heartbeat.json` (plus per-tick
+`tcp_ms` in its ring) and in every wedge/fatal dump header. **Check these
+first when diagnosing slow or stalled frames** — on 2026-06-10 two
+"attract-idle degradations" turned out to be a TCP client trickle-draining
+mega-dumps, throttling the main loop to 6 fps (all 8 watchdog stack
+samples inside `WS2_32!send`). A slow-frames wedge with a large
+`tcp_send_stall_ms` delta over the same window is observer interference,
+not a guest bug.
+
+## Call-contract (bail) telemetry
+
+The dispatch call contract (Bug D family fix, 2026-06-10) guards every
+generated continuation: it may only run if the guest actually returned to
+the call site ($ra == site return address, $sp == caller's sp at the
+call). Violations begin a "bail" unwind that abandons stale C frames and
+re-dispatches the guest's true target. Counters in
+`psx_freeze_heartbeat.json` and dump headers:
+
+- `bail_first` — contract violations detected (wild returns). Nonzero
+ during gameplay means the game executed a wild control transfer (e.g.
+ Tomba's dead jumptable case `jal 0x80120B3C`, the chest freeze). A
+ small count with the game continuing normally is the fix working.
+- `bail_resolved` — unwinds that resolved at an enclosing call site whose
+ contract matched (multi-level return).
+- `bail_flattened` — unwinds that reached the outermost dispatch loop and
+ re-dispatched the wild target on a clean host stack.
+- `bail_anomaly` — bail flag observed at exception entry (must stay 0;
+ anything else is a runtime bug).
+
+---
+
+## `bios_info` — linked recompiled-BIOS identity (native only)
+
+Reports which BIOS image this build's recompiled C was generated from
+(`psx_bios_image`, emitted into the generated dispatch from the BIOS profile)
+and whether the loaded ROM matches it: `image_id`, `sha256`, `crc32`, `size`,
+`bundled` (redistributable image shipped with the game), the kernel-bless
+window, the HLE anchors (`shell_entry_phys` / `deliver_event_ret`; 0 =
+structurally unavailable on this BIOS), and `image_wordsum` vs
+`loaded_wordsum` with `match`. With the launch identity gate a running
+process always reports `match:1`.
+
+- `{"cmd":"bios_info"}`
+
+## `s3_smear_watch` — callee-saved-register smear tripwire (native only)
+
+Latches the first interpreted instruction in a PC window whose execution
+changes `$s3` (`runtime/src/dirty_ram_interp.c`). A `jalr`'s exec_one spans
+the entire nested native callee, so the latch names the callee that returned
+with a clobbered callee-saved register; the insn ring is frozen at the latch.
+
+- `{"cmd":"s3_smear_watch","lo":"","hi":""}` — arm (each arming
+ fully re-specifies the watch). Optional `"excl":""`: exact
+ encoding to ignore, so a watched loop's own `$s3` advance (e.g. an
+ `addi s3,s3,8` list walk) doesn't trip the latch.
+- `{"cmd":"s3_smear_watch"}` — report the latch: `valid`, `pc`, `insn`,
+ `s3_old`/`s3_new`, `call_target` (rs at the call site for jr/jalr),
+ `frame`.
+- `{"cmd":"s3_smear_watch","lo":"0"}` — disarm.
+
+## `callret_watch` — interp JALR call-resolution ring (native only)
+
+64-entry ring (`runtime/src/dirty_ram_interp.c`) recording, for every
+interpreted JALR whose call PC lies in a window, which resolution tier ran
+the callee and the full post-call outcome — the complement of
+`s3_smear_watch`: the tripwire names the callee that came back smeared, this
+ring names the return path that let it come back.
+
+- `{"cmd":"callret_watch","lo":"","hi":""}` — arm (resets the ring).
+- `{"cmd":"callret_watch"}` — dump (newest last): per entry `cyc`, `f`
+ (frame), `pc`, `tgt`, `path` (`CRES_*` tier code, see the enum in
+ dirty_ram_interp.c; `|0x100` = finish() escaped), pre-call
+ `sp_b`/`ra_b`/`s0_b`/`s3_b`, post-call `pc_a`/`ra_a`/`sp_a`/`s0_a`/
+ `s3_a`/`v0_a`, `bail`/`rfe`/`esc`/`in_exc` flags, `dstatic`/`dblocks`/
+ `dexc` engine-attribution deltas across the call, `last_func`.
+- `{"cmd":"callret_watch","lo":"0"}` — disarm.
+
+## `hle_dump` — BIOS-HLE tier call ring (native only)
+
+Always-on ring (`runtime/src/bios_hle.c`, 16K entries) recording every
+A0/B0/C0 kernel-vector dispatch the HLE tier's hook observes, plus the boot
+shell-skip event.
+
+The hook is installed when EITHER axis is on (`bios_hle_plan.h`), so a run with
+the boot-skip on but kernel calls left to LLE — the default on the bundled
+OpenBIOS, which exports no `deliver_event_ret` — reports
+`backend: "LLE (recompiled BIOS)"` and still fills the ring with `route: 0`
+vector observations plus the one `route: 2` boot entry. Only a run with BOTH axes
+off installs no hook and leaves the ring empty; use `bioscall_dump` for LLE-side
+vector observation there.
+
+- `{"cmd":"hle_dump"}` — status: `backend` (`HLE (LLE fallback)` /
+ `LLE (recompiled BIOS)`), `boot_skip`, `boot_turbo_active`, `total`.
+- `{"cmd":"hle_dump","tail":N}` — last N entries: `seq`, `cycle` (guest
+ cycle), `vec` (0xA0/0xB0/0xC0, or 0x30000 for the boot skip), `fn` ($t1
+ function number), `a0..a3`, `ra`, `v0` (result when HLE-serviced), `route`
+ (0 = fell through to LLE, 1 = serviced in HLE, 2 = boot shell-skip).
+- Filters: `"fn":N`, `"route":0|1|2`.
+
+---
+
+## Rule when the server can't answer your question
+
+If an inspection need isn't covered by the existing commands, **do not fall back to printf or log files**. Instead:
+
+1. Add a handler in `runtime/src/debug_server.c` (native)
+2. Add the matching handler in `runtime/src/beetle_debug_server.c` (Beetle oracle)
+ when the question needs a cross-check against hardware behaviour
+3. Keep field names parallel between the two
+4. Run `python tools/gen_tcp_commands.py` to refresh the generated index, and add
+ a row to the curated inventory above if the command needs explaining
+
+> Step 4 used to read "Update this file", and that did not survive contact with
+> reality: this document described 47 commands while the servers registered 292.
+> The index is now generated from the command tables, and
+> `python tools/gen_tcp_commands.py --check` fails when it drifts. Prose in the
+> curated inventory is still hand-written and still worth adding.
+
+The TCP server is the canonical instrumentation surface. Rule 3 in `CLAUDE.md` is absolute: **no `fprintf(stderr, …)` in source code, ever, for any reason**.
+
+
+
+## Complete command index (generated)
+
+**292 commands registered** — 279 on the native server (`runtime/src/debug_server.c`), 61 on the Beetle server (`runtime/src/beetle_debug_server.c`).
-Protocol: **JSON over newline**, one object per line, responses on same connection.
-
-- Request shape: `{"id": N, "cmd": "", ...params}`
-- Success: `{"id": N, "ok": true, ...data}`
-- Failure: `{"id": N, "ok": false, "error": ""}`
-
-There are **two** servers, both implementing this protocol with overlapping command sets:
-
-| Server | Port | Source |
-|---|---|---|
-| **Native** (our recompiled runtime) | `4370` | `runtime/src/debug_server.c` |
-| **DuckStation** (oracle) | `4371` | `duckstation/src/core/psxrecomp_debug_server.cpp` (patched, see `tools/duckstation/psxrecomp_oracle.patch`) |
-
-The `debug_client.py` CLI can target either, or `compare` two at once to diff state live — that's how divergence hunts work.
-
-```bash
-python tools/debug_client.py [args] # native (port 4370)
-python tools/debug_client.py --port 4380 # psx-beetle
-python tools/debug_client.py --ds [args] # duckstation (port 4371)
-python tools/debug_client.py compare # run on both, diff results
-```
-
-Commands without a bespoke CLI mapping pass through generically: extra
-args of the form `key=value` become JSON fields (ints when numeric, else
-strings), so every server command is reachable, e.g.
-`debug_client.py --port 4370 gpu_frame_dump frame=14528 count=65536`.
-
----
-
-## Command inventory
-
-Columns: **N** = native, **D** = DuckStation oracle.
-
-| Command | N | D | Params | Description |
-|---|---|---|---|---|
-| `ping` / `frame` | ✓ | ✓ | — | Heartbeat + current frame number |
-| `get_registers` (`regs`) | ✓ | ✓ | — | All 32 GPRs + PC + HI + LO (native also: COP0 SR/Cause/EPC, I_STAT, I_MASK) |
-| `read_ram` | ✓ | ✓ | `addr`, `len` | Read bytes from PS1 address space as hex string — up to the full 2 MB in ONE response line. `dump_ram` is an alias (the old chunked multi-line variant is gone: it broke the one-request/one-response protocol and wedged the server) |
-| `write_ram` | ✓ | ✓ | `addr`, `hex` | Write bytes to PS1 address space |
-| `read_scratch` | | ✓ | `addr`, `len` | Read PS1 scratchpad (0x1F800000 region) |
-| `read_vram` / `vram_peek` | ✓¹ | ✓ | `x`, `y`, `w`, `h` | Read 16-bit VRAM pixels (max 128×128) |
-| `gpu_state` | ✓ | ✓ | — | Display area, display depth, draw offset, GPUSTAT, clip rect, xfer state |
-| `sio_state` | ✓ | ✓ | — | SIO registers + (native only) pad/memcard protocol + TX/RX history |
-| `irq_state` | ✓ | ✓ | — | `I_STAT`, `I_MASK` (both), plus chain state on native |
-| `dma_state` | ✓ | ✓ | — | DPCR, DICR, all 7 channel states (madr/bcr/chcr) |
-| `event_state` | | ✓ | — | EvCB table summary (stub on DS — events are BIOS-level) |
-| `overlay_state` | | ✓ | — | Current overlay info |
-| `sljit_async` | ✓ | | — | Off-thread sljit compile-worker telemetry: `worker_running`, `async_on`, always-on counters (`enqueued`/`compiled`/`failed`/`queued_now`), `last_compile_ms`/`max_compile_ms` (off-thread compile time), resolved `cache_dir`, and a `recent[]` ring of the last 64 compiles (per-shard `phys`/`crc`/`code_len`/`ms`/`ok`). Query it to prove the worker compiles off the dispatch thread without a frame hitch |
-| `cdrom_sector_dump` | ✓ | | `offset`, `len` | Dump bytes from the last CD-ROM sector observed by the controller, including LBA/mode metadata |
-| `cdrom_sector_history` | ✓ | | `count`, optional `lba` | Dump newest CD-ROM sector history entries, including raw XA subheader fields, CPU/audio delivery flags, and the first 128 bytes |
-| `cdrom_sector_history_clear` | ✓ | | — | Reset the CD-ROM sector history ring |
-| `watch` | ✓ | ✓ | `addr` | Set byte-level memory watchpoint (fires per-frame on change) |
-| `unwatch` | ✓ | ✓ | `addr` | Remove memory watchpoint |
-| `set_input` | ✓ | ✓ | `buttons`, optional `frames`, optional `lx`, `ly`, `rx`, `ry` | Override pad1 buttons and optional analog axes (PS1 inverted bitmask, 0 = pressed; axes 0-255). Holds until `clear_input` on both backends; pass `frames=N` (beetle) to auto-release after N frames |
-| `clear_input` | ✓ | ✓ | — | Remove input and analog axis overrides |
-| `turbo` | ✓ | | `enabled` | Enable/disable TCP-controlled frontend turbo for fast-forward validation |
-| `turbo_state` | ✓ | | — | Query TCP-controlled turbo state |
-| `pause` | ✓ | ✓ | — | Pause emulation |
-| `continue` (`c`) | ✓ | ✓ | — | Resume emulation |
-| `step` | ✓ | ✓ | `[count]` | Step N frames (default 1) |
-| `run_to_frame` | ✓ | ✓ | `frame` | Run until frame number, then pause |
-| `history` | ✓ | ✓ | — | Ring buffer stats (frames available) |
-| `get_frame` | ✓ | ✓ | `frame` | Full frame record from ring buffer |
-| `frame_range` | ✓ | ✓ | `start`, `end` | Range query, max 200 frames |
-| `frame_timeseries` | ✓ | ✓ | `start`, `end` | Compact timeseries, max 200 frames |
-| `set_snapshot` | ✓ | ✓ | `slot`, `addr`, `size` | Configure per-frame RAM snapshot region (slots 0-3) |
-| `get_snapshots` | ✓ | ✓ | — | Show snapshot config |
-| `screenshot` | ✓ | ✓ | `path` (optional) | Write a 24-bit BMP of the current display to `path` (default `psx_screenshot.bmp` in the runtime cwd); single metadata response `{path,width,height}`. `screenshot_file` is an alias; the old inline-hex-row `screenshot` is gone (it streamed h+1 response lines per request and poisoned the connection) |
-| `first_failure` | ✓ | | — | Find first divergence point between runs (native-side tracking) |
-| `read_frame_ram` | ✓ | | `addr`, `len`, `frame` | Read RAM **as of a specific frame** (from ring buffer) |
-| `wtrace_range` | ✓ | | `lo`, `hi` | Set RAM-write trace range (ring of 1024 writes with RA) |
-| `wtrace_dump` | ✓ | beetle | optional `addr_lo`, `addr_hi`, `count`, `newest` | Dump RAM-write trace entries as JSON. The address filter is applied server-side over the FULL ring before the emit cap — always pass it when hunting a specific buffer, otherwise you only see the oldest `count` entries of the whole ring |
-| `wtrace_clear` | ✓ | | — | Reset the trace ring |
-| `mmio_dump` | ✓ | | optional `addr`, `count`, `newest` | Dump the always-on MMIO write ring (256K entries, ALL 0x1F801xxx writes — SPU/DMA traffic rolls it in well under a minute of gameplay; for display history use `gp1_dump`) |
-| `mmio_clear` | ✓ | | — | Reset the MMIO write ring |
-| `gp1_dump` | ✓ | | optional `frame_lo`, `frame_hi`, `count`, `newest` | Dump the dedicated ALWAYS-ON GP1 (0x1F801814 display control) ring — 512K entries ≈ 15 min of gameplay (Tomba writes ~10 GP1/frame), survives the general MMIO ring's eviction. Frame filter is server-side over the full ring. Each entry: val + func/pc/cpu_pc/ra/sp/a0/a1/sr/epc/frame |
-| `pc_break` | | ✓² | `addr` | DS execute breakpoint, state captured on hit (via `pc_hit_last`) |
-| `pc_unbreak` | | ✓² | `addr` | Remove an execute breakpoint |
-| `pc_break_list` | | ✓² | — | List active execute breakpoints |
-| `pc_hit_last` | | ✓² | — | Captured state (PC, $ra, all GPRs, COP0) from most recent PC break hit |
-| `pc_hit_clear` | | ✓² | — | Clear the last-hit record |
-| `quit` | ✓ | | — | Shutdown native runtime |
-
-¹ Native `vram_peek` is the legacy name; DS calls it `read_vram`. Same semantics.
-² The `pc_*` family is specific to the DS oracle: DuckStation's CPU core honours `CPU::AddBreakpointWithCallback`, while our native runtime dispatches whole recompiled functions (no mid-function PC breaks).
-
----
-
-## Divergence-hunt workflow
-
-When a recompiled-BIOS bug is suspected, the two servers let you find the **first** divergence instead of chasing symptoms. Standard procedure (inherited from v3's `DEBUG.md`):
-
-1. **Sync state via PC + registers, not frame number.** Frames drift after even a single timing glitch. Pause both servers; compare `get_registers` until they match.
-2. **Dump both sides fully.** Compare `get_frame`, `gpu_state`, `irq_state`, `dma_state` (DS), `dump_ram` over the same regions.
-3. **Byte-level comparison.** Tiny mismatches usually point at one subsystem. Use `debug_client.py compare ` for automatic diff.
-4. **Find the earliest mismatch**, not a later symptom. Ring-buffer queries (`frame_range`, `read_frame_ram`) help locate which frame went wrong.
-5. **Trace the write.** Use `watch` to catch the divergent store, or DS's `pc_break` on the suspect function entry. Look at `$ra` in `pc_hit_last` to identify the caller chain.
-6. **Classify.** codegen (recompiler generates wrong instruction), runtime (MMIO or kernel simulation wrong), timing (IRQ cadence), or BIOS (real-hardware quirk we didn't model).
-7. **Minimal fix** in the correct subsystem. Never hand-deliver state to hide the symptom (see CLAUDE.md §0).
-
----
-
-## Server send budget + serve-stall telemetry
-
-The server is pumped on the **main thread**: every millisecond it spends
-sending a response is a millisecond the emulator does not run. Inline
-responses are bounded — 2 s per zero-progress chunk and **15 s total per
-response**; a client that exceeds the budget is disconnected (the runtime
-never stalls indefinitely). Responses bigger than the budget allows must
-use the `*_dump_file` variants, which write to disk instead of the socket.
-
-Cumulative serve-stall is exported as `tcp_send_stall_ms` /
-`tcp_clients_dropped` in `psx_freeze_heartbeat.json` (plus per-tick
-`tcp_ms` in its ring) and in every wedge/fatal dump header. **Check these
-first when diagnosing slow or stalled frames** — on 2026-06-10 two
-"attract-idle degradations" turned out to be a TCP client trickle-draining
-mega-dumps, throttling the main loop to 6 fps (all 8 watchdog stack
-samples inside `WS2_32!send`). A slow-frames wedge with a large
-`tcp_send_stall_ms` delta over the same window is observer interference,
-not a guest bug.
-
-## Call-contract (bail) telemetry
-
-The dispatch call contract (Bug D family fix, 2026-06-10) guards every
-generated continuation: it may only run if the guest actually returned to
-the call site ($ra == site return address, $sp == caller's sp at the
-call). Violations begin a "bail" unwind that abandons stale C frames and
-re-dispatches the guest's true target. Counters in
-`psx_freeze_heartbeat.json` and dump headers:
-
-- `bail_first` — contract violations detected (wild returns). Nonzero
- during gameplay means the game executed a wild control transfer (e.g.
- Tomba's dead jumptable case `jal 0x80120B3C`, the chest freeze). A
- small count with the game continuing normally is the fix working.
-- `bail_resolved` — unwinds that resolved at an enclosing call site whose
- contract matched (multi-level return).
-- `bail_flattened` — unwinds that reached the outermost dispatch loop and
- re-dispatched the wild target on a clean host stack.
-- `bail_anomaly` — bail flag observed at exception entry (must stay 0;
- anything else is a runtime bug).
-
----
-
-## `hle_dump` — BIOS-HLE tier call ring (native only)
-
-Always-on ring (`runtime/src/bios_hle.c`, 16K entries) recording every
-A0/B0/C0 kernel-vector dispatch the HLE tier's hook observes, plus the boot
-shell-skip event. Empty in pure-LLE mode (the tier installs no hook there —
-use `bioscall_dump` for LLE-side vector observation).
-
-- `{"cmd":"hle_dump"}` — status: `backend` (`HLE (LLE fallback)` /
- `LLE (recompiled BIOS)`), `boot_skip`, `boot_turbo_active`, `total`.
-- `{"cmd":"hle_dump","tail":N}` — last N entries: `seq`, `cycle` (guest
- cycle), `vec` (0xA0/0xB0/0xC0, or 0x30000 for the boot skip), `fn` ($t1
- function number), `a0..a3`, `ra`, `v0` (result when HLE-serviced), `route`
- (0 = fell through to LLE, 1 = serviced in HLE, 2 = boot shell-skip).
-- Filters: `"fn":N`, `"route":0|1|2`.
-
----
-
-## Rule when the server can't answer your question
-
-If an inspection need isn't covered by the existing commands, **do not fall back to printf or log files**. Instead:
-
-1. Add a handler in `runtime/src/debug_server.c` (native)
-2. Add the matching handler in `duckstation/src/core/psxrecomp_debug_server.cpp` (DS), regenerate the patch via the instructions in `tools/duckstation/README.md`
-3. Keep field names parallel between the two
-4. Update this file
-
-The TCP server is the canonical instrumentation surface. Rule 3 in `CLAUDE.md` is absolute: **no `fprintf(stderr, …)` in source code, ever, for any reason**.
+47 of 292 have prose above; **245 are index-only**. An index-only command still works — it just has no description here yet. Send it `{"cmd":""}` and read the reply, or find its `handle_*` function in the server source.
+
+Regenerate with `python tools/gen_tcp_commands.py`; `--check` fails if this block has drifted from the code.
+
+| Command | Native | Beetle | Described above |
+|---|:--:|:--:|:--:|
+| `a0_history` | ✓ | | |
+| `audio_events` | ✓ | ✓ | |
+| `audio_stats` | ✓ | ✓ | |
+| `audio_wav` | ✓ | ✓ | |
+| `autocompile_run` | ✓ | | |
+| `autocompile_status` | ✓ | | |
+| `bios_info` | ✓ | | ✓ |
+| `bioscall_dump` | ✓ | | |
+| `c0_history` | ✓ | | |
+| `call_focus_dump` | ✓ | | |
+| `call_focus_reset` | ✓ | | |
+| `call_focus_stats` | ✓ | | |
+| `callret_watch` | ✓ | | ✓ |
+| `capture_freeze` | ✓ | | |
+| `capture_quads` | ✓ | | |
+| `card_buffer_dump` | ✓ | | |
+| `card_data_writes` | ✓ | | |
+| `card_data_writes_reset` | ✓ | | |
+| `card_mgr_clear` | ✓ | | |
+| `card_mgr_trace` | ✓ | | |
+| `card_read_summary` | ✓ | | |
+| `card_read_summary_reset` | ✓ | | |
+| `card_trace_dump` | ✓ | | |
+| `card_txn_dump` | ✓ | | |
+| `cd_overwrite` | ✓ | | |
+| `cd_read_log` | ✓ | | |
+| `cdc_volume` | | ✓ | |
+| `cdrom_bursts` | ✓ | | |
+| `cdrom_cmd_dump` | | ✓ | |
+| `cdrom_cmd_reset` | | ✓ | |
+| `cdrom_command_history` | ✓ | | |
+| `cdrom_command_history_clear` | ✓ | | |
+| `cdrom_instant_rate` | ✓ | | |
+| `cdrom_sector_dump` | ✓ | | ✓ |
+| `cdrom_sector_history` | ✓ | | ✓ |
+| `cdrom_sector_history_clear` | ✓ | | ✓ |
+| `cdrom_state` | ✓ | | |
+| `cdrom_timing` | ✓ | | |
+| `cdrom_trace_clear` | ✓ | | |
+| `cdrom_trace_dump` | ✓ | | |
+| `ce_profile` | ✓ | | |
+| `chain_trace` | ✓ | | |
+| `clear_input` | ✓ | ✓ | ✓ |
+| `continue` | ✓ | | ✓ |
+| `cyc_watch` | ✓ | ✓ | |
+| `cyc_watch_clear` | ✓ | ✓ | |
+| `cyc_watch_dump` | ✓ | ✓ | |
+| `cycles_to_next_event` | ✓ | | |
+| `d44_ring` | ✓ | | |
+| `data_shards` | ✓ | | |
+| `devtrace_ctl` | ✓ | ✓ | |
+| `devtrace_dump` | ✓ | ✓ | |
+| `dirty_block_dump_file` | ✓ | | |
+| `dirty_block_log` | ✓ | | |
+| `dirty_break_clear` | ✓ | | |
+| `dirty_break_range` | ✓ | | |
+| `dirty_break_state` | ✓ | | |
+| `dirty_flow_log` | ✓ | | |
+| `dirty_insn_dump_file` | ✓ | | |
+| `dirty_insn_gate` | ✓ | | |
+| `dirty_insn_log` | ✓ | | |
+| `dirty_ram_stats` | ✓ | | |
+| `dirty_ram_unsupported` | ✓ | | |
+| `disp_ring` | ✓ | | |
+| `dispatch_check` | ✓ | | |
+| `dispatch_stats` | ✓ | | |
+| `dispatch_tail` | ✓ | | |
+| `display_ring_aux` | ✓ | | |
+| `display_ring_get` | ✓ | | |
+| `display_ring_stats` | ✓ | | |
+| `dma_cdrom_history` | ✓ | | |
+| `dma_state` | ✓ | | ✓ |
+| `dma_trace_clear` | ✓ | | |
+| `dma_trace_dump` | ✓ | | |
+| `dump_buffer` | ✓ | | |
+| `dump_ram` | ✓ | ✓ | ✓ |
+| `evcb_snapshot` | ✓ | | |
+| `evcb_walk_dump` | ✓ | | |
+| `evcb_walk_stats` | ✓ | | |
+| `event_ring_clear` | ✓ | | |
+| `event_ring_dump` | ✓ | | |
+| `event_ring_tail` | ✓ | | |
+| `exc_ring` | | ✓ | |
+| `first_failure` | ✓ | ✓ | ✓ |
+| `fmv_state` | ✓ | | |
+| `fn_clear` | ✓ | | |
+| `fn_disable` | ✓ | | |
+| `fn_entry_dump` | ✓ | | |
+| `fn_entry_tail` | ✓ | | ✓ |
+| `fn_exit_dump` | ✓ | | |
+| `fn_filter` | ✓ | | |
+| `fn_stats` | ✓ | | |
+| `fntrace_arm` | ✓ | ✓ | |
+| `fntrace_arm_clear` | ✓ | | |
+| `fntrace_armed` | ✓ | | |
+| `fntrace_arms` | | ✓ | |
+| `fntrace_clear` | ✓ | | |
+| `fntrace_disarm` | | ✓ | |
+| `fntrace_dump` | ✓ | ✓ | |
+| `fntrace_reset` | | ✓ | |
+| `fntrace_unfiltered` | | ✓ | |
+| `frame` | ✓ | | ✓ |
+| `frame_fingerprint` | ✓ | | |
+| `frame_perf` | ✓ | | |
+| `frame_range` | ✓ | ✓ | ✓ |
+| `frame_timeseries` | ✓ | ✓ | ✓ |
+| `freeze_check` | ✓ | | |
+| `game_options` | ✓ | | |
+| `get_frame` | ✓ | ✓ | ✓ |
+| `get_quads` | ✓ | | |
+| `get_registers` | ✓ | ✓ | ✓ |
+| `get_snapshots` | ✓ | ✓ | ✓ |
+| `gl_coh_ring` | ✓ | | |
+| `gl_fbo_peek` | ✓ | | |
+| `gl_interp` | ✓ | | |
+| `gl_present_ring` | ✓ | | |
+| `gl_vram_diff` | ✓ | | |
+| `gl_wide_fast` | ✓ | | |
+| `gl_ws_ablate` | ✓ | | |
+| `gp1_dump` | ✓ | | ✓ |
+| `gpu_frame_dump` | ✓ | | ✓ |
+| `gpu_opcodes` | ✓ | | |
+| `gpu_ring_stats` | ✓ | | |
+| `gpu_state` | ✓ | | ✓ |
+| `gte_frame_stats` | ✓ | | |
+| `gte_intpl_dump` | ✓ | | |
+| `gte_latch_dump` | ✓ | | |
+| `gte_ring_dump` | ✓ | | |
+| `gte_state` | ✓ | | |
+| `history` | ✓ | ✓ | ✓ |
+| `hle_dump` | ✓ | | ✓ |
+| `idle_skip` | ✓ | | |
+| `imask_trace` | ✓ | | |
+| `insn_freeze` | ✓ | | |
+| `insn_freeze_snapshot` | ✓ | | |
+| `insn_freeze_status` | ✓ | | |
+| `insn_freeze_target` | ✓ | | |
+| `irq_state` | ✓ | | ✓ |
+| `irqctx_ring` | ✓ | | |
+| `kernel_bless` | ✓ | | |
+| `latency` | ✓ | | |
+| `load_transitions` | ✓ | | |
+| `lockstep` | ✓ | | |
+| `lockstep_func` | ✓ | | |
+| `mc_status` | ✓ | | |
+| `mdec_state` | ✓ | | |
+| `mdec_trace` | ✓ | | |
+| `mdec_trace_clear` | ✓ | | |
+| `mem_words` | ✓ | | |
+| `mmio_clear` | ✓ | | ✓ |
+| `mmio_dump` | ✓ | | ✓ |
+| `mmx6_freshfix` | ✓ | | |
+| `overlay_candidates` | ✓ | | |
+| `overlay_capture_dump` | ✓ | | |
+| `overlay_cps_probe` | ✓ | | |
+| `overlay_diff_off` | ✓ | | |
+| `overlay_diff_on` | ✓ | | |
+| `overlay_dump` | ✓ | | |
+| `overlay_fp_dump` | ✓ | | |
+| `overlay_irq_ratelimit` | ✓ | | |
+| `overlay_irq_suppress_off` | ✓ | | |
+| `overlay_irq_suppress_on` | ✓ | | |
+| `overlay_loader_status` | ✓ | | |
+| `overlay_native_block` | ✓ | | |
+| `overlay_native_event_granularity` | ✓ | | |
+| `overlay_native_off` | ✓ | | |
+| `overlay_native_on` | ✓ | | |
+| `overlay_native_ring` | ✓ | | |
+| `overlay_rescan` | ✓ | | |
+| `overlay_shadow_detail` | ✓ | | |
+| `overlay_shadow_dump` | ✓ | | |
+| `pace_state` | ✓ | | |
+| `pad_status` | ✓ | ✓ | |
+| `parity_ctl` | ✓ | ✓ | |
+| `parity_dump` | ✓ | ✓ | |
+| `pause` | ✓ | | ✓ |
+| `phase_hot` | ✓ | | |
+| `phase_profile` | ✓ | | |
+| `ping` | ✓ | ✓ | ✓ |
+| `present_ring` | ✓ | | |
+| `press` | ✓ | ✓ | |
+| `probe_clear` | ✓ | | |
+| `probe_trace` | ✓ | | |
+| `quit` | ✓ | | ✓ |
+| `ra_load_watch` | ✓ | | |
+| `read_frame_ram` | ✓ | ✓ | ✓ |
+| `read_ram` | ✓ | ✓ | ✓ |
+| `record_frame` | ✓ | | |
+| `record_frame_dump` | ✓ | | |
+| `record_reads_dump` | ✓ | | |
+| `restore_trace` | ✓ | | |
+| `restore_trace_clear` | ✓ | | |
+| `restore_trace_window` | ✓ | | |
+| `rtrace_arm` | ✓ | ✓ | |
+| `rtrace_clear` | ✓ | | |
+| `rtrace_disarm` | | ✓ | |
+| `rtrace_disarm_all` | | ✓ | |
+| `rtrace_dump` | ✓ | ✓ | |
+| `rtrace_ranges` | ✓ | ✓ | |
+| `rtrace_reset` | | ✓ | |
+| `rtrace_stats` | ✓ | ✓ | |
+| `run_to_frame` | ✓ | | ✓ |
+| `s3_smear_watch` | ✓ | | ✓ |
+| `savestate` | ✓ | | |
+| `screenshot` | ✓ | ✓ | ✓ |
+| `screenshot_file` | ✓ | ✓ | ✓ |
+| `set_input` | ✓ | ✓ | ✓ |
+| `set_snapshot` | ✓ | ✓ | ✓ |
+| `sio_arm_audit` | ✓ | | |
+| `sio_burst_stats` | ✓ | | |
+| `sio_ctrl_reg_clear` | ✓ | | |
+| `sio_ctrl_reg_trace` | ✓ | | |
+| `sio_ctrl_reg_window` | ✓ | | |
+| `sio_irq_dump` | ✓ | | |
+| `sio_irq_window` | ✓ | | |
+| `sio_pc_trace` | ✓ | | |
+| `sio_pc_window` | ✓ | | |
+| `sio_state` | ✓ | | ✓ |
+| `sio_trace` | ✓ | ✓ | |
+| `sio_trace_reset` | | ✓ | |
+| `sio_trace_window` | ✓ | | |
+| `sio_write_window` | | ✓ | |
+| `sp_ring` | ✓ | | |
+| `spu_events` | ✓ | ✓ | |
+| `spu_events_reset` | ✓ | | |
+| `spu_ram` | ✓ | | |
+| `spu_status` | ✓ | | |
+| `spu_voices` | ✓ | ✓ | |
+| `sreg_trace_clear` | ✓ | | |
+| `sreg_trace_dump` | ✓ | | |
+| `sreg_trace_find` | ✓ | | |
+| `sreg_trace_stats` | ✓ | | |
+| `stack_profile` | ✓ | | |
+| `starv_ring` | ✓ | | |
+| `step` | ✓ | | ✓ |
+| `synth_recurse` | ✓ | | |
+| `thread_ctx_ring` | ✓ | | |
+| `thread_trace` | ✓ | | |
+| `thread_trace_clear` | ✓ | | |
+| `timers_state` | ✓ | | |
+| `turbo` | ✓ | | ✓ |
+| `turbo_audio_sink` | ✓ | | |
+| `turbo_loads` | ✓ | | |
+| `turbo_state` | ✓ | | ✓ |
+| `unknown_dispatch_log` | ✓ | | |
+| `unwatch` | ✓ | | ✓ |
+| `vblank_rate` | ✓ | | |
+| `vk_perf` | ✓ | | |
+| `vram_peek` | ✓ | ✓ | ✓ |
+| `vsync_query_hle` | ✓ | | |
+| `warm_cd_route` | ✓ | | |
+| `watch` | ✓ | | ✓ |
+| `wide_full` | ✓ | | |
+| `wide_shot` | ✓ | | |
+| `write_ram` | ✓ | | ✓ |
+| `ws_aspect` | ✓ | | |
+| `ws_aspect_cone_site` | ✓ | | |
+| `ws_backdrop_margin` | ✓ | | |
+| `ws_backdrop_ring` | ✓ | | |
+| `ws_backdrop_stretch` | ✓ | | |
+| `ws_census` | ✓ | | |
+| `ws_dbg_stretch` | ✓ | | |
+| `ws_dome` | ✓ | | |
+| `ws_dome_probe` | ✓ | | |
+| `ws_far_threshold` | ✓ | | |
+| `ws_hud_mode` | ✓ | | |
+| `ws_margin` | ✓ | | |
+| `ws_nw` | ✓ | | |
+| `wtrace_add` | ✓ | | |
+| `wtrace_all_dump` | ✓ | ✓ | |
+| `wtrace_all_reset` | ✓ | ✓ | |
+| `wtrace_all_stats` | ✓ | ✓ | |
+| `wtrace_arm` | ✓ | ✓ | |
+| `wtrace_boot_dump` | ✓ | | |
+| `wtrace_boot_reset` | ✓ | | |
+| `wtrace_boot_stats` | ✓ | | |
+| `wtrace_boot_summary` | ✓ | | |
+| `wtrace_clear` | ✓ | | ✓ |
+| `wtrace_del` | ✓ | | |
+| `wtrace_disarm` | ✓ | ✓ | |
+| `wtrace_disarm_all` | ✓ | ✓ | |
+| `wtrace_dump` | ✓ | ✓ | ✓ |
+| `wtrace_range` | ✓ | | ✓ |
+| `wtrace_ranges` | ✓ | ✓ | |
+| `wtrace_reset` | ✓ | ✓ | |
+| `wtrace_stats` | ✓ | ✓ | |
+| `wtrace_trans_dump` | ✓ | | |
+| `wtrace_trans_reset` | ✓ | | |
+| `wtrace_trans_stats` | ✓ | | |
+| `xlate` | ✓ | | |
+| `xprobe` | ✓ | | |
+| `xprobe_arm` | ✓ | | |
+
+
diff --git a/THIRD_PARTY_ATTRIBUTION.md b/THIRD_PARTY_ATTRIBUTION.md
index 35129ccb..b57d5f90 100644
--- a/THIRD_PARTY_ATTRIBUTION.md
+++ b/THIRD_PARTY_ATTRIBUTION.md
@@ -1,5 +1,18 @@
# Third-Party Attribution
+## OpenBIOS — PCSX-Redux's free PS1 BIOS
+
+[OpenBIOS](https://github.com/grumpycoders/pcsx-redux) (src/mips/openbios) by
+the PCSX-Redux authors, licensed **MIT** (notice: `bios/OpenBIOS.LICENSE`;
+the binary also links permissively-licensed code from
+[uC-sdk](https://github.com/grumpycoders/uC-sdk), noted there too). Vendored
+as the prebuilt image `bios/openbios.bin` (pin and build recipe recorded in
+`bios/OpenBIOS.toml`) and statically recompiled by
+`psxrecomp-bios --config bios/OpenBIOS.toml` exactly like the retail BIOS.
+Normal runtime builds stage it automatically so players supply only a disc;
+`bios/OpenBIOS.LICENSE` always rides alongside the shipped image.
+
+
## sljit — stack-less JIT compiler (Tier-2 in-process overlay backend)
[sljit](https://github.com/zherczeg/sljit) by Zoltan Herczeg, licensed
diff --git a/WIDESCREEN.md b/WIDESCREEN.md
index 6594947c..17130b48 100644
--- a/WIDESCREEN.md
+++ b/WIDESCREEN.md
@@ -65,19 +65,59 @@ sprite_anchor_addr = "0x1F800070" # scratchpad holding the prim's
# GTE-projected anchor SXY at tag time.
hud_sprt_squash = true # center/edge-squash untagged SPRTs
# (screen-space HUD/menus).
+auto_ui_squash = true # pre-scan the current GPU linked list,
+ # select its highest populated UI rank,
+ # and share one anchor across each
+ # complete glyph/icon run.
clear_reveal = true # clear synthetic native-wide side margins
# at opted-in scene/map boundaries (default false).
nw_left_hud_packet_lo = "0x000E3400" # optional targeted left-HUD packet range
nw_left_hud_packet_hi = "0x000E4100" # (half-open); avoids shifting 2D scenery.
offer_ultrawide = true # separate experimental 21:9 launcher row.
+adaptive_view = true # expose live resize-driven aspect mode.
+
+# Proven object/model participation comparisons may be forced only while a
+# widened world view is configured. The complete instruction word guards
+# same-address overlay variants; true 4:3 executes the vanilla comparison.
+[[widescreen.cull.keep]]
+address = "0x8002B310"
+expected = "0x28A21C01" # SLT/SLTU/SLTI/SLTIU only
+result = 1 # forced comparison result, 0 or 1
```
+When `adaptive_view` is enabled by the game, the launcher's **Aspect ratio**
+selector gains an **Adaptive** entry. The previously selected fixed aspect
+still determines the initial window shape. Once the game window exists,
+resizing it continuously
+updates the GTE/native-wide projection, cull margins, wide render target, and
+present aspect. The live ratio is clamped to 4:3 on the narrow side and to the
+widest mode the game offers (`16:9`, or `21:9` with `offer_ultrawide`). BIOS,
+FMV, menus, and other title-classified 2D frames retain their existing 4:3
+pillarbox policy. The user choice persists as `[video] adaptive_view` in
+`settings.toml`.
+
+The native-wide draw-area early-out correction was independently identified
+and contributed by **OpokXeno** in
+[PR #73](https://github.com/mstan/psxrecomp/pull/73). The landed variant keeps
+that mirror-space correction while restricting it to active framebuffer
+targets, so offscreen texture targets retain the original host-side pruning.
+
Tomba's values are Ghidra-evidenced: `0x8005E08C` is the shared per-prim helper
all ~23 character render functions (RTPS cluster `0x800459E0`–`0x8004FB54`) call;
the RTPS preamble stores the anchor SXY to scratchpad `0x1F800070`.
+`auto_ui_squash` is a runtime-only opt-in and does not require regenerated game
+code. It applies only on the projection-and-stretch path: eligible
+axis-aligned textured quads/rectangles in the front populated ordering-table
+layer are grouped by texture and screen row before any command is transformed.
+Depth-sorted world packets, full-frame backdrops, and true 4:3 frames remain
+untouched.
+
**Changing `sprite_tag_funcs` requires a game regen** (the tag callback is
-emitted into the generated C). Everything else is runtime-only.
+emitted into the generated C). `widescreen.cull.keep` is consumed by both the
+native recompiler and the dirty-RAM interpreter: regenerate main/overlay native
+code after changing it, while the interpreter reads the same guarded records
+from the runtime config.
---
diff --git a/accuracy/axis5_sio_controller.md b/accuracy/axis5_sio_controller.md
index 2263f5f6..52a09878 100644
--- a/accuracy/axis5_sio_controller.md
+++ b/accuracy/axis5_sio_controller.md
@@ -6,7 +6,13 @@ against the in-tree Beetle/Mednafen oracle
`frontio.cpp`, `input/dualshock.cpp`, `input/gamepad.cpp`, `input/memcard.cpp`)
and nocash psx-spx "Controllers and Memory Cards".
-READ-ONLY research. No code was modified. Citations use `file:line`.
+Historical read-only research; citations use the line numbers from that audit.
+
+> **Status 2026-07-26:** Tomba now offers explicit Analog / D-Pad modes instead
+> of Hybrid. The Tomba-only `g_pad_legacy_cfg` protocol fork, its game-config
+> key, debug command, and helper script have been removed. References to them
+> below document the retired implementation and are not descriptions of the
+> current runtime.
---
@@ -245,7 +251,10 @@ every title — which is exactly what the LEGACY comment predicted, letting the
## 4. Prioritized fix list (player-visible impact first)
-> **STATUS 2026-06-27 (branch wt/tomba2-axis5-controller):** Fixes 1, 2, 3 IMPLEMENTED
+> **STATUS 2026-07-26:** Fixes 1, 2, 3 are implemented. Fix 4 is also complete:
+> Tomba no longer uses Hybrid, and the legacy pad-config branch has been removed.
+>
+> **Prior status 2026-06-27 (branch wt/tomba2-axis5-controller):** Fixes 1, 2, 3 IMPLEMENTED
> in sio.c (0x43 live frame; 0x45 live analog byte; 0x44 analog-mode-lock + hybrid-flip
> lock gate), transcribed from dualshock.cpp. Validated no-regression on Tomba 2
> (digital pad, pad 0xFFFF at rest, boots). These are gated to the MODERN SM and leave
@@ -275,10 +284,10 @@ every title — which is exactly what the LEGACY comment predicted, letting the
game-locked mode) — mirror `dualshock.cpp:203,714-725`. Prevents the hybrid
heuristic from fighting games that pin DualShock.
-4. **[P1] Retire `g_pad_legacy_cfg` once 1–3 land** (sio.c:665-717, the LEGACY
- block + `legacy_pad_config` game.toml key + Tomba's opt-in). The completeness
- path the file itself mandates: one correct SM for all titles, no per-game
- compat branch. Validate Tomba on the modern SM after 1–3.
+4. **[DONE 2026-07-26] Retire `g_pad_legacy_cfg`.** Tomba now exposes only
+ explicit Analog / D-Pad modes, so there is no live type flip to accommodate.
+ The flag, legacy SIO branches, config key, debug command, helper script, and
+ Tomba opt-in were removed. All titles use the modern DualShock state machine.
5. **[P2] Echo-back `0x4D` rumble map** (D7). Even with no rumble output,
echo the previous `rumble_magic[]` bytes (`dualshock.cpp:993-1024`) so a game
@@ -321,8 +330,8 @@ the always-on rings), drive the same input, then diff:
does), then deflect the stick. `pad_status.analog` must NOT change while
locked; cross-check Beetle stays in its locked mode across the same input.
-- **Fix 4 (retire legacy):** run Tomba on the modern SM with `legacy_pad_config`
- off. `pad_status` at rest = `0xFFFF` both slots, no disconnect blips; menu
+- **Fix 4 (retire legacy):** run Tomba in both explicit Analog and D-Pad modes
+ on the modern SM. `pad_status` at rest = `0xFFFF` both slots, no disconnect blips; menu
responsive; in-game d-pad↔stick transitions produce no phantom dash/unpause.
Diff a 600-byte `sio_trace` window vs Beetle for the same navigation — RX
streams should match transaction-for-transaction (allowing the D3 stick-order
diff --git a/bios/OpenBIOS.LICENSE b/bios/OpenBIOS.LICENSE
new file mode 100644
index 00000000..cfe37313
--- /dev/null
+++ b/bios/OpenBIOS.LICENSE
@@ -0,0 +1,30 @@
+License notice for bios/openbios.bin — OpenBIOS, the PCSX-Redux project's
+free PS1 BIOS (https://github.com/grumpycoders/pcsx-redux, src/mips/openbios).
+Built at the commits pinned in bios/OpenBIOS.toml. The binary also links code
+from uC-sdk (https://github.com/grumpycoders/uC-sdk), a mixture of permissive
+(non-reciprocal) licenses that require this mention.
+
+The OpenBIOS source carries the following notice, reproduced here because the
+binary is redistributed with games verified against it:
+
+MIT License
+
+Copyright (c) 2019 PCSX-Redux authors
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/bios/OpenBIOS.toml b/bios/OpenBIOS.toml
new file mode 100644
index 00000000..de6a781b
--- /dev/null
+++ b/bios/OpenBIOS.toml
@@ -0,0 +1,126 @@
+# bios/OpenBIOS.toml — BIOS build profile for PCSX-Redux OpenBIOS.
+#
+# OpenBIOS is the PCSX-Redux project's from-scratch, MIT-licensed PS1 BIOS
+# (https://github.com/grumpycoders/pcsx-redux, src/mips/openbios). MIT means
+# the image is REDISTRIBUTABLE: a game verified against it ships its BIOS
+# with the build and the player supplies only a disc (redistributable=true
+# below removes the whole BIOS-selection surface at runtime).
+#
+# Pinned build (bios/openbios.bin + the ELFs the seeds derive from):
+# pcsx-redux 55fbf0468345acfbe5512628a3be5c0cfe3f22e7
+# uC-sdk 69e06871824e2d62069487a7426ded09090ceb69
+# toolchain g++-mipsel-none-elf (GCC 16.1.0, static.grumpycoder.net build)
+# build make -C src/mips/openbios PREFIX=mipsel-none-elf FORMAT=elf32-littlemips
+# Regenerating seeds after a re-pin (--extra is REQUIRED to keep the 5
+# empirically-discovered dispatch-miss seeds; without it they are silently
+# dropped and the regenerated set has 641 entries, not 646):
+# tools/gen_bios_seeds_from_elf.py --profile bios/OpenBIOS.toml \
+# --elf .../openbios.elf --elf .../shell.elf \
+# --extra recompiler/seeds/openbios_dispatch_miss.json \
+# --out recompiler/seeds/openbios_elf_seeds.json
+#
+# The address model below was established by direct disassembly of the boot
+# path (reset vector -> copy loops), each copy confirmed twice (boot + the
+# warm-restart path reloading lengths from ROM constants; the shell length
+# additionally confirmed by the blob's own self-relocation constants).
+
+[program]
+name = "PCSX-Redux OpenBIOS"
+id = "OPENBIOS"
+rom = "bios/openbios.bin"
+load_address = "0xBFC00000"
+entry_pc = "0xBFC00000"
+text_size = "0x80000"
+
+[program.image]
+sha256 = "fabe498fbf224e4721f12f31b6f5fe0659205e341dc4e5c5f91b9bd1a1011c57"
+license = "MIT"
+redistributable = true
+
+[recompiler]
+seeds = "recompiler/seeds/openbios_elf_seeds.json"
+out_dir = "generated"
+out_stem = "OpenBIOS"
+strict = true
+
+# OpenBIOS's boot-time bulk code copies. Windows are BYTE-PROVEN against
+# openbios.elf (readelf -lW program headers give the .data LMA; the embedded
+# shell blob was located by content search; both ranges memcmp-verified
+# ELF-content == image-content — an earlier hand-disassembly of the copy
+# routines mis-read both source offsets by 0xB8/0x2C0-class deltas, which
+# emitted every shell/ramtext function from shifted bytes):
+# - kernel .data (VMA 0x500, WAX — code+data): LMA 0xBFC1E4D4, FileSiz
+# 0x46A0, copied to RAM 0x500 at boot. Byte-verbatim -> kernel_bless.
+# - shell blob (objcopy of shell.elf, linked at 0x80030000): embedded in
+# .rodata at ROM 0xA500, size 0x13D44, copied to RAM 0x80030000. The
+# first-crash a1=0xBFC1E244 was exactly this blob's END pointer mid-copy.
+# NOT modeled, by design:
+# - the 4x16-byte vector stubs copied to RAM 0x80/0xA0/0xB0/0xC0
+# (runtime-written trampolines -> dirty-RAM interp territory, rule 18);
+# - the RAM->RAM A0-table move (0x4844 -> 0x200): nothing dispatches TO the
+# table, discovery gets entries from ELF symbols, and runtime dispatch
+# keys on the actual jump targets.
+[recompiler.address_model]
+normalize_mask = "0x1FFFFFFF"
+
+[[recompiler.address_model.copy]]
+name = "Kernel ramtext"
+rom_lo = "0x1FC1E4D4"
+rom_hi = "0x1FC22B74"
+ram_lo = "0x00000500"
+runtime_base = "0x00000500"
+dispatch_key = "ram"
+kernel_bless = true
+
+[[recompiler.address_model.copy]]
+name = "Shell"
+rom_lo = "0x1FC0A500"
+rom_hi = "0x1FC1E244"
+ram_lo = "0x00030000"
+runtime_base = "0x80030000"
+dispatch_key = "rom"
+kernel_bless = false
+
+# No [[recompiler.install_slots]]: none identified yet. Runtime-installed
+# stubs fall to the dirty-RAM interpreter, which is correct (just slower)
+# until a slot is identified and added.
+
+# HLE anchors: only the boot-skip anchor is exported. deliver_event_ret is
+# deliberately ABSENT (0) until the B0 event services are validated against
+# this kernel's EvCB behavior (they are believed compatible — OpenBIOS
+# reimplements the documented kernel ABI, EvCB anchor 0x120/0x124, TCB
+# 0x108/0x110 — but believed is not verified). Absent = the kernel-call HLE
+# tier is STRUCTURALLY UNAVAILABLE here and config/env cannot force it on.
+#
+# That refusal is scoped to kernel calls ONLY. The boot-skip is a separate axis
+# gated on shell_entry_phys alone and works under pure LLE, so `bios_hle` (and
+# the deprecated `fast_boot` alias) skips the boot animation and goes straight
+# to the game on THIS image exactly as it does on retail SCPH-1001 — the flag
+# means one thing on every BIOS. Both axes are decided in one pure place,
+# psx_bios_hle_plan() (runtime/include/bios_hle_plan.h), which exists because
+# deriving the boot-skip from the anchor-gated call tier silently took the skip
+# away on this image (fixed 2026-07-27).
+#
+# shell_entry_phys was verified against the generated C (2026-07-23):
+# - startShell (ELF symbol, ROM 0xBFC074DC) ends `lui $t9,0x8003; jr $t9`
+# (0xBFC07534/38) — a TAIL jump to the RAM shell copy at 0x80030000
+# that goes through the dispatch trampoline, where the hook fires on
+# the pre-normalize phys 0x00030000;
+# - $ra at that point is the jal link 0xBFC06EA8 (startShell restores its
+# stack before the tail jump), so the intercept's resume-at-$ra lands
+# exactly at the shell call's continuation;
+# - that continuation OVERWRITES $v0 immediately (addiu $v0,$zero,8 — a
+# boot-progress POST byte), so the intercept's v0=0 is never observed,
+# and the boot proceeds unconditionally to the SYSTEM.CNF/EXE load
+# (gameMainThunk), exactly the retail LoadRunShell contract the skip
+# models. The shell's own animation (waitVSync/flip/convexHull in
+# shell.elf) is the only thing skipped.
+[recompiler.runtime_exports]
+shell_entry_phys = "0x00030000"
+
+# No [runtime] block: a BIOS profile states facts about an image, never runtime
+# preferences, and load_bios_config rejects one outright. This file used to end
+# with `[runtime] bios_hle = false`, which nothing read — the recompiler parsed
+# it into a field with no consumers. Runtime options live in game.toml /
+# settings.toml; per-image HLE capability is expressed by which anchors
+# [recompiler.runtime_exports] above does and does not export.
diff --git a/bios/SCPH1001.toml b/bios/SCPH1001.toml
new file mode 100644
index 00000000..1f95ade7
--- /dev/null
+++ b/bios/SCPH1001.toml
@@ -0,0 +1,79 @@
+# bios/SCPH1001.toml — BIOS build profile for the retail Sony SCPH-1001 image.
+#
+# One profile per BIOS image. Consumed by `psxrecomp-bios --config`, by
+# tools/regen_bios.sh (which reads rom/seeds/out_dir/out_stem from here), and
+# by tools/bios_emitter_fingerprint.sh (which hashes this file, the seeds and
+# the ROM into the generated/.emitter.sha staleness stamp).
+#
+# The .BIN image itself stays untracked (bios/* gitignore rule) — profiles are
+# the only tracked contents of this directory.
+
+[program]
+name = "Sony SCPH-1001 BIOS"
+id = "SCPH-1001"
+rom = "bios/SCPH1001.BIN"
+load_address = "0xBFC00000"
+entry_pc = "0xBFC00000"
+text_size = "0x80000"
+
+[recompiler]
+seeds = "recompiler/seeds/phase2_ghidra_seeds.json"
+out_dir = "generated"
+out_stem = "SCPH1001"
+strict = true
+
+# 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.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.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.
+
+# Kernel-RAM PCs the BIOS overwrites with 4-instruction dispatch stubs at
+# runtime (CLAUDE.md rule 18; docs/dynamic_handler_install.md). The emitter
+# plants a dirty-check hook at these PCs so the installed stub executes.
+[[recompiler.install_slots]]
+ram_addr = "0x00000CF0" # SIO data-byte handler dispatch slot
+
+# 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. 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"
diff --git a/bios/openbios.bin b/bios/openbios.bin
new file mode 100644
index 00000000..f7597d11
Binary files /dev/null and b/bios/openbios.bin differ
diff --git a/build_recompiler.sh b/build_recompiler.sh
deleted file mode 100755
index e94ebcbd..00000000
--- a/build_recompiler.sh
+++ /dev/null
@@ -1,3 +0,0 @@
-#!/usr/bin/env bash
-
-cmake -S recompiler -B recompiler/build -G Ninja -DCMAKE_BUILD_TYPE=Release && cmake --build recompiler/build
diff --git a/build_runtime.sh b/build_runtime.sh
deleted file mode 100755
index dfe41a17..00000000
--- a/build_runtime.sh
+++ /dev/null
@@ -1,3 +0,0 @@
-#!/usr/bin/env bash
-
-cmake -S runtime -B runtime/build -G Ninja -DCMAKE_BUILD_TYPE=Release && cmake --build runtime/build --target psx-runtime
diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md
index 3293157c..cefb7073 100644
--- a/docs/ARCHITECTURE.md
+++ b/docs/ARCHITECTURE.md
@@ -64,7 +64,8 @@ the paths to its generated C. The runtime provides:
(`cdrom.c`, `iso_reader.cpp`), MDEC, SIO0 controllers/memory cards
(`sio.c`, `memcard.c`), SPU (`spu.c`), GTE (`gte.cpp`), and interrupt delivery
(`interrupts.c`).
-- **Host services** — window/input/audio via SDL2, a cooperative-thread
+- **Host services** — window/input/audio via SDL3 by default (SDL2 is an
+ explicit compatibility backend), a cooperative-thread
scheduler on host fibers (`psx_fiber.c`: Win32 Fibers / POSIX `ucontext`), and
the optional debug TCP server (`debug_server.c`).
@@ -75,14 +76,31 @@ kernel, and it is the reference implementation and the correctness oracle. There
are no per-vector HLE shims replacing it.
On top of that, PSXRecomp carries an optional **HLE tier** (`bios_hle`, on by
-default for player convenience) that can synthesize the post-boot kernel handoff
-state to skip the BIOS boot sequence, and intercept a small set of BIOS services
-— always falling through to the recompiled BIOS for anything it doesn't
-implement. LLE stays fully linked and is what every accuracy check runs against.
-Turn it off with `[runtime] bios_hle = false` or `PSX_BIOS_HLE=0`; with it off the
-build behaves as pure LLE. (Design notes:
+default for player convenience) that skips the BIOS boot sequence and intercepts
+a small set of BIOS services — always falling through to the recompiled BIOS for
+anything it doesn't implement. LLE stays fully linked and is what every accuracy
+check runs against. Turn it off with `[runtime] bios_hle = false` or
+`PSX_BIOS_HLE=0`; with it off the build behaves as pure LLE. (Design notes:
[`docs/internal/HLE_SCHEDULER_CARVEOUT_PLAN.md`](internal/HLE_SCHEDULER_CARVEOUT_PLAN.md).)
+Those are **two independent axes**, and the distinction matters because a build
+links more than one BIOS ([`BIOS_SELECTION.md`](BIOS_SELECTION.md)):
+
+| axis | what it needs from the image | on retail SCPH-1001 | on bundled OpenBIOS |
+|---|---|---|---|
+| boot-skip | `shell_entry_phys` — works under pure LLE | yes | yes |
+| kernel-call HLE | `deliver_event_ret` — the kernel's own DeliverEvent `$ra` | yes | refused, loudly |
+
+So **"skip the BIOS and go straight to the game" means the same thing on every
+BIOS**: the boot-skip is not synthesis, it just returns immediately from the
+shell call, so it needs nothing BIOS-specific beyond knowing where the shell is
+entered. Whether the *kernel-call* tier is additionally available is a separate,
+per-image question, and refusing it must never cancel the boot-skip — deriving
+one from the other is the bug fixed in 2026-07. Both axes are decided in one pure
+place, `psx_bios_hle_plan()`
+([`runtime/include/bios_hle_plan.h`](../runtime/include/bios_hle_plan.h)), which
+is unit-tested over the whole matrix.
+
### Static / overlay / interpreter dispatch
This is the heart of the system and has its own doc,
@@ -123,7 +141,10 @@ Three GPU backends behind one interface:
- **OpenGL** — GPU-authoritative VRAM/FBO renderer, the default; moves
rasterization and supersampling onto the GPU. Falls back to software if GL
init fails. (See [`docs/internal/GL_RENDERER_HANDOFF.md`](internal/GL_RENDERER_HANDOFF.md).)
-- **Vulkan** — experimental, off by default (`PSX_ENABLE_VULKAN=OFF`).
+- **Vulkan** — experimental. The build option `PSX_ENABLE_VULKAN` defaults **ON**
+ (compiled when the SDK tools are present), but it is not the runtime default
+ renderer: selecting it also requires the game to offer Vulkan and the user to
+ request it, otherwise the runtime falls back to OpenGL.
Widescreen (a genuine wider GTE FOV, not a stretch) is opt-in and gen-time; see
[`WIDESCREEN.md`](../WIDESCREEN.md) and
@@ -146,9 +167,14 @@ backend against the interpreter. See
## Configuration
-Every process has a **BIOS config**; a game adds a **game config** (`game.toml`),
-and the two are merged (game wins on scalar keys; program blocks and generated
-dispatch tables are additive). Full schema:
+A game is configured by its **game config** (`game.toml`). A **BIOS config**
+(`bios/*.toml`) describes a BIOS image's identity and address model.
+
+The two are **not merged.** The BIOS config is consumed only by the recompiler
+at build time (`psxrecomp-bios`, and `psxrecomp-game` for the address model);
+the shipping runtime never loads it — `runtime/src/main.cpp` contains no call to
+`load_bios_config`. `[runtime]` keys therefore take effect only from `game.toml`,
+`settings.toml`, the CLI, and the environment. Full schema:
[`docs/config_schema.md`](config_schema.md).
## Where to go next
diff --git a/docs/BIOS_SELECTION.md b/docs/BIOS_SELECTION.md
new file mode 100644
index 00000000..c699730c
--- /dev/null
+++ b/docs/BIOS_SELECTION.md
@@ -0,0 +1,149 @@
+# Which BIOS a build uses
+
+A PlayStation game needs a BIOS. PSXRecomp can supply one — **OpenBIOS**, an
+MIT-licensed, from-scratch PS1 BIOS from the PCSX-Redux project that we are
+allowed to redistribute — so a player can be handed a build and a disc image and
+just play. A player who prefers their own dumped retail BIOS can use that
+instead.
+
+Both recompiled BIOS backends are linked into every normal build. The OpenBIOS
+image itself and its MIT notice are staged in `bios/` beside the executable;
+the retail image is never shipped and comes from the player. Which backend runs
+is decided when the game launches, not when it is built.
+
+## The rule
+
+> **No BIOS chosen → OpenBIOS. A BIOS the player explicitly chose → that BIOS.**
+
+That is the whole user-facing contract. A player never has to find a BIOS file
+to start playing, and never loses the ability to use their own.
+
+A developer can switch this off for a title that is known not to work on
+OpenBIOS; then a retail BIOS is required and the player is prompted for one.
+
+## What counts as "chosen"
+
+Only a deliberate act by the player:
+
+- `--bios ` on the command line, or
+- a BIOS selected in the launcher / settings, which is then remembered.
+
+**Finding a BIOS file on disk does not count.** Earlier versions searched
+directories near the executable and would quietly adopt whatever they found.
+That made behaviour depend on what happened to be lying around — two players
+with identical builds could get different BIOSes. Discovery is no longer a
+selection mechanism.
+
+A player who has chosen a BIOS can clear that choice and go back to OpenBIOS;
+the choice is not permanent.
+
+## "A retail BIOS" means one specific image
+
+A build compiles in exactly one retail image (currently SCPH-1001) alongside
+OpenBIOS. The compiled-in code is generated *from that exact image*, so a
+different dump — SCPH-101, SCPH-5552, a bad rip — cannot be substituted: the
+recompiled code would execute against data it does not match, and crash.
+
+Every build therefore checks the identity of a chosen BIOS before using it, and
+says which image it expects if the check fails. This is not DRM and not a
+preference; it is the difference between running and wild-jumping.
+
+The acceptance gate compares **file size and CRC32** against each linked
+backend's recorded identity (`bios_backend_for_file` in `runtime/src/main.cpp`).
+A SHA-256 is also recorded per image (`image_sha256` in
+`runtime/include/psx_bios_image.h`) and is surfaced for provenance and bug
+reports, but it is **not** part of the accept/reject decision.
+
+If a chosen BIOS does not match:
+
+- with OpenBIOS available, the player is told why and can continue on OpenBIOS;
+- with OpenBIOS disabled for the title, the choice is rejected and re-prompted.
+
+## Developer setting
+
+One optional line in `game.toml`:
+
+```toml
+[runtime]
+openbios = true # default. false = this title requires a retail BIOS.
+```
+
+Set `openbios = false` only for a title with a **verified** OpenBIOS
+incompatibility. Per-title compatibility is not implied by the framework
+supporting OpenBIOS — verify a title before shipping it that way.
+
+This setting is deliberately **not** overridable by a player's `settings.toml`,
+unlike most runtime options. It records a developer's compatibility finding, not
+a preference. A player re-enabling OpenBIOS on a title known to break under it
+produces confusing bug reports and no benefit.
+
+There is no third mode. A title either allows OpenBIOS (and still honours an
+explicit retail choice) or requires retail. "OpenBIOS only, retail forbidden"
+would take away a working option from players for no reason.
+
+## Boot behaviour does not depend on which BIOS runs
+
+Whichever BIOS is active, `[runtime] bios_hle` (on by default) skips the BIOS
+boot sequence and goes straight to the game. That is deliberate and enforced: the
+skip is not a synthesized handoff, it just returns immediately from the BIOS's
+call into its shell, so all it needs is the per-image address of that shell entry
+— which every linked BIOS records. A player who switches BIOS gets the same
+boot, and the flag means one thing everywhere.
+
+The HLE tier's *other* half — servicing a few kernel calls (the B0 event family)
+in the runtime instead of the recompiled BIOS — **is** per-image, because it needs
+the kernel's own `DeliverEvent` return address to send a delivered callback back
+to. Retail SCPH-1001 exports it; OpenBIOS deliberately does not until its event
+semantics are validated, so OpenBIOS runs those calls on the recompiled kernel and
+prints one line at startup saying so. That refusal is scoped to kernel calls and
+never cancels the boot-skip.
+
+Until 2026-07 it did: the boot-skip was derived from the already-refused
+kernel-call decision, so choosing OpenBIOS silently sat the player in the boot
+animation while the identical build on retail went straight to the game. The two
+axes are now resolved in one pure, unit-tested function
+(`psx_bios_hle_plan()`, `runtime/include/bios_hle_plan.h`), and the test asserts
+the boot decision is byte-identical across the two images.
+
+## Save data
+
+**Memory cards are unaffected.** They are card images; they carry no BIOS state
+and move freely between BIOSes and between builds.
+
+**Savestates are BIOS-specific.** The kernel occupies RAM, and OpenBIOS and
+retail lay that RAM out differently, so a savestate captured under one BIOS is
+not valid under the other. Savestates therefore record which BIOS was active and
+refuse to load under a different one. Without that check, switching BIOS would
+silently corrupt a restored state — the only quiet failure mode in this design,
+which is why it is enforced rather than warned about.
+
+## Why both are compiled in
+
+The alternative — a build flavour per BIOS — was the original design, and it
+required a game to state its BIOS in three places that nothing cross-checked
+(`bios_config` in `game.toml`, plus two CMake variables). Setting one and
+forgetting another produced a build that linked one BIOS's code against another
+BIOS's generated game code, and it compiled cleanly.
+
+Carrying both costs roughly 20 MB of binary and removes that entire class of
+mistake: one build configuration, one source of truth, the choice made at
+runtime where it can be validated.
+
+It is affordable because the game's own recompiled code is **identical**
+regardless of which BIOS it was generated against — verified byte-for-byte on
+Tomba!, Tomba! 2 and Ape Escape. Only the BIOS code is duplicated, and the two
+images collide on just 22 symbols, which are namespaced per BIOS.
+
+## Attribution
+
+OpenBIOS is MIT-licensed. Its notice is vendored at `bios/OpenBIOS.LICENSE`,
+with the upstream source pin and build recipe in `bios/OpenBIOS.toml` and
+attribution in `THIRD_PARTY_ATTRIBUTION.md`. Builds that ship it credit the
+PCSX-Redux authors in the launcher, whether or not the licence compels it.
+
+Native runtime builds automatically stage both `bios/openbios.bin` and
+`bios/OpenBIOS.LICENSE`. Release packaging must copy that directory as a unit;
+shipping the image without its notice violates the distribution contract.
+
+Retail BIOS images are **not** redistributable and are never shipped. A player
+using one supplies their own dump.
diff --git a/docs/BUILDING.md b/docs/BUILDING.md
index ac97a05e..d02dbf0c 100644
--- a/docs/BUILDING.md
+++ b/docs/BUILDING.md
@@ -8,8 +8,11 @@ Silicon & Intel), and **Linux**. There are two things you can build:
- a **game** — done from that game's own repository, which links this framework
in as a submodule (see [Linking the framework](#linking-the-framework)).
-You always supply your own **`SCPH1001.BIN` BIOS** and, for a game, your own
-legally-obtained **disc image**. This project ships none of those.
+For the retail **`SCPH1001.BIN` BIOS** you supply your own dump; the
+MIT-licensed **OpenBIOS** alternative is bundled (`bios/openbios.bin`, see
+[Regenerating BIOS backends](#regenerating-bios-backends)). For a
+game you always supply your own legally-obtained **disc image** — this
+project ships no game data.
## Toolchain requirements
@@ -25,7 +28,8 @@ legally-obtained **disc image**. This project ships none of those.
| Dependency | How it's provided | Used for |
|---|---|---|
-| **SDL2** | System. Windows/MSVC: prebuilt pack auto-found at `../sdl2-msvc/SDL2-*`. Windows/MinGW + macOS + Linux: found via pkg-config. | Window, input, GL/Vulkan context, audio, threads |
+| **SDL3 3.4+** | Preferred system package; otherwise SDL 3.4.10 is fetched from the official release archive with a pinned SHA-256. | Window, input, GL/Vulkan context, audio, threads |
+| **SDL2** | Optional explicit fallback (`-DPSX_SDL_BACKEND=SDL2`). Windows/MSVC uses `../sdl2-msvc/SDL2-*`; MinGW/macOS/Linux use pkg-config. | Compatibility and A/B testing |
| **fmt** 9.1.0 | vendored `recompiler/lib/fmt` | String formatting (runtime uses it header-only) |
| **toml11** | vendored `recompiler/lib/toml11` | Parsing `game.toml` / configs |
| **ELFIO** | vendored `recompiler/lib/ELFIO` | ELF parsing (recompiler only) |
@@ -33,38 +37,56 @@ legally-obtained **disc image**. This project ships none of those.
| **TinyCC (TCC) 0.9.27** | Not in this repo — downloaded at release-packaging time and bundled beside the game exe in `overlay_toolchain/`. | Toolchain-free overlay compilation for players (run as a subprocess) |
| **Python 3** | System (development) or an embedded copy bundled in releases | Runs `tools/compile_overlays.py` in the overlay pipeline |
| **OpenGL** | System (`opengl32` on Windows; `find_package(OpenGL)` elsewhere) | The GL renderer |
-| **Vulkan** | Headers only, optional, **off by default** (`PSX_ENABLE_VULKAN=OFF`); loaded dynamically via SDL. Shader compilation needs `glslc` from the Vulkan SDK. | The experimental Vulkan renderer |
+| **Vulkan** | Headers only, **on by default** (`PSX_ENABLE_VULKAN=ON`) — built when the SDK tools are available, otherwise skipped; loaded dynamically via SDL. Shader compilation needs `glslc` from the Vulkan SDK. Pass `-DPSX_ENABLE_VULKAN=OFF` to exclude it. | The experimental Vulkan renderer |
Developers building overlays locally just need `gcc` on `PATH` (the `gcc` tier);
the bundled `tcc` matters only for end-user release packages.
### Get the source
-This repository has no required git submodules:
+This repository has two submodules, **neither of which is required** for the
+recompiler and runtime builds described below:
+
+| Submodule | Needed when |
+|---|---|
+| `recomp-ui` | `-DPSX_RECOMP_UI=ON` (the launcher UI). Configure **fails** with a `FATAL_ERROR` if this is ON and the submodule is absent. |
+| `lib/recomp-net` | `-DPSX_NETPLAY=ON` (netplay). |
+
+A plain clone is enough to build the recompiler and the runtime:
```sh
git clone https://github.com/mstan/psxrecomp.git
```
+Add `--recurse-submodules` if you intend to build the launcher or netplay:
+
+```sh
+git clone --recurse-submodules https://github.com/mstan/psxrecomp.git
+```
+
## Per-platform prerequisites
**Windows (MSYS2/MinGW — recommended for release parity):**
```sh
# In an MSYS2 MinGW64 shell:
pacman -S --needed mingw-w64-x86_64-toolchain mingw-w64-x86_64-cmake \
- mingw-w64-x86_64-ninja mingw-w64-x86_64-SDL2 mingw-w64-x86_64-ccache
+ mingw-w64-x86_64-ninja mingw-w64-x86_64-ccache
```
**macOS:**
```sh
-brew install sdl2 pkg-config ninja cmake
+brew install ninja cmake
```
**Linux (Debian/Ubuntu):**
```sh
-sudo apt install build-essential cmake ninja-build pkg-config libsdl2-dev
+sudo apt install build-essential cmake ninja-build
```
+These are the SDL3-default prerequisites. For the SDL2 fallback, additionally
+install `mingw-w64-x86_64-SDL2`, `sdl2`, or `libsdl2-dev` respectively, plus
+`pkg-config` outside MSVC.
+
## Build the framework
Two CMake trees: the recompiler (a tool) and the runtime (the engine).
@@ -74,12 +96,23 @@ Two CMake trees: the recompiler (a tool) and the runtime (the engine).
cmake -S recompiler -B recompiler/build -G Ninja -DCMAKE_BUILD_TYPE=Release
cmake --build recompiler/build
-# 2. (Optional) regenerate the BIOS C from your SCPH1001.BIN
-# Requires bios/SCPH1001.BIN to be present.
-bash tools/regen_bios.sh
+# 2. REQUIRED before the first runtime build: generate a BIOS backend.
+# Step 3 configures psx-runtime from generated/OpenBIOS_full.c. If generated/
+# is empty, CMake does NOT fall back -- it fails at configure time with
+# "Cannot find source file: .../generated/OpenBIOS_full.c" followed by
+# "No SOURCES given to target: psx-runtime". Re-run this step whenever the
+# recompiler emitter changes; a stale generated/ raises a fingerprint-mismatch
+# warning from runtime.cmake.
+#
+# OpenBIOS can always be regenerated from the tracked image. Regenerating
+# the retail backend requires your own bios/SCPH1001.BIN dump and is genuinely
+# optional -- OpenBIOS alone is enough to build and boot.
+bash tools/regen_bios.sh --config bios/OpenBIOS.toml
+bash tools/regen_bios.sh --config bios/SCPH1001.toml # optional, needs your own dump
# 3. Runtime → produces psx-runtime (BIOS-only for this repo)
-cmake -S runtime -B runtime/build -G Ninja -DCMAKE_BUILD_TYPE=Release
+cmake -S runtime -B runtime/build -G Ninja -DCMAKE_BUILD_TYPE=Release \
+ -DPSX_RECOMP_UI=OFF
cmake --build runtime/build --target psx-runtime
```
@@ -89,11 +122,20 @@ On Linux/macOS, `tools/setup_dev.sh` performs the same source-checkout setup:
sh tools/setup_dev.sh
```
-It checks for the native toolchain and SDL2 development package, builds the CLI
-and recompiler tools, refreshes generated BIOS C when `bios/SCPH1001.BIN` is
-present, and builds the BIOS-only runtime when BIOS/generated sources are
-available. It does not create per-game runtime targets; use the CLI generator
-for game projects.
+It checks the native toolchain, builds the CLI and recompiler tools, refreshes
+generated BIOS C when `bios/SCPH1001.BIN` is present, and builds the BIOS-only
+runtime when BIOS/generated sources are available. It does not create per-game
+runtime targets; use the CLI generator for game projects.
+
+### Check the build
+
+After step 1 above — no BIOS or disc needed — verify the tree is sane:
+
+```sh
+cd recompiler/build && ctest --output-on-failure
+```
+
+29 tests, under five seconds. See [`TESTING.md`](TESTING.md).
On Windows with MSVC or plain MinGW makefiles, swap `-G Ninja` for your generator
(e.g. `-G "Unix Makefiles"`); everything else is identical.
@@ -109,10 +151,15 @@ On Windows with MSVC or plain MinGW makefiles, swap `-G Ninja` for your generato
| Option | Default | Effect |
|---|---|---|
| `PSX_DEBUG_TOOLS` | ON for Debug/RelWithDebInfo, OFF for Release | TCP debug server + heartbeat + per-block recording |
-| `PSX_STATIC_RUNTIME` | ON for MinGW Release | Self-contained exe (statically links SDL2 + libgcc/libstdc++) |
-| `PSX_LAUNCHER` | ON | Allow downstream game projects to wire recomp-ui launcher support |
-| `PSX_ENABLE_VULKAN` | OFF | Build the experimental Vulkan renderer |
-| `PSX_BUILD_COSIM` | OFF | Build the first-divergence co-sim oracle target |
+| `PSX_SDL_BACKEND` | `SDL3` | Host backend. Set `SDL2` explicitly for compatibility or A/B testing. |
+| `PSX_SDL3_FETCH` | ON | Fetch the pinned SDL3 release when a compatible system SDL3 package is unavailable. Set OFF for offline/system-only builds. |
+| `PSX_STATIC_RUNTIME` | ON for MinGW Release | Self-contained exe (statically links SDL + libgcc/libstdc++) |
+| `PSX_RECOMP_UI` | ON | Wire a downstream game's pinned recomp-ui launcher; set OFF for headless/generated builds |
+| `PSX_ENABLE_VULKAN` | **ON** | Build the experimental Vulkan renderer when the SDK tools are present (skipped if not). Pass `OFF` to exclude it outright. |
+| `PSX_NETPLAY` | OFF | Netplay support; requires the `lib/recomp-net` submodule |
+
+See [SDL backends](SDL_BACKENDS.md) for the fallback command and the initial
+SDL3/SDL2 game A/B results.
## Build and run a game
@@ -124,9 +171,13 @@ for TombaRecomp:
# Extract the game's PS-X EXE from your disc (helper included in the game repo):
python3 tools/extract_psx_exe.py tomba/tomba.bin SCUS_942.36 tomba/SCUS_942.36
-# Regenerate the game's C from the disc/EXE (game repos invoke the recompiler
-# binary directly with their config):
-../psxrecomp/recompiler/build/psxrecomp-game --config game.toml
+# Regenerate the game's C from the disc/EXE. The framework is a submodule at
+# psxrecomp/ inside the game repo, so build its recompiler once, then run it.
+# (Game repos also ship tools/regen.sh (macOS/Linux) / tools/regen.ps1 (Windows)
+# that wrap this command.)
+cmake -S psxrecomp/recompiler -B psxrecomp/recompiler/build -G Ninja -DCMAKE_BUILD_TYPE=Release
+cmake --build psxrecomp/recompiler/build
+psxrecomp/recompiler/build/psxrecomp-game --config game.toml
# Configure + build the game runtime:
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release
@@ -180,10 +231,42 @@ these files without it). If you hit this on a framework source file, please open
an issue with your `gcc -v` / `as --version` — the build should apply the flag
for you.
-**`SDL2 MSVC dev package not found`.** The MSVC build expects the prebuilt SDL2
-pack beside the repo at `../sdl2-msvc/SDL2-*`. Use the MSYS2/MinGW toolchain
-(which finds SDL2 via pkg-config) or place the pack there.
+**`SDL3 3.4+ was not found`.** The default build normally downloads the pinned
+release. Check network access, install a system SDL3 package and provide
+`SDL3_DIR`, or re-enable `-DPSX_SDL3_FETCH=ON`.
+
+**`SDL2 MSVC dev package not found`.** This applies only when
+`-DPSX_SDL_BACKEND=SDL2` is selected. Place the prebuilt SDL2 pack beside the
+repo at `../sdl2-msvc/SDL2-*`, or use the MSYS2/MinGW toolchain with SDL2
+available through pkg-config.
**Overlays never compile / stay slow.** In development you need `gcc` on `PATH`
for the `gcc` tier; otherwise areas stay in the interpreter. See
[`EXECUTION_MODEL.md`](EXECUTION_MODEL.md).
+
+## Regenerating BIOS backends
+
+Every normal runtime links both statically recompiled BIOS backends. OpenBIOS
+symbols and retail SCPH-1001 symbols are namespaced so they coexist in one
+executable; the active backend is selected at launch:
+
+```bash
+# OpenBIOS (tracked and redistributable)
+tools/regen_bios.sh --config bios/OpenBIOS.toml
+
+# Retail SCPH-1001 (requires your own local dump)
+tools/regen_bios.sh --config bios/SCPH1001.toml
+```
+
+`PSXRECOMP_BIOS_STEMS` defaults to `OpenBIOS;SCPH1001`. The runtime build
+automatically stages `bios/openbios.bin` and `bios/OpenBIOS.LICENSE` beside
+every native executable. A release packager must copy that `bios/` directory
+unchanged.
+
+At runtime, no explicit player choice means OpenBIOS. A retail BIOS selected
+with the launcher, `settings.toml`, or `--bios` wins after its exact identity is
+verified. `[runtime] openbios = false` is reserved for a title with a verified
+OpenBIOS incompatibility. See [`BIOS_SELECTION.md`](BIOS_SELECTION.md).
+
+OpenBIOS seeds come from its ELF symbol tables (no Ghidra pass needed):
+see the pin + regeneration recipe in `bios/OpenBIOS.toml`.
diff --git a/docs/FEATURES.md b/docs/FEATURES.md
index 94bc5afd..a64de402 100644
--- a/docs/FEATURES.md
+++ b/docs/FEATURES.md
@@ -46,7 +46,22 @@ includes `generated/overlays.c` when it exists.
---
-## Layer A — Dynamic DLL loading (runtime, SHELVED)
+## Layer A — Dynamic DLL loading (runtime) — SHIPPED
+
+> **Status corrected 2026-07-27.** This section described Layer A as "SHELVED".
+> It is not: dynamic DLL overlay loading is the **live production dispatch
+> path**. `runtime/src/overlay_loader.c` is ~4,400 lines with 15 `LoadLibrary`
+> call sites and was last modified 2026-07-26;
+> [`COMPILING_OVERLAYS.md`](COMPILING_OVERLAYS.md) documents building the
+> `cache/…/_.dll` shards that ship inside release packages. The
+> shelving described below was a real moment in the project's history, and the
+> account of the CRC-keying dead ends is worth keeping — but it was superseded.
+> Content-hash dispatch replaced the unstable CRC key, and the Load Game crash
+> was subsequently root-caused rather than worked around.
+>
+> Read everything below this banner as **history**, not current state. For the
+> current pipeline see [`COMPILING_OVERLAYS.md`](COMPILING_OVERLAYS.md) and
+> [`AOT_OVERLAY_PLAN.md`](AOT_OVERLAY_PLAN.md).
### What it was supposed to do
@@ -100,6 +115,10 @@ but the Load Game crash was not diagnosed before shelving.
The infrastructure is complete. The blocker is code correctness, not plumbing.
+*(Historical. All three items were subsequently resolved — the CRC key was
+replaced by content-hash dispatch and the cache layout now carries an emitter
+tag, `//-/cg_/_.dll`.)*
+
---
## Tomba overlay map (known)
diff --git a/docs/LOAD_TIME_ZERO.md b/docs/LOAD_TIME_ZERO.md
index 8ab5db91..f52c477d 100644
--- a/docs/LOAD_TIME_ZERO.md
+++ b/docs/LOAD_TIME_ZERO.md
@@ -375,9 +375,12 @@ rejected on paper.
`traps.c:540`; store `data_shards.c` (494 LOC, no TODOs); config
`[data_shards] funcs` `config_loader.cpp:716-723`. `.dss` per shard,
no manifest (doc/impl drift).
-- bios_hle: B0 events 0x07-0x0D only; boot = one-shot shell-skip at
- `PSX_SHELL_ENTRY_PHYS` + boot-turbo until game start. A0/C0 observed,
- fall through.
+- bios_hle: B0 events 0x07-0x0D only; boot = one-shot shell-skip at the
+ image's `shell_entry_phys` + boot-turbo until game start. A0/C0 observed,
+ fall through. The boot-skip and the B0 tier are independent axes
+ (`bios_hle_plan.h`): the skip needs only `shell_entry_phys` and so lands
+ the same load-time win under OpenBIOS as under retail, even though
+ OpenBIOS refuses the B0 tier for want of `deliver_event_ret`.
- `_wt-tomba-loadshards-fw` worktree dir is EMPTY/unregistered — the
spike lives only as branch `spike/tomba-load-shards` (fw) +
`spike/load-shards` (TombaRecomp). Recreate worktrees before E4.
diff --git a/docs/MOD_PACKAGES.md b/docs/MOD_PACKAGES.md
new file mode 100644
index 00000000..44a88d5d
--- /dev/null
+++ b/docs/MOD_PACKAGES.md
@@ -0,0 +1,392 @@
+# PSXRecomp mod packages and features
+
+A `.psxmod` is a versioned installation, provenance, and trust boundary. A
+package may contribute any number of independently configurable **features**.
+The launcher presents those features as the primary Mods list; package
+installation, version selection, and removal are a secondary management view.
+
+Feature identity is always `(package_id, feature_id)`. Enabling one feature
+never enables, disables, or reconfigures another feature.
+
+The player selects a verified stock BIN/CUE. Resolution produces guarded native
+operations and sparse disc overlays without rewriting or replacing that stock
+image.
+
+## Feature manifest
+
+```toml
+format_version = 1
+id = "example.localization"
+version = "1.2.0"
+name = "Example Localization Pack"
+author = "Example Author"
+description = "Independent title and script features."
+license = "MIT"
+resolver = "declarative"
+
+[[target]]
+game_id = "SLUS-00000"
+# Required for disc overlays. Use the digest of the supported stock image.
+disc_sha256 = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
+
+[[feature]]
+id = "title-screen"
+name = "Title Screen"
+description = "Selects the title-screen artwork."
+group = "Localization"
+default_enabled = false
+
+[[feature]]
+id = "retranslation"
+name = "Retranslation"
+description = "Uses the revised English script."
+group = "Localization"
+
+[[option]]
+feature = "title-screen"
+id = "variant"
+label = "Title artwork"
+type = "choice"
+default = "rockman"
+
+[[option.choice]]
+value = "mega-man"
+label = "Mega Man X6 (USA)"
+
+[[option.choice]]
+value = "rockman"
+label = "Rockman X6 (Japan)"
+
+[[patch]]
+feature = "title-screen"
+target = "main_exe"
+address = 0x80041234
+expected = "2a 00 02 24"
+replace = "0e 00 02 24"
+when = { variant = "rockman" }
+
+[[overlay]]
+feature = "retranslation"
+target = "disc_raw"
+offset = 123456
+file = "assets/retranslated-script.bin"
+sha256 = "..."
+# Optional additional guard over the same range in the stock image.
+expected_sha256 = "..."
+```
+
+Every `[[option]]`, `[[patch]]`, and `[[overlay]]` in a feature-style manifest
+must name its owning feature. Ambiguous operations are rejected.
+
+Option types are `boolean`, `choice`, and bounded `integer`. Conditions are
+feature-local: `when = { option = "value", ... }` requires every listed option
+to match. The legacy `when_option`/`when_value` pair remains accepted for a
+single condition.
+
+## Bounded integer patches
+
+Package format 2 can encode a bounded integer option directly into a guarded
+write:
+
+```toml
+format_version = 2
+
+[[feature]]
+id = "starting-lives"
+name = "Starting Lives"
+
+[[option]]
+feature = "starting-lives"
+id = "count"
+label = "Lives"
+type = "integer"
+min = 0
+max = 99
+step = 1
+default = 2
+
+[[patch]]
+feature = "starting-lives"
+target = "main_exe"
+address = 0x8001DE64
+expected = "02 00 02 24"
+replace_from = { option = "count", encoding = "u16le", offset = 0 }
+
+[[patch]]
+feature = "starting-lives"
+target = "main_exe"
+address = 0x8001DE70
+expected = "03 00 02 24"
+replace_from = { option = "count", encoding = "u16le", offset = 0, addend = 1 }
+```
+
+`replace_from` and literal `replace` are mutually exclusive. The referenced
+option must be a bounded integer owned by the same feature. The initial
+encodings are `u8`, `u16le`, and `u32le`. `offset` selects a byte field inside
+the expected guard and defaults to zero. Generated replacement bytes begin as
+an exact copy of the expected bytes, then the encoded value replaces only that
+field. This lets a MIPS immediate, for example, retain a guard and collision
+claim over its complete instruction. `addend` is the only supported transform,
+and the complete declared option range after that addend must fit the unsigned
+encoding.
+
+There is deliberately no host-endian encoding, signed inference, mask, shift,
+scale, expression language, or partial-field merge. A package uses multiple
+guarded `[[patch]]` entries when the same value has multiple destinations.
+Generated bytes enter the ordinary pre-boot write plan, collision checks, and
+fingerprint. A generated value identical to the stock guard is omitted as a
+no-op, so an enabled stock-valued option does not claim or conflict on bytes it
+does not change.
+
+Integer values use canonical decimal text. Leading plus signs, redundant
+leading zeroes, values outside the bounds, and values not aligned to `step` are
+rejected.
+
+## Ordered values and split MIPS immediates
+
+Package format 3 adds feature-local ordering constraints for related integer
+fields:
+
+```toml
+format_version = 3
+
+[[constraint]]
+feature = "rank-thresholds"
+kind = "ordered_integer"
+direction = "nondecreasing"
+options = ["rank-c", "rank-b", "rank-a"]
+```
+
+All listed options must be integer options on that feature. Defaults must
+satisfy the constraint. While a feature is enabled, an edit that would invert
+the order is rejected with the neighboring option labels. Disabled features
+may retain an incomplete or invalid draft, but cannot be enabled until it is
+valid. `nonincreasing` is also supported.
+
+Format 3 also provides a narrow, typed transform for constants constructed by
+a linked MIPS `LUI`/`ORI` pair:
+
+```toml
+replace_from = {
+ option = "speed",
+ encoding = "mips_lui_ori_u32",
+ omit_when_default = true
+}
+```
+
+The patch must target one aligned, fully guarded eight-byte `main_exe`
+instruction pair. The loader verifies the opcodes and register linkage, then
+places the raw high and low 16-bit halves into the two immediates. It does not
+apply signed-`ADDIU` carry adjustment. `offset` and `addend` are not accepted
+for this encoding.
+
+`omit_when_default` suppresses the entire patch when the selected value equals
+the option default. This models source tools whose declared default means
+"make no writes," including cases where multiple guarded sites contain
+different stock values. For any nondefault selection, every declared site
+retains its collision claim even if one generated replacement happens to equal
+its stock guard.
+
+## Sparse fields and integer predicates
+
+Package format 4 separates a patch's complete expected-byte guard from the
+fields it owns and writes. This is for semantic records whose independently
+configurable fields share one useful guard:
+
+```toml
+format_version = 4
+
+[[option]]
+feature = "saber-timing"
+id = "frames"
+label = "Frames"
+type = "integer"
+min = 0
+max = 99
+default = 2
+
+# Positive values change only byte 0.
+[[patch]]
+feature = "saber-timing"
+target = "main_exe"
+address = 0x80077640
+expected = "02 42 01 02"
+fields = [
+ { offset = 0, option = "frames", encoding = "u8" },
+]
+when_integer = { option = "frames", op = "gt", value = 0 }
+
+# Zero has a narrow, explicitly declared compound representation.
+[[patch]]
+feature = "saber-timing"
+target = "main_exe"
+address = 0x80077640
+expected = "02 42 01 02"
+fields = [
+ { offset = 0, replace = "01" },
+ { offset = 2, replace = "00" },
+]
+when_integer = { option = "frames", op = "eq", value = 0 }
+```
+
+`fields` is mutually exclusive with `replace` and `replace_from`. Every field
+has a nonnegative `offset` and exactly one payload form:
+
+- `replace = "..."` supplies non-empty literal bytes; or
+- `option` plus `encoding` uses a bounded integer option on the same feature,
+ with the optional checked `addend`.
+
+Fields must fit inside `expected` and their owned byte ranges may not overlap.
+The supported dynamic encodings are the existing `u8`, `u16le`, `u32le`, and
+linked `mips_lui_ori_u32` forms. A sparse MIPS field owns only the two
+immediate halfwords while the complete linked instruction pair remains
+guarded; as with format 3, that encoding does not accept an addend.
+
+Resolution omits individual fields whose generated payload already equals the
+guard. If no field changes, the patch is a no-op. Otherwise the plan retains
+the complete guard and the exact remaining owned ranges. Runtime guard
+validation checks every byte of `expected` before applying any writes, while
+collision detection and writing use only the owned fields. Thus two features
+can safely own adjacent bytes in one record without either overwriting the
+other. Overlapping guards must still agree on their expected bytes; conflicting
+guards make the plan unsatisfiable and are rejected.
+
+`when_integer` is a patch-level, feature-local predicate over one bounded
+integer option. `op` is exactly one of `eq`, `ne`, `lt`, `le`, `gt`, or `ge`,
+and `value` is an integer constant inside the option bounds. Equality
+constants must also be selectable under the option's `step`. String-valued
+`when` conditions and one `when_integer` predicate may coexist and are ANDed.
+
+Sparse fields and integer predicates are still pre-boot plan construction.
+They do not provide a general expression evaluator, masks, arithmetic beyond
+the checked field addend, package code execution, or per-frame dispatch.
+
+## Trusted static plugins
+
+Package format 5 can activate a game-owned plugin that is already statically
+linked into the executable:
+
+```toml
+format_version = 5
+
+[[feature]]
+id = "warp-debug-menu"
+name = "Warp Debug Menu"
+
+[[plugin]]
+feature = "warp-debug-menu"
+id = "example.warp-debug"
+```
+
+The plugin id is a stable registry key, not a library path or symbol name. The
+package archive supplies no native code. Resolution fails before launch when
+an enabled plugin has no registered implementation or when two features claim
+the same plugin id. Active plugin identities and owners participate in the
+canonical plan fingerprint.
+
+An implementation may register an activation callback, a deterministic
+guest-VBlank callback, or both under the same id. Activation runs after the
+launcher's final mod-plan commit and before renderer/window initialization; it
+is appropriate for a game-owned mod that selects a fixed display aspect or
+another pre-boot host feature. VBlank callbacks run from the emulated GPU
+VBlank event, independent of host presentation, pacing, turbo, or skipped
+frames. Trusted callbacks receive only the narrow C services exposed by
+`runtime/include/mod_plugins.h`. Games should continue to use declarative
+patches and overlays when those operations are sufficient.
+
+## Native operations
+
+`main_exe` writes use PSX guest virtual addresses. Expected bytes are checked
+after the BIOS loads the executable, then the complete write plan is applied
+before its entry point. Changed executable ranges use the existing dirty-RAM
+interpreter/native-overlay machinery; untouched functions remain on the static
+native path.
+
+Small `disc_raw` and `disc_user` patches are equal-length guarded writes and may
+not cross a sector boundary:
+
+- `disc_raw` offsets use `lba * 2352 + byte_in_sector`.
+- `disc_user` offsets use `lba * 2048 + byte_in_sector`.
+
+File-backed `[[overlay]]` operations are intended for large assets and may span
+any number of sectors. Their paths must remain inside the archive. Payload
+size and SHA-256 are verified while scanning, but disabled payloads are not
+retained in memory. Enabled payloads are loaded and reverified during
+resolution, then indexed by target and LBA before boot. A CD read performs a
+direct indexed lookup rather than scanning every installed mod.
+
+Feature disc overlays require an exact `disc_sha256` on every target entry.
+`expected_sha256` can additionally guard the replaced stock range.
+
+## State and migration
+
+`mods/state.toml` format 2 stores selected package versions separately from
+per-feature enabled states and values:
+
+```toml
+format_version = 2
+
+[[package]]
+id = "example.localization"
+version = "1.2.0"
+
+[[feature]]
+package_id = "example.localization"
+id = "title-screen"
+enabled = true
+
+[feature.values]
+variant = "rockman"
+```
+
+State format 1 and package-only manifests remain readable as a migration aid.
+They appear through one synthetic legacy feature. New packages should use
+explicit features.
+
+The old `derived_disc` VCDIFF mechanism is legacy conversion scaffolding only.
+Feature-style manifests reject it. It is not a product mod primitive, fallback,
+or image-selection workflow; patched discs may be used offline as parity
+oracles while converting known mods to native operations.
+
+## Resolution and diagnostics
+
+Before boot, the manager:
+
+1. verifies the selected stock game and revision;
+2. expands only enabled features and their selected options;
+3. orders active packages deterministically by dependencies;
+4. verifies enabled payloads and operation bounds;
+5. collision-checks the complete owned byte-range plan and guard
+ compatibility;
+6. coalesces only truly identical target/range/expected/replacement writes or
+ identical overlays; and
+7. produces a canonical SHA-256 plan fingerprint.
+
+Incompatible overlaps fail before launch. Structured diagnostics identify both
+`(package, feature)` owners and the exact contested target range. The launcher
+marks both feature rows and lets the user decide what to disable. It never
+silently chooses a winner.
+
+Operation boundaries are not semantic boundaries. Legacy full-record writes
+compose when both their expected and replacement bytes agree throughout the
+owned intersection. Format-4 sparse patches collide only on declared owned
+fields, while their complete guards must remain mutually compatible.
+Partially overlapping overlays compose when their replacement payload bytes
+agree. A differing owned byte or incompatible guard produces a diagnostic at
+that exact location. Exact duplicate operations may be coalesced.
+
+Package-level dependencies and conflicts are reserved for actual implementation
+relationships. Mutually exclusive choices such as US versus Japanese artwork
+belong inside one feature as option values.
+
+## Trusted adapters and archive safety
+
+`resolver = "builtin:"` selects a resolver statically registered by the
+game. Format-5 plugin ids likewise select only statically registered
+implementations. Packages cannot load arbitrary native code or select
+arbitrary symbols.
+
+The installer accepts stored or DEFLATE-compressed ZIP entries, validates CRCs,
+rejects encrypted entries and unsafe or absolute paths, limits archives to 4096
+files and 256 MiB expanded size, stages extraction, validates the manifest, and
+publishes the version atomically.
diff --git a/docs/OVERLAY_XREF.md b/docs/OVERLAY_XREF.md
new file mode 100644
index 00000000..9892888b
--- /dev/null
+++ b/docs/OVERLAY_XREF.md
@@ -0,0 +1,201 @@
+# Overlay cross-reference tooling — `tools/overlay_xref.py`
+
+Static analysis of an overlay-heavy PS1 title has a hole in it: **the code you
+care about does not exist in the boot EXE.** Titles like Tomba 2 ship a ~165 KB
+loader and stream the entire game in as code overlays, so a Ghidra import of
+`SCUS_944.54` covers `0x80010000`–`0x80038800` and nothing else. Every
+`[widescreen.cull]` site this project has ever needed lives outside that range.
+
+`tools/overlay_xref.py` closes the hole by reading the runtime's overlay capture
+set. It is **game-agnostic** — point it at any title's capture directory.
+
+---
+
+## Why the capture set is a legitimate source of truth
+
+The runtime writes one JSON per distinct overlay body it has ever mapped to
+`/overlay_captures.json.d/.json`
+(schema `psxrecomp overlay capture v2`):
+
+| field | meaning |
+|---|---|
+| `load_addr` | virtual base the body was mapped at |
+| `size` | body length in bytes |
+| `bytes_b64` | the actual bytes that were in guest RAM |
+| `executed_pcs` | PCs observed executing inside this body |
+
+The set is **content-hashed and additive across every run**, so it is an
+always-on ring buffer that needs no arming — it already contains everything the
+game has ever loaded, including bodies from sessions that ended months ago. This
+satisfies the project's ring-buffer rule: you *query* it for the window of
+interest rather than arming a trace and hoping to catch the event.
+
+For scale, Tomba 2's probe capture dir holds **30 769 captures across 116
+distinct load bases**, with ~34 M executed-PC records.
+
+---
+
+## The problem it exists to solve
+
+`bias_sites`, `range_sites`, `screen_x_sites` and `depth_sites` are matched by
+**address plus opcode class only**. Overlays genuinely map *different code* to
+the same virtual address, so an address-only record can silently rewrite an
+unrelated instruction.
+
+This is not theoretical. Measured on Tomba 2's shipped config:
+
+| address | finding |
+|---|---|
+| `0x80110A08` (committed `bias_site`) | **3 distinct words**: `lw $s1,20($sp)` / `addiu $a1,$v1,0x80` / `addu $v1,$zr,$zr`. The opcode guard saves this one. |
+| `0x8013F138` (committed `bias_site`) | **5 variants, all `addiu $v0,$zr,imm`**, immediates `0x155 / 0x124 / 0x208 / 0x320 / 0x224`. Same opcode, so **one address-only record widens all five.** |
+| `0x8010E2FC` | **two entirely different overlay bodies** at the same VA (bases `0x8010C000` and `0x8010A000`). |
+
+**Run `word ` on every candidate site before committing it.** If more than
+one word appears, use a full expected-word guarded record rather than a bare
+address.
+
+---
+
+## Commands
+
+```
+overlay_xref.py -d [args]
+```
+
+| command | purpose |
+|---|---|
+| `index` | per-base summary: variant count, sizes, how many captures executed |
+| `word VA...` | every distinct word at each VA, with capture ids and exec counts |
+| `dis VA -n N` | disassemble N instructions, **one listing per distinct variant** |
+| `func VA` | walk back to the prologue, forward past `jr $ra`, then list |
+| `cover VA...` | which load bases/sizes cover each VA |
+| `scan --op OP [--imm-min/--imm-max]` | every site matching an opcode, optionally by immediate range |
+| `imm VALUE` | every site whose 16-bit immediate equals VALUE (ALU **and** load/store displacements) |
+| `callers VA` | every `jal`/`j` site targeting VA — a call graph without Ghidra analysis |
+| `window [--signed] [--gap N]` | find the centered-window gate idiom (below) |
+| `ramimage OUT` | composite the captures into a flat guest-RAM image for Ghidra |
+
+A parse of the full Tomba 2 set takes ~85 s cold; results are cached to
+`/.overlay_xref.cache` (~10 s warm). `--no-cache` forces a
+re-parse.
+
+---
+
+## The centered-window gate idiom
+
+Almost every PS1 visibility or proximity gate is written as one unsigned compare
+against a recentred delta:
+
+```
+subu rT, rRef, rObj # signed coordinate delta
+addiu rT, rT, BIAS # recentre <-- bias site
+andi rT, rT, 0xFFFF # truncate back to 16-bit (optional)
+sltiu rD, rT, SPAN # |delta| < SPAN/2 <-- range site
+```
+
+`window` finds these pairs. Instructions between the bias and the compare are
+allowed **only when they carry the value** (a same-register `andi`/`addiu`/
+`sll`/`sra`/`or`); a genuine redefinition breaks the pair.
+
+> **The `andi` truncation is part of the idiom, not a clobber.** A naive
+> "destination was rewritten, so this isn't a pair" heuristic breaks on it and
+> silently reports zero real gates while still finding plenty of false ones.
+> That failure is quiet and costs a whole analysis pass.
+
+### Reading the constants
+
+`SPAN == 2*BIAS + 1` is a symmetric window (`|delta| < BIAS`). Anything else is
+one-sided or asymmetric and deserves a second look:
+
+| bias / span | shape |
+|---|---|
+| `0xE6 / 0x1CD` (230 / 461) | symmetric, `|delta| < 230` |
+| `0x258 / 0x259` (600 / 601) | one-sided, `delta ∈ [-600, 0]` |
+| `0x320 / 0x44D` (800 / 1101) | asymmetric, `delta ∈ [-800, 300]` |
+
+### A trap worth naming
+
+**Most window pairs in a 3D action title are 12-bit ANGLE windows inside
+collision tests, not distance gates.** On Tomba 2, 43 pairs were found and the
+strongest-looking candidates turned out to be attack/hit cones:
+
+```
+sqrt(dx² + dz²) (jal 0x80084080)
+compare against summed radii at actor+0x80
+atan2 (jal 0x80085690)
+angle window (andi 0xFFF, bias/span)
+sb 1, 43(actor) # set the hit flag
+```
+
+Confirmed for `0x8001FBBC` (bias `0x580` / span `0x301`) and `0x8010E2FC`
+(682 / 1365). Widening one of these changes **combat reach**, not what is drawn.
+Always read the surrounding function before classifying a pair.
+
+---
+
+## Feeding Ghidra: `ramimage`
+
+Overlay code only exists in RAM at runtime, so the decompiler cannot reach it
+from a static EXE import. `ramimage` flattens the capture set into a raw guest
+RAM image that Ghidra can analyse:
+
+```bash
+python tools/overlay_xref.py -d \
+ ramimage /ghidra/_ram.bin \
+ --manifest /ghidra/_ram_conflicts.txt
+```
+
+Import it with `language = MIPS:LE:32:default`, `base_address = 0x80000000`,
+compiler `default`, then let auto-analysis run.
+
+Where several overlays mapped different bodies to one address, the winner is the
+variant seen **executing** in the most captures (ties broken by capture count).
+Every disagreement is written to the conflict manifest.
+
+> ### The image is a composite fiction in the conflicting regions
+>
+> Tomba 2's image covers **74.4 % of the 2 MB address space** and has
+> **44 576 conflicting addresses (~11 % of covered words)**. Inside those
+> regions the listing is a blend of overlays that never coexisted.
+>
+> Treat Ghidra output from this image as a **lead, not a fact**. Confirm every
+> instruction you intend to patch with `word ` / `dis ` against the real
+> per-overlay variants. See the worked example below for why this is not
+> optional.
+
+### Worked example — the decompiler will invent addressing
+
+Ghidra decompiled an actor state machine as reading the player through a global:
+
+```c
+uVar2 = FUN_800782b0(param_1 + 0x2c, (int)*(short *)(DAT_800e7f5c + 0x2c), ...);
+```
+
+The actual instructions are:
+
+```
+0x8006A560: 8E0200DC lw $v0,220($s0) # player via +0xDC
+0x8006A568: 8445002C lh $a1,44($v0)
+```
+
+There is **exactly one** real reference to `0x800E7F5C` in the entire capture set
+(`0x80069B70`) — confirmed independently by `imm 0x7F5C` and by Ghidra's own
+xrefs. The `DAT_800e7f5c` in that function body is decompiler inference from
+constant propagation, not a load. A site list built from the decompiler's view of
+addressing here would have been wrong.
+
+Recommended split of labour:
+
+| task | use |
+|---|---|
+| call graph / xrefs | `overlay_xref.py callers`, `imm`, `scan` — exact, needs no analysis pass |
+| readable control flow | Ghidra `create_function` + `get_code decompiler` |
+| **anything you will patch** | `word` / `dis` on the real variants — always |
+
+---
+
+## Related
+
+- `docs/config_schema.md` — `[widescreen.cull]` schema
+- `WIDESCREEN.md` — native-wide / adaptive view design
+- `docs/COMPILING_OVERLAYS.md` — how captures become compiled overlay DLLs
diff --git a/docs/RECURSION_BUG.md b/docs/RECURSION_BUG.md
index a084d4fa..4bc4e387 100644
--- a/docs/RECURSION_BUG.md
+++ b/docs/RECURSION_BUG.md
@@ -203,7 +203,7 @@ cmake --build ../psxrecomp/recompiler/build --target psxrecomp-game psxrecomp-bi
(cd ../psxrecomp && ./recompiler/build/psxrecomp-bios.exe --config bios/SCPH1001.toml)
../psxrecomp/recompiler/build/psxrecomp-game.exe --config tomba_soak_e.toml
# build:
-cmake -S . -B -G Ninja -DCMAKE_BUILD_TYPE=Release -DPSX_DEBUG_TOOLS=ON -DPSX_LAUNCHER=OFF \
+cmake -S . -B -G Ninja -DCMAKE_BUILD_TYPE=Release -DPSX_DEBUG_TOOLS=ON -DPSX_RECOMP_UI=OFF \
-DCMAKE_C_COMPILER=C:/msys64/mingw64/bin/gcc.exe -DCMAKE_CXX_COMPILER=C:/msys64/mingw64/bin/g++.exe
cmake --build --target psx-runtime -j8
# run (per-instance card; taskkill first):
diff --git a/docs/RELOCATION_MANIFEST_FORMAT.md b/docs/RELOCATION_MANIFEST_FORMAT.md
index 12c001f9..04dca27f 100644
--- a/docs/RELOCATION_MANIFEST_FORMAT.md
+++ b/docs/RELOCATION_MANIFEST_FORMAT.md
@@ -1,5 +1,18 @@
# Relocation Manifest Format
+> **STATUS AMENDMENT (2026-07, BIOS profiles).** The bulk boot-time
+> ROM-to-RAM copy case this document originally targeted is now carried by
+> the BIOS profile instead: `[[recompiler.address_model.copy]]` in
+> `bios/.toml` (single source of truth for the recompiler, the
+> emitted C, and the runtime via `psx_bios_image`; see
+> `recompiler/src/bios_address_model.h`). The kernel-bless mechanism in
+> `memory.c` remains the runtime enforcement of the "verify live RAM
+> against the ROM source before dispatching AOT code" contract described
+> here. This document stays authoritative ONLY for the oracle-observed
+> case — regions with `src_rom_addr: null` that cannot be seen statically
+> — which remains unimplemented.
+
+
**Status:** Phase B (design + first iteration), 2026-04-30.
**File on disk:** `generated/address_aliases.json`.
**Authority:** `PLAN.md:941-967` ("BIOS Relocation Is a Hard Gate"), `CLAUDE.md:333-365` (Rule 18).
diff --git a/docs/SAVESTATE_LOAD_PERFORMANCE.md b/docs/SAVESTATE_LOAD_PERFORMANCE.md
new file mode 100644
index 00000000..50c58def
--- /dev/null
+++ b/docs/SAVESTATE_LOAD_PERFORMANCE.md
@@ -0,0 +1,205 @@
+# Savestate load performance — solo and netplay
+
+Status: **shipped** (MotK / Bomberman runtime + vendored `recomp-net`).
+Scope: host-side restore hitch after F-key / protocol load, and lockstep
+correctness when both peers restore together.
+
+This is not the CD “load window” turbo work in [`LOAD_TIME_ZERO.md`](LOAD_TIME_ZERO.md).
+That doc is about *in-game* disc loads. This one is about **user savestate
+restore** (`.pst` / `boot_state_*`) feeling instant and staying in sync under
+delay-netplay.
+
+---
+
+## Symptoms that drove the work
+
+| Mode | Symptom | Host FPS | Root class |
+|------|---------|----------|------------|
+| Solo | Picture frozen ~1–several seconds after load; worse on 2nd/3rd load of same slot | ~60 | Giant `psx_advance_cycles` / IRQ blackout / present latch |
+| Netplay | Host `applied, waiting for guest…`; guest stuck at `applying after transfer…` then disconnect | N/A | INPUT / admit deadlock across apply ↔ ready |
+| Netplay spam | Hang when mashing load while holding directions | N/A | Same tip-starvation class; tip runway emptied by `hard_resync` |
+
+---
+
+## Solo restore path
+
+Call chain (HLE scheduler required):
+
+```
+F-key / debug → savestate_request_load
+ → savestate_poll (block leader)
+ → boot_state_load
+ → psx_cycles_resync_after_restore
+ → interrupts_resync_after_restore
+ → cdrom_accelerate_after_savestate
+ → psx_frontend_on_savestate_loaded
+ → psx_scheduler_resume_at(pc) /* longjmp; does not return */
+```
+
+### 1. Anchor host-only cycle deadlines (`psx_cycles_resync_after_restore`)
+
+**Bug:** `gte_ts_done` / `muldiv_ts_done` and load-absorb fields live on
+`CPUState` but are **not** in the savestate wire format. After a warm load,
+`psx_cycle_count` rewinds to the snapshot while those deadlines still sit on
+the pre-load live timeline. The next GTE / muldiv stall then advances
+`(live_ts − restored_cycle)` in one shot — tens of millions of cycles, many
+nested presents, `chk(e=0)`, sticky VBlank, no draw.
+
+**Fix:** After restore, set `gte_ts_done` / `muldiv_ts_done` to the restored
+`psx_cycle_count`, clear absorb/fudge state, and re-anchor device sync /
+idle-skip latches.
+
+- `runtime/src/psx_cycles.c` — `psx_cycles_resync_after_restore`
+- Called from `savestate_poll` immediately after a successful `boot_state_load`
+
+### 2. Clear absolute IRQ cooldowns (`interrupts_resync_after_restore`)
+
+**Bug:** `post_exception_cooldown_until` is an absolute guest-cycle stamp.
+Leaving it in the future after a clock rewind blocks every IRQ (including
+VBlank) until the restored clock “catches up” — freeze for however long the
+user played past the save, while host FPS stays ~60.
+
+**Fix:** Zero the cooldown and related exception / VBlank phase bookkeeping.
+
+### 3. Cap CD second-response debt (`cdrom_accelerate_after_savestate`)
+
+Restored / imminent CD command delays (ReadTOC, Init, seeks) can freeze the
+picture for ~1s after the restored frame presents. A short post-load boost
+window clamps outstanding delays so the display recovers immediately.
+
+### 4. Frontend present / audio re-anchor (`psx_frontend_on_savestate_loaded`)
+
+After restore:
+
+- Force present (`s_force_present_after_load`) so a disabled-display blank
+ latch or smooth-60 duplicate does not hide the restored frame.
+- Invalidate GL present-dirty early-out (`gl_renderer_invalidate_present`) —
+ critical on **2nd+ load of the same slot**, where the framebuffer can match
+ the last swap and skip `SwapWindow`.
+- Reset frame pacer + FPS baseline (admit / hitch can leave deadlines in the past).
+- Resync guest-cycle→audio sample budgeting.
+
+### 5. Smaller / faster `.pst` I/O (boot_state v4 zlib)
+
+Large sections may be zlib-compressed on save (`BS_SEC` pad bit0). Shrinks
+disk and helps slow storage; older readers still accept uncompressed v3.
+
+### 6. No post-load request cooldown
+
+A former 12-frame (earlier 60-frame) “load ignored” debounce in
+`request_load_inner` was removed. It was only key-repeat padding and could
+break netplay: protocol path entered `LOAD_APPLYING` while the cooldown
+silently refused to stage `s_load_pending`.
+
+Overlapping loads are gated elsewhere:
+
+- Solo: single `s_load_pending` slot (last request wins).
+- Netplay: `np_xfer_busy()` until the barrier clears.
+
+### Diagnostics
+
+`PSX_POST_LOAD_PROBE=1` arms a short post-load window that logs advance size,
+IRQ check outcomes, dirty/idle/horizon, and host ms (`main.cpp` + cycle
+attribution). Use this when a freeze returns; do **not** pause the runtime to
+measure — extend the probe / rings instead.
+
+---
+
+## Netplay load path
+
+Host-only initiate (`psx_netplay_request_load`). Guest follows via STATE_* on
+the same UDP/relay path as inputs.
+
+### High-level sequence
+
+```
+host: hash PROBE(op=LOAD, size, crc)
+guest: REPLY match?
+ yes → both stage savestate_request_load_protocol + LOAD_APPLYING
+ no → host STATE_BEGIN/CHUNK → guest writes sandbox
+ → both stage load in np_apply_ready_state (transfer complete)
+both: admit while savestate_pending (guest cycles → savestate_poll)
+both: local LOADED → LOAD_READY
+host: ready PROBE(op=LOAD, size=0, NP_LOAD_READY_CRC)
+guest: ACK when applied
+both: hard_resync + prime_delay_inputs (once)
+both: stay in LOAD_READY until try_admit succeeds → resume lockstep
+```
+
+### Correctness / performance rules (do not regress)
+
+1. **Stage apply only when both peers have the bytes**
+ - Do **not** call `savestate_request_load_protocol` on the host at SEND
+ begin. Host would restore during transfer, enter ready early, and starve
+ the guest of tips needed for `savestate_poll`.
+ - Hash-miss: both stage in `np_apply_ready_state` after transfer.
+ - Hash-hit: both stage when the probe reply is handled.
+
+2. **Keep INPUT flowing until mutual ready**
+ - `LOAD_APPLYING` / enter `LOAD_READY`: do **not** set
+ `input_send_suppress`.
+ - Suppress only inside `np_commit_load_sync` for the
+ `hard_resync` → `prime_delay_inputs` window (prime clears suppress).
+ - App barrier (`psx_netplay_poll_admit`) freezes sim after apply /
+ during ready; that is separate from INPUT emission.
+
+3. **Ready probe must not stall INPUT (`recomp-net`)**
+ - `rnet_session_state_probe` with `LOAD` + `total_size == 0` sets
+ `state_stall_sim = 0` (same as SAVE coord).
+ - Previously `state_stall_sim = 1` blocked `send_input_bundle` on the host
+ as soon as the first peer finished apply — same deadlock as suppress,
+ worse under spam because `hard_resync` leaves only ~D tip frames of
+ runway.
+ - Hash probes (`total_size != 0`) still stall until finish / transfer.
+
+4. **`hard_resync` + prime once at mutual ready, not at apply**
+ - Clearing rings / `sim_tick → 0` at apply time lets the later peer wipe
+ the earlier peer’s tip and stall resume.
+ - After mutual ready: clear local **and** remote rings, prime neutral
+ delay prefix, wait for `try_admit` (fresh tip + INPUT_CONFIRM) before
+ dropping `LOAD_READY`.
+
+5. **Peer disconnect during barrier**
+ - `psx_netplay_peer_disconnected(0)` while `psx_netplay_in_load_barrier()`
+ so rx silence for a multi-second restore does not soft-exit to lobby.
+ BYE / `peer_gone` still honored.
+
+6. **Spam loads**
+ - Host ignores new requests while `np_xfer_busy()`.
+ - No savestate-layer frame cooldown (see solo §6).
+
+### Where the code lives
+
+| Piece | Location |
+|-------|----------|
+| App xfer / barrier | `runtime/src/psx_netplay.c` |
+| Session stall / tip / hard_resync | `lib/recomp-net/src/session/rnet_session.c` |
+| Protocol notes | `lib/recomp-net/docs/protocol.md` |
+| Staging API | `savestate_request_load_protocol` (bypasses netplay user block) |
+
+---
+
+## Expected log lines (healthy netplay load)
+
+```
+netplay load slot=N — hash probe (…)
+netplay load slot=N — hashes match, applying… # or transferring / applying after transfer
+savestate: LOADED slot N …
+netplay load slot=N — applied, waiting for guest… # host
+netplay guest load slot=N — applied, waiting for host… # or ready acked
+netplay load slot=N — mutual ready, waiting lockstep…
+netplay load slot=N — peer ready, resuming lockstep
+```
+
+Stuck on `waiting for guest` / guest never leaving `applying…` → tip
+starvation (rules 1–3). Sticky `INPUT desync … stalled` → confirm/hash
+disagreement after resume (inspect tip epoch / history collisions).
+
+---
+
+## Related docs
+
+- [`LOAD_TIME_ZERO.md`](LOAD_TIME_ZERO.md) — in-game CD load wall-time (different problem).
+- [`CYCLE_TIMING_ARCH.md`](CYCLE_TIMING_ARCH.md) — cycle / GTE stall model.
+- `lib/recomp-net/docs/protocol.md` — STATE_PROBE / post-load ready rendezvous.
+- `runtime/include/boot_state.h` — `.pst` section version / zlib flags.
diff --git a/docs/SDL_BACKENDS.md b/docs/SDL_BACKENDS.md
new file mode 100644
index 00000000..6324a3a6
--- /dev/null
+++ b/docs/SDL_BACKENDS.md
@@ -0,0 +1,50 @@
+# SDL backends
+
+PSXRecomp uses SDL3 by default. A compatible system SDL3 package is preferred;
+otherwise CMake downloads the integrity-pinned SDL 3.4.10 release. To build the
+compatibility backend explicitly:
+
+```sh
+cmake -S . -B build-sdl2 -DPSX_SDL_BACKEND=SDL2
+```
+
+`PSX_SDL_BACKEND` accepts only `SDL3` or `SDL2`; it never silently changes the
+requested backend. `PSX_SDL3_FETCH=OFF` makes an SDL3 build system-package-only.
+
+## Initial Windows A/B exercise
+
+These measurements are smoke-level comparisons, not claims that one SDL
+generation makes emulation faster. Both builds used MinGW GCC 15.2, Release,
+static SDL, OpenBIOS, the OpenGL renderer, and a 165 Hz Windows host. Each row is
+three warmed runs of the runtime's frame-window diagnostic:
+
+| Game and workload | SDL3 wall time | SDL2 wall time | SDL3 difference |
+|---|---:|---:|---:|
+| Tomba 2, frames 60–660 | 9506.5 ± 10.9 ms | 9511.3 ± 19.0 ms | -0.05% |
+| Mega Man X6, frames 60–660 | 8174.0 ± 114.0 ms | 8199.2 ± 287.0 ms | -0.31% |
+
+Every Tomba 2 run executed exactly 928,243 dirty instructions and 309,407 dirty
+dispatches. Every Mega Man X6 run executed 3,962,702 dirty instructions and
+1,048,509 dirty dispatches. The paced wall-time differences are smaller than
+the observed variance, so the useful result is parity rather than a speed win.
+
+The SDL3 build was additionally exercised through:
+
+- the shared recomp-ui launcher event loop;
+- the SDL renderer presentation path (Tomba 2, 240 frames); and
+- the Vulkan renderer on an RTX 3080 Ti, including SDL3 instance-extension and
+ surface creation APIs (Tomba 2, 240 frames).
+
+The repeatable diagnostic setup is:
+
+```sh
+PSX_RUNTIME_PERF_DIAG=1 \
+PSX_BENCH_WINDOW=60:660 \
+PSX_DEV_INPUT=0 \
+./game-runtime --no-launcher --disc /path/to/game.cue --renderer opengl
+```
+
+Compare only runs with identical frame windows and dirty instruction/dispatch
+counts. Warm each executable and disc image before collecting results; cold
+filesystem and overlay-capture state caused much larger swings than the SDL
+backend in the initial exercise.
diff --git a/docs/STRING_TRANSLATION.md b/docs/STRING_TRANSLATION.md
index e0d665cb..3fca3e85 100644
--- a/docs/STRING_TRANSLATION.md
+++ b/docs/STRING_TRANSLATION.md
@@ -1,7 +1,28 @@
# On-the-Fly String Translation / Localization for psxrecomp
-**Status:** SPEC (design only — no implementation yet). Reusable framework
-feature; **Tsumu Light (SLPS-02253)** is the first consumer.
+**Status:** IMPLEMENTED and wired into the runtime. Reusable framework feature;
+**Tsumu Light (SLPS-02253)** is the first consumer.
+
+> This document was left marked "SPEC (design only — no implementation yet)"
+> long after the feature shipped. It now reads as the design rationale behind
+> working code, not a proposal. Where the prose below says "will" or "should",
+> read it as describing what was built.
+
+The implementation lives in `runtime/src/text_xlate.cpp` (~970 lines) behind
+`runtime/include/text_xlate.h`:
+
+| Entry point | Role |
+|---|---|
+| `text_xlate_init(project_root, language)` | Loads `translations/*.toml` under the project root; called from `runtime/src/main.cpp` at startup |
+| `text_xlate_set_language(language)` | Re-applies the resolved language (launcher/settings selection) |
+| `text_xlate_on_dispatch(cpu, target)` | Per-dispatch capture/substitution hook |
+| `text_xlate_vram_upload(x, y, w, h)` | VRAM-upload interception path |
+| `text_xlate_debug_json(subcmd, out, cap)` | TCP debug-server introspection |
+
+Configuration is `[localization].language` (falling back to `[runtime]`), with
+`[localization].languages` driving the launcher's language dropdown; see
+`recompiler/src/config_loader.h`. Setting the language to `jp`, `off`, or empty
+disables substitution while leaving capture running.
**Goal.** Let a Japanese-only PSX title be played in English (or any target
language) by **capturing the game's source strings from memory and substituting
@@ -292,8 +313,10 @@ and is a Phase-0 deliverable.)
## 3. Framework design (PSX)
-Shared module, proposed location: `runtime/src/text_xlate.c` +
-`runtime/include/text_xlate.h` (peers of `fntrace.*` and `bios_hle.*`).
+Shared module, shipped at `runtime/src/text_xlate.cpp` +
+`runtime/include/text_xlate.h` (peers of `fntrace.*` and `bios_hle.*`). The
+design below was written as a proposal and is described in the future tense in
+places; it is built, so read "will" as "does".
### 3.1 The hook point — `psx_dispatch`
@@ -684,7 +707,11 @@ slots, and screenshot the level-select / HUD.
- `tools/pms_build_translations.py` — decode/build/todo authoring.
- `translations.json` — table format.
-**psxrecomp hook points** (`F:\Projects\psxrecomp\_wt-tsumu\psxrecomp\`):
+**psxrecomp hook points** (paths relative to this repository; they were captured
+from the since-removed `_wt-tsumu` worktree, but the files live on `master`):
+- `runtime/src/text_xlate.cpp` + `runtime/include/text_xlate.h` — the shipped
+ module: `text_xlate_init` / `_set_language` / `_on_dispatch` /
+ `_vram_upload` / `_debug_json`. Loads every `translations/*.toml`.
- `runtime/include/fntrace.h` + `runtime/src/fntrace.c:58` — always-on dispatch
ring (capture analogue).
- `runtime/include/bios_hle.h:54` (`g_psx_bios_hle_hook`) + `runtime/src/bios_hle.c:297`
diff --git a/docs/TESTING.md b/docs/TESTING.md
new file mode 100644
index 00000000..36d2dccc
--- /dev/null
+++ b/docs/TESTING.md
@@ -0,0 +1,100 @@
+# Testing
+
+## Running the tests
+
+```sh
+cmake -S recompiler -B recompiler/build -G Ninja -DCMAKE_BUILD_TYPE=Release
+cmake --build recompiler/build
+cd recompiler/build && ctest --output-on-failure
+```
+
+That is the whole thing. 29 tests, under 5 seconds, and it needs **no BIOS dump,
+no disc image, and no generated code** — a plain recompiler build is enough. This
+is the check to run before opening a PR.
+
+Until 2026-07-27 no document in this repository mentioned `ctest`, `pytest`, or
+how to run a test at all, so the suite was effectively invisible. If you add a
+test, add it to `ctest` in the same commit — an unregistered test cannot fail,
+and a test that cannot fail is not a test.
+
+> On a memory-constrained machine, parallel builds of this tree can crash
+> `cc1plus` while compiling the toml11-heavy `config_loader.cpp`. If
+> `cmake --build` dies with no diagnostic, retry with `-j 2` or `-j 1`; the
+> failure is resource exhaustion, not a code error.
+
+### Running one test
+
+```sh
+ctest -R overlay_guard_codegen --output-on-failure # by name (regex)
+ctest -N # list without running
+```
+
+Every test is also a plain executable or script, so you can run it directly:
+
+```sh
+./recompiler/build/bios_address_model_test
+python recompiler/tests/test_overlay_guard_codegen.py \
+ --recompiler "$(pwd)/recompiler/build/psxrecomp-game.exe"
+```
+
+> Pass the recompiler as an **absolute** path. A relative path satisfies the
+> test's own `os.path.isfile` check but then fails inside `subprocess.run` on
+> Windows with `WinError 2`, which looks like a broken test rather than a bad
+> argument.
+
+## What the suite covers
+
+| Group | Where | Needs |
+|---|---|---|
+| C/C++ unit tests | `recompiler/tests/`, `runtime/tests/` | recompiler build |
+| Codegen contract tests | `recompiler/tests/test_*.py` | `psxrecomp-game` |
+| Runtime source-invariant guards | `runtime/tests/test_*.py` | nothing — they read source |
+
+The source-invariant guards are the cheapest and most useful class here. They
+assert structural properties of the runtime (an ordering holds, a fast path is
+invalidated, a fallback exists) by reading the source, so they cost milliseconds
+and catch whole regression classes without running a game. Registered from
+`recompiler/CMakeLists.txt` rather than `runtime/CMakeLists.txt`, because the
+runtime tree cannot configure until a BIOS has been generated, and these need
+neither.
+
+## Known-failing tests (not registered)
+
+Three tests exist and are **deliberately left out of `ctest`** because they fail
+today. They are not registered because a suite with a known-red test is a suite
+people stop believing — the exact failure mode that took CI off pull requests in
+the first place (see `.github/workflows/cli-release.yml`). Fix or retire them,
+then wire them in.
+
+| Test | Status |
+|---|---|
+| `runtime/tests/test_interpreter_perf_guards.py` | Asserts `psx_devices_mmio_sync` invalidates the inline cycle limit. It does not: the function delegates to `psx_devices_service_to_now()` (which clears `g_psx_cycle_fast_limit`, `psx_cycles.c:161`) **or** to `psx_devices_recompute_deadline()` (`:153-157`), and that second branch never clears it. Needs a timing owner to decide whether the guard found a real hole or the invariant moved. The guard is also partly stale — it still names `s_next_service_cycle`, since renamed to `psx_next_service_cycle`. |
+| `runtime/tests/test_runtime_perf_diag_guards.py` | Asserts a substring that is no longer present in the runtime source. Either the diagnostic was removed or it was renamed; the guard has not been updated either way. |
+| `runtime/tests/test_overlay_pair_dedup_runtime.py` | Needs its companion harness (`overlay_pair_dedup_harness.c`) built. Unlike the other Python tests it is not source-only, so it needs a build target before it can be registered. |
+
+## Tests that are not in `ctest` and should not be
+
+`runtime/tests/` also holds fixtures and harnesses (`*_fixture.c`, `*_harness.c`)
+that are inputs to other tests, not tests themselves. Do not register them.
+
+Several C tests under `runtime/tests/` require a **built runtime**, which
+requires a generated BIOS (see [`BUILDING.md`](BUILDING.md)). Those are wired
+into `runtime/CMakeLists.txt` and run from `runtime/build`:
+
+```sh
+cd runtime/build && ctest --output-on-failure
+```
+
+## CI
+
+`.github/workflows/cli-release.yml` runs on `workflow_dispatch` and published
+releases only. Its header explains why per-PR triggers were removed on
+2026-07-25: the Windows job failed often enough that a red check stopped
+carrying information, and a check nobody trusts costs attention without buying
+confidence.
+
+That reasoning still holds. The gap it left was that no fast, trustworthy
+alternative existed. The `ctest` suite above is a candidate: it is hermetic
+(no BIOS, no disc, no network), takes under five seconds, and is currently
+green. Restoring a per-PR check on top of it is a smaller decision than
+restoring the old one.
diff --git a/docs/assets/games/ape-escape.png b/docs/assets/games/ape-escape.png
new file mode 100644
index 00000000..dc757e70
Binary files /dev/null and b/docs/assets/games/ape-escape.png differ
diff --git a/docs/assets/games/mega-man-x4.png b/docs/assets/games/mega-man-x4.png
new file mode 100644
index 00000000..3e245657
Binary files /dev/null and b/docs/assets/games/mega-man-x4.png differ
diff --git a/docs/assets/games/mega-man-x5.png b/docs/assets/games/mega-man-x5.png
new file mode 100644
index 00000000..99e8a8cd
Binary files /dev/null and b/docs/assets/games/mega-man-x5.png differ
diff --git a/docs/assets/games/mega-man-x6.png b/docs/assets/games/mega-man-x6.png
new file mode 100644
index 00000000..9953c34e
Binary files /dev/null and b/docs/assets/games/mega-man-x6.png differ
diff --git a/docs/assets/games/tomba-2.png b/docs/assets/games/tomba-2.png
new file mode 100644
index 00000000..0660aef7
Binary files /dev/null and b/docs/assets/games/tomba-2.png differ
diff --git a/docs/assets/games/tomba.png b/docs/assets/games/tomba.png
new file mode 100644
index 00000000..170c2d0d
Binary files /dev/null and b/docs/assets/games/tomba.png differ
diff --git a/docs/assets/ui/mod-launcher.png b/docs/assets/ui/mod-launcher.png
new file mode 100644
index 00000000..95badb57
Binary files /dev/null and b/docs/assets/ui/mod-launcher.png differ
diff --git a/docs/assets/widescreen/ape-escape-21x9.png b/docs/assets/widescreen/ape-escape-21x9.png
new file mode 100644
index 00000000..d9db5796
Binary files /dev/null and b/docs/assets/widescreen/ape-escape-21x9.png differ
diff --git a/docs/assets/widescreen/mmx6-16x9.png b/docs/assets/widescreen/mmx6-16x9.png
new file mode 100644
index 00000000..4547644b
Binary files /dev/null and b/docs/assets/widescreen/mmx6-16x9.png differ
diff --git a/docs/assets/widescreen/tomba-2-adaptive.png b/docs/assets/widescreen/tomba-2-adaptive.png
new file mode 100644
index 00000000..9f8e4d67
Binary files /dev/null and b/docs/assets/widescreen/tomba-2-adaptive.png differ
diff --git a/docs/beetle-linux.md b/docs/beetle-linux.md
index 3134315e..a1feb3b0 100644
--- a/docs/beetle-linux.md
+++ b/docs/beetle-linux.md
@@ -38,7 +38,7 @@ make platform=unix STATIC_LINKING=1 HAVE_LIGHTREC=0 -j"$(nproc)"
cp mednafen_psx_libretro.so libmednafen_psx.a
cd ../runtime
-cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DPSX_LAUNCHER=OFF -DPSX_DEBUG_TOOLS=ON
+cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DPSX_RECOMP_UI=OFF -DPSX_DEBUG_TOOLS=ON
ninja -C build psx-beetle
# Headless run (picks the next free port from 4380 if taken; watch stderr)
diff --git a/docs/config_schema.md b/docs/config_schema.md
index 2db957e8..0d41756e 100644
--- a/docs/config_schema.md
+++ b/docs/config_schema.md
@@ -26,10 +26,18 @@ psx-runtime # boots BIOS discless
psx-runtime games/tomba/game.toml # boots BIOS, then loads game
```
-When both are loaded, keys merge:
+How the two configs relate:
-- **Scalar keys (`debug_port`, `window_title`, `memcard_dir`, ...)**: game
- wins if set, inherit from bios.toml otherwise. Shallow override.
+- **Scalar keys (`debug_port`, `window_title`, `memcard_dir`, ...)**: these come
+ from `game.toml` only. There is **no BIOS→game inheritance.** An earlier
+ version of this document described a shallow override where the game won and
+ otherwise inherited from `bios.toml`; that merge was never implemented.
+ `load_bios_config` (`recompiler/src/config_loader.cpp`) is called only from the
+ recompiler front-ends — `main_bios.cpp`, and `main_psx.cpp` purely to build the
+ `BiosAddressModel` — and never from `runtime/src/main.cpp`. Setting a
+ `[runtime]` scalar in a BIOS toml has no effect on a game run.
+ Runtime precedence is: environment > CLI > `settings.toml` > `game.toml` >
+ compiled-in default.
- **`[program]` (BIOS) and `[game]` blocks**: NOT merged — they describe
different programs. Both are visible to the loader.
- **Generated dispatch tables and C output**: ADDITIVE. BIOS contributes
@@ -83,6 +91,10 @@ respective files.
seeds = "recompiler/seeds/phase2_ghidra_seeds.json" # BIOS
seeds = "seeds/ghidra_funcs.txt" # game (note: game seeds aren't json today)
bios_thunks = "seeds/tomba_bios_thunks.txt" # game-only
+bios_config = "psxrecomp/bios/SCPH1001.toml" # game-only: BIOS profile whose address
+ # model game codegen folds RAM aliases
+ # through (see "BIOS profiles" below);
+ # defaults to the SCPH1001 profile
out_dir = "generated" # both
strict = true # both — currently always true
discovery = "whole-image" # game-only: "whole-image" or "reachable"
@@ -140,6 +152,121 @@ Patches are build-time inputs, not runtime memory writes or live toggles.
Regenerate the affected main executable or captured overlays after changing
them.
+### Guarded widescreen participation comparisons
+
+Games may disable a proven object/model cull verdict in widened world views
+without changing true 4:3 behavior:
+
+```toml
+[[widescreen.cull.keep]]
+address = "0x8002B310"
+expected = "0x28A21C01"
+result = 1
+```
+
+- `expected` must encode `SLT`, `SLTU`, `SLTI`, or `SLTIU`.
+- `result` must be 0 or 1.
+- The site identity is the normalized physical address plus the complete
+ 32-bit instruction. A nonmatching overlay variant at the same VA is left
+ unchanged.
+- At true 4:3 the original comparison is evaluated. The configured result is
+ forced only when `psx_ws_x_margin() > 0`.
+- Native generated code and the dirty-RAM interpreter implement the same
+ semantics.
+- Regenerate main/overlay native code after changing the list.
+
+Prefer an aspect-derived cone over `keep` when the original predicate is a
+camera-frustum test. `keep` has no queue policy and is appropriate only for a
+separately proven binary verdict.
+
+### Aspect-aware terrain and model participation
+
+Exact terrain-frustum angle loads can follow the live horizontal field:
+
+```toml
+[[widescreen.cull.angle]]
+address = "0x8013F138"
+expected = "0x24020155"
+```
+
+`expected` must be `ADDI`/`ADDIU rt,zero,imm`, with a positive 12-bit angular
+half-extent below one quarter-turn. The helper widens `tan(angle)` by the live
+per-side horizontal extent. It is exact at 4:3 and full-word guarded against
+same-address overlay variants.
+
+A model-list or per-child cosine rejection can use a horizontal-only envelope:
+
+```toml
+[widescreen.cull.aspect_cone]
+forward_addr = "0x1F8000E8" # signed Q12 X/Z/Y halfwords
+object_type_offset = 12
+object_reg = 19
+x_reg = 16
+z_reg = 17
+y_reg = 18
+hysteresis_pixels = 24
+queue_reserve = 4
+queue_count_addrs = ["0x1F800144", "0x1F800150", "0x1F80015C"]
+queue_capacities = [24, 40, 28]
+queue_type_masks = ["0x00000204", "0x00000010", "0x00000020"]
+
+[[widescreen.cull.aspect_cone.sites]]
+address = "0x80077368"
+expected = "0x28620358" # signed SLTI reject predicate
+
+[[widescreen.cull.aspect_cone.sites]]
+address = "0x8002B368"
+expected = "0x0082202A" # signed SLT reject predicate
+cosine_threshold = 856 # required for SLT; Q10
+object_reg = 20 # optional per-site register overrides
+x_reg = 19
+z_reg = 18
+y_reg = 17
+queue_guard = false # this lower-level predicate appends to no fixed queue
+```
+
+- Sites must be signed `SLTI` or `SLT` reject predicates: zero is the keep
+ path and one is rejection.
+- An `SLTI` site derives its Q10 cosine threshold from the immediate unless
+ `cosine_threshold` is given. An `SLT` site requires it explicitly.
+- A vanilla keep is always preserved. Only a vanilla rejection is retested.
+- Horizontal reach follows the current client aspect. Vertical reach,
+ near/far checks, type dispatch, and the game’s queue-capacity branches are
+ unchanged.
+- `guard_pixels` is the activation guard outside the visible field.
+ `hysteresis_pixels` moves deactivation farther out.
+- For `queue_guard = true`, non-visible guard/hysteresis candidates are
+ rejected at `capacity - queue_reserve`, preserving headroom for candidates
+ intersecting the visible wide field.
+- Use `queue_guard = false` only after proving that the exact predicate does
+ not append to those queues.
+- Site address, instruction, threshold, registers, queue policy, guard size,
+ and enclosing cone/queue metadata all contribute to overlay cache identity.
+- Generated/native overlay code and the dirty-RAM interpreter use the same
+ live helper. Dynamic resizing therefore needs no recompilation.
+
+The debug server’s `ws_aspect_cone_site` command accepts an `address` string
+and reports exact-site identity/keep/reject counters.
+
+Explicit `bias_sites` / `range_sites` may opt into an additional resident
+object lead without widening terrain or render queues:
+
+```toml
+[widescreen.cull]
+guard_pixels = 16
+activation_guard_pixels = 256
+bias_sites = ["0x80069BA8"]
+range_sites = ["0x80069BB0"]
+```
+
+`activation_guard_pixels` is added only to the live margin emitted at those
+two explicit site families, and only while widescreen reveals extra world.
+At true 4:3 it is exactly zero. `guard_pixels` remains the shared
+render/terrain participation guard; keep it small when terrain producers or
+model queues have fixed capacity. Both values are restricted to `[0, 256]`
+and contribute to native-overlay cache identity. Changing the activation
+guard requires regenerating the game and overlay code.
+
## Runtime block
Consumed by the cmake macro `psxrecomp_v4_add_runtime_target` (eventually)
@@ -186,6 +313,22 @@ turbo_audio_sink = true
the guest SPU timeline advancing but discards accelerated samples before host
playback, then fades normal output back in.
+## Audio Block
+
+Game projects may choose the host playback cushion after validating their
+audio production cadence:
+
+```toml
+[audio]
+buffer_ms = 60
+```
+
+`buffer_ms` accepts 30–500 milliseconds and defaults to 180. Lower values
+reduce audible input-to-sound delay, but leave less reserve for frames where a
+game temporarily produces no audio and can therefore crackle on affected
+titles. This is deliberately a per-game developer choice; it is not read from
+the player's `settings.toml`.
+
## Video Block
Runtime video defaults live in `[video]`:
@@ -194,6 +337,8 @@ Runtime video defaults live in `[video]`:
[video]
renderer = "opengl" # "software", "opengl", or "vulkan"
offer_vulkan = false # show Vulkan in the launcher only after game validation
+auto_skip_fmv = false # legacy Settings/runtime default
+offer_skip_fmv = true # false when the game exposes this through Mods
```
`renderer = "vulkan"` remains an experimental runtime choice and still requires
@@ -201,6 +346,11 @@ a build compiled with Vulkan support. `offer_vulkan` controls launcher
visibility only; it defaults to false so game projects must explicitly expose
Vulkan after validating their visuals and stability.
+`offer_skip_fmv` defaults to true for compatibility with the shared PSX
+Settings surface. A game migrating Skip FMVs into its built-in mod catalog sets
+it to false. The runtime then hides the Settings row, ignores stale persisted
+values, and leaves activation to the selected trusted plugin.
+
Reserved future fields:
- `default_disc_path` — game runtimes can pre-mount a disc
- `default_game_root` — for sibling-junction setups
@@ -240,3 +390,45 @@ These are noted here so future work knows where to slot them:
Phase A might allow multiple).
- `[program] type` explicit discriminator (currently inferred from
`rom` vs `exe` field presence).
+
+## BIOS profiles (`bios/.toml`)
+
+One profile per BIOS image; the profile is the single source of truth for the
+image identity, the relocation windows, and the runtime anchors. Two ship:
+`bios/SCPH1001.toml` (retail; user supplies the dump) and `bios/OpenBIOS.toml`
+(MIT, redistributable, shipped with the build). Normal runtimes link both
+generated backends (`PSXRECOMP_BIOS_STEMS=OpenBIOS;SCPH1001`) and select one at
+launch. The recompiler's `[recompiler] bios_config` identifies the profile used
+for game code generation; it does not choose the player's runtime BIOS.
+
+```toml
+[program.image] # identity; recompiler refuses a mismatched ROM
+sha256 = "..." # pins the exact image (empty = unchecked)
+redistributable = false # true: BIOS ships with the game; runtime hides
+ # any requirement to provide that image
+
+[recompiler.address_model] # boot-time bulk code copies out of ROM
+normalize_mask = "0x1FFFFFFF"
+[[recompiler.address_model.copy]]
+name = "Kernel Part 2" # comment label in the emitted C
+rom_lo = "0x1FC10000" # [lo, hi) physical, hi EXCLUSIVE
+rom_hi = "0x1FC18000"
+ram_lo = "0x00000500" # physical RAM destination
+runtime_base = "0x00000500" # vaddr the CPU executes the copy at
+dispatch_key = "ram" # "ram": functions keyed by RAM address;
+ # "rom": RAM alias folds back to ROM
+kernel_bless = true # runtime may byte-verify + run native
+
+[[recompiler.install_slots]] # kernel-RAM PCs the BIOS patches at runtime
+ram_addr = "0x00000CF0"
+
+[recompiler.runtime_exports] # per-image HLE anchors (omit = unavailable)
+shell_entry_phys = "0x00030000"
+deliver_event_ret = "0x80001720"
+```
+
+Every `copy` entry is a claim that the boot copy is byte-verbatim; the
+runtime kernel-bless memcmp enforces it. A BIOS with no copies (runs
+entirely from ROM) is valid: normalization degenerates to the KSEG mask.
+Semantic invariants (disjoint windows, no fold-output/input intersection,
+single bless window) are enforced at load; violations refuse to build.
diff --git a/docs/internal/ACCURACY_BURNDOWN.md b/docs/internal/ACCURACY_BURNDOWN.md
index fb8ae9ad..6e8aaa07 100644
--- a/docs/internal/ACCURACY_BURNDOWN.md
+++ b/docs/internal/ACCURACY_BURNDOWN.md
@@ -159,9 +159,11 @@ bug**, not timing.
now arms the cycle-paced ack scheduler, BAUD+ACK=1258 cyc, like the card
path). This was the cause of the load=4 Tomba 2 boot wedge (accurate CPU
exposed access-paced pad timing). See WEDGE_load4_shell_rootcause.md.
+ - DONE 2026-07-26: Tomba now offers explicit Analog / D-Pad modes; the
+ Tomba-only legacy pad-config fork and its debug/config surface were removed.
+ Every title now uses the modern DualShock config state machine.
- TODO (axis5 Fix-6 / "1.0e-e2"): fully remove the pad fast-path so pad+card
- share the unified shifter path; D4/D8 config-mode `0x43`/`0x45` semantics
- (hybrid-pad fix, retire g_pad_legacy_cfg) still open.
+ share the unified shifter path.
- [ ] **GPU**: GP0/GP1 command set, rasterization rules (top-left fill, dithering,
semi-transparency modes, mask bit, texture windows, blending), VRAM-as-texture —
cross-ref DuckStation gpu_*, Beetle gpu.cpp, GPU test ROMs; validate by VRAM diff.
@@ -224,8 +226,9 @@ overlap the cycle axis or each other (different files), so they parallelize.
must transmit the LIVE button frame (like `0x42`) + use the trailing byte to toggle
config; we send all-zeros → active-low → "all buttons pressed" phantom input every
`0x43` frame. P0: `0x45` status must report the LIVE analog/digital mode, not always
- analog-ON (→ frame-length misparse after a flip). Fixing both lets the
- `g_pad_legacy_cfg` Tomba hack be DELETED. P1: analog-mode lock (`0x44`); P2: `0x4D`
+ analog-ON (→ frame-length misparse after a flip). Both were fixed, and the
+ Tomba legacy fork was deleted 2026-07-26 when Tomba moved to explicit
+ Analog / D-Pad modes. P1: analog-mode lock (`0x44`); P2: `0x4D`
rumble echo + cycle-paced pad ACK. Validate: `sio_trace` diff on `0x43`/`0x45`.
- **`accuracy/axis5_gpu.md`.** P1: dithering ENTIRELY MISSING in both renderers (decoded
but never forwarded) — largest systematic banding divergence. P2: SW rasterizer uses
diff --git a/docs/internal/FAITHFUL_TIMING_PLAN.md b/docs/internal/FAITHFUL_TIMING_PLAN.md
index 6c06956b..737fca4a 100644
--- a/docs/internal/FAITHFUL_TIMING_PLAN.md
+++ b/docs/internal/FAITHFUL_TIMING_PLAN.md
@@ -213,6 +213,54 @@ on a fixed region -> next.
## 5. Status / Log (update every session)
+- **2026-07-28 (per-game host audio cushion — implemented, parser validated):**
+ Added `[audio] buffer_ms` as a runtime-only developer setting with a guarded
+ 30–500 ms range. The compatibility default remains 180 ms, preserving the
+ reserve required by titles with long streamed-stage production gaps; a game
+ may opt into a lower target after validation to reduce audible latency.
+ `audio_stats` and the opt-in runtime cadence report now expose both actual
+ fill and configured target. This changes only the host playback bridge and
+ does not alter guest SPU state, instruction timing, or code generation.
+ Recompiler build and all 33 registered tests pass.
+
+- **2026-07-27 (BIOS boot-skip parity across BIOS images — FIXED, validated):**
+ `bios_hle`'s boot-skip did nothing under the bundled OpenBIOS. Root cause was
+ ordering, not policy: `main.cpp` correctly forced the *kernel-call* tier off
+ when an image exports no `deliver_event_ret`, by assigning `bios_hle = false`,
+ and then derived `boot_skip` from that already-mutated flag. OpenBIOS omits
+ `deliver_event_ret` (B0 semantics unvalidated) but DOES export
+ `shell_entry_phys`, so the skip was structurally available and got cancelled
+ anyway — one player-facing flag meaning two different things depending on a
+ BIOS detail no player can see. Enhancement-phase (load-time) defect; no
+ timing-core change.
+ Fix: the two axes are now decided by one pure, dependency-free function,
+ `psx_bios_hle_plan()` (`runtime/src/bios_hle_plan.c`), which reads the
+ REQUESTED `bios_hle` for the boot decision and gates each axis on only the
+ anchor it actually needs; refusals are reported at startup instead of silently
+ downgrading. `psx_bios_hle_configure()` additionally clamps `boot_skip` to
+ `shell_entry_phys != 0` so the banner and `hle_dump` cannot overstate what can
+ fire. New `bios_hle_plan_test` (16/16 runtime ctest green) pins the matrix,
+ including an exhaustive 64-case sweep asserting the boot decision is
+ independent of `deliver_event_ret` and the call decision independent of
+ `shell_entry_phys`.
+ Validated on Ape Escape (SCUS-94423) built against this framework, both BIOS
+ backends linked, headless: OpenBIOS → `bios_boot=skip to game (shell
+ skipped)`, `hle_dump route=2` shows the one-shot fire at `vec 0x30000`,
+ `ra=0xBFC06EA8` (the exact continuation `bios/OpenBIOS.toml` documents),
+ game running on screen. Retail SCPH-1001 → same banner, same `vec 0x30000`
+ fire at `ra=0xBFC0702C`, game running; differs only in
+ `bios_backend=HLE (LLE fallback)` vs `LLE (recompiled BIOS)`. Negative control
+ `PSX_BIOS_HLE=0` on OpenBIOS → `bios_boot=real intro`, ring total 0 (hook not
+ installed), display 640x478 shell mode: the flag still turns the skip off.
+ Also in this pass: a BIOS profile's `[runtime]` block was parsed into
+ `BiosConfig::runtime` and read by NOTHING, and `bios/OpenBIOS.toml` carried an
+ inert `bios_hle = false` there that read as the reason HLE was off.
+ `load_bios_config` now REJECTS `[runtime]` in a BIOS profile. And
+ `psx_icache_fastpath_test` had been unbuildable ("redefinition of
+ `psx_advance_cycles`" — the test's counting stub vs the header's `static
+ inline`), which blocked every runtime test registered after it; it now builds
+ with `PSX_OVERLAY_DLL_BUILD=1`, the seam `psx_cycles.h` already provides.
+
- **2026-07-21 (VLC load-charge batching — shipped; dual still ~22 ms):**
Runtime-only batch: under `psx_next_service_cycle`, `psx_cyc_charge`
accumulates into `g_psx_cyc_batch` (no per-insn `psx_cycle_count` store);
@@ -304,9 +352,9 @@ on a fixed region -> next.
re-enumerates gamecontrollers, keeps selection by GUID. Offline + netplay.
- **2026-07-20 (launcher: lobby lock emoji via symbol fallback font):**
- Password lobbies showed □ for 🔒 — LatoLatin has no emoji. Load bundled
- `NotoSansSymbols2-Regular.ttf` (system Segoe UI Symbol on Windows) with
- RmlUi `fallback_face=true` so missing glyphs resolve.
+ Password lobbies showed □ for 🔒 because the primary face has no emoji.
+ Load a symbol fallback face through the shared Dear ImGui font atlas so
+ missing glyphs resolve.
- **2026-07-20 (launcher lobbies: button order + dblclick join):**
Lobbies actions: Return to Launcher → Change Player Name → Host Game →
@@ -559,7 +607,7 @@ on a fixed region -> next.
- **2026-07-19 (netplay lobby server + launcher menus):**
Lobby WS+JSON owned by proprietary `recomp-net-server` (was C
`servers/lobby/` under open recomp-net);
- `psx_lobby_client` + RmlUi home → Offline / Netplay → lobbies table
+ `psx_lobby_client` + shared launcher home → Offline / Netplay → lobbies table
(host/join/password). Launch hands `PsxNetplayConfig` to
`psx_netplay_start` (LAN endpoints from lobby). ICE relay stubbed.
diff --git a/docs/internal/README.md b/docs/internal/README.md
index 5ab8d0de..91857f24 100644
--- a/docs/internal/README.md
+++ b/docs/internal/README.md
@@ -40,3 +40,7 @@ behind a specific subsystem.
## Overlay cache internals
- [`SLJIT_PERSIST_CACHE.md`](SLJIT_PERSIST_CACHE.md) — persisted overlay-shard
cache design.
+
+## Savestate / netplay
+- [`../SAVESTATE_LOAD_PERFORMANCE.md`](../SAVESTATE_LOAD_PERFORMANCE.md) —
+ solo restore hitch fixes and netplay load barrier / INPUT rules.
diff --git a/docs/internal/upstream/pr13-fps-telemetry.md b/docs/internal/upstream/pr13-fps-telemetry.md
index 2ae437d1..e7c3f587 100644
--- a/docs/internal/upstream/pr13-fps-telemetry.md
+++ b/docs/internal/upstream/pr13-fps-telemetry.md
@@ -3,7 +3,9 @@
Parked from NyperYuhgard's PSXrecomp PR #13, commit
[`b49548e7b8f494c9584eb09039a8c325d7375b46`](https://github.com/mstan/psxrecomp/commit/b49548e7b8f494c9584eb09039a8c325d7375b46).
-This branch retains only the title-neutral simulated-vblank FPS and realtime-speed
-telemetry. It was adapted to current master and guards window-title access in
-headless/windowless operation. The overlay-cache inventory from the same upstream
-commit is parked separately.
+This branch retains only the simulated-vblank FPS and realtime-speed telemetry.
+It was adapted to current master, guards window-title access in
+headless/windowless operation, and is disabled by default. Set
+`PSX_FPS_TELEMETRY=1` before launch to opt in to the title-bar counter and
+matching stderr line. The overlay-cache inventory from the same upstream commit
+is parked separately.
diff --git a/recomp-ui b/recomp-ui
new file mode 160000
index 00000000..3db3d604
--- /dev/null
+++ b/recomp-ui
@@ -0,0 +1 @@
+Subproject commit 3db3d6042b2db8034775506bd8c1080fad2e5317
diff --git a/recompiler/include/bios_rom_alias.h b/recompiler/include/bios_rom_alias.h
new file mode 100644
index 00000000..249a093a
--- /dev/null
+++ b/recompiler/include/bios_rom_alias.h
@@ -0,0 +1,95 @@
+// bios_rom_alias.h — accept both BIOS ROM filename conventions.
+//
+// The multi-BIOS work introduced region-qualified ROM filenames
+// ("US-PSX-SCPH1001.BIN", "US-PSOne-SCPH101.BIN", "EUR-PSX-SCPH5552.bin")
+// alongside the historical bare model name ("SCPH1001.BIN"). Both conventions
+// are in the wild: every existing dump folder, bios.cfg, game.toml and
+// tools/regen_bios.sh default uses the bare name, while master's SCPH*.toml
+// files now name the qualified one. Whichever a config asks for, the other
+// must still be found — otherwise a config and a dump folder that disagree
+// fatal out ("cannot open BIOS file") even though the right ROM is sitting
+// right there.
+//
+// Matching is on the MODEL TOKEN: the last '-'-separated component of the
+// filename stem, compared case-insensitively.
+//
+// "US-PSX-SCPH1001.BIN" -> SCPH1001
+// "SCPH1001.BIN" -> SCPH1001
+// "EUR-PSX-SCPH5552.bin" -> SCPH5552
+//
+// That is convention-agnostic rather than a hardcoded alias table, so a ROM
+// named with a region prefix nobody has thought of yet still resolves.
+//
+// Header-only, and deliberately placed in recompiler/include because both
+// build trees already put that directory on the include path (see
+// runtime/runtime.cmake) — the recompiler resolves the [program] rom field
+// with it, the runtime resolves its BIOS search rungs with it, and neither can
+// drift from the other.
+
+#pragma once
+
+#include
+#include
+#include
+#include
+
+namespace PSXRecompV4 {
+
+// The model token of a BIOS filename: the last '-'-separated piece of the
+// stem, uppercased. "" for an empty stem.
+inline std::string bios_model_token(const std::filesystem::path& p) {
+ std::string stem = p.stem().string();
+ const size_t dash = stem.find_last_of('-');
+ if (dash != std::string::npos) stem = stem.substr(dash + 1);
+ for (char& c : stem) c = (char)std::toupper((unsigned char)c);
+ return stem;
+}
+
+// Does `candidate` name the same BIOS model as `wanted_token`, with a
+// BIOS-ish extension? Extension is compared case-insensitively (.BIN/.bin).
+inline bool bios_name_matches(const std::filesystem::path& candidate,
+ const std::string& wanted_token) {
+ if (wanted_token.empty()) return false;
+ std::string ext = candidate.extension().string();
+ for (char& c : ext) c = (char)std::tolower((unsigned char)c);
+ if (ext != ".bin" && ext != ".rom") return false;
+ return bios_model_token(candidate) == wanted_token;
+}
+
+// Resolve a configured BIOS ROM path to something that exists, tolerating
+// either filename convention. Returns `configured` unchanged when nothing
+// better is found, so the caller's own "cannot open BIOS file" diagnostic
+// still fires and still names what was asked for.
+inline std::filesystem::path resolve_bios_rom(const std::filesystem::path& configured) {
+ namespace fs = std::filesystem;
+ std::error_code ec;
+
+ if (configured.empty()) return configured;
+ if (fs::exists(configured, ec) && !fs::is_directory(configured, ec)) return configured;
+
+ const std::string want = bios_model_token(configured);
+
+ // Same directory, other naming convention (either direction).
+ auto scan = [&](const fs::path& dir) -> fs::path {
+ std::error_code dec;
+ if (dir.empty() || !fs::is_directory(dir, dec)) return {};
+ dec.clear();
+ for (fs::directory_iterator it(dir, dec), end; !dec && it != end; it.increment(dec)) {
+ if (!it->is_regular_file(dec)) continue;
+ if (bios_name_matches(it->path(), want)) return it->path();
+ }
+ return {};
+ };
+
+ if (fs::path hit = scan(configured.parent_path()); !hit.empty()) return hit;
+
+ // A config that names the ROM without its "bios/" directory (master's
+ // SCPH5552.toml does exactly this) still resolves against the conventional
+ // location.
+ const fs::path bios_dir = configured.parent_path() / "bios";
+ if (fs::path hit = scan(bios_dir); !hit.empty()) return hit;
+
+ return configured;
+}
+
+} // namespace PSXRecompV4
diff --git a/recompiler/seeds/openbios_dispatch_miss.json b/recompiler/seeds/openbios_dispatch_miss.json
new file mode 100644
index 00000000..b0a04092
--- /dev/null
+++ b/recompiler/seeds/openbios_dispatch_miss.json
@@ -0,0 +1,32 @@
+{
+ "schema": "psxrecomp phase2 seeds",
+ "source": "empirical dispatch-miss loop for the OpenBIOS profile - addresses observed as FAIL-FAST unknown dispatch at runtime, merged by gen_bios_seeds_from_elf.py --extra. Same loop as SCPH1001's dispatch_miss_seeds.json.",
+ "seed_count": 5,
+ "seeds": [
+ {
+ "address": "0xBFC0B410",
+ "label": "miss_BFC0B410",
+ "rationale": "empirical FAIL-FAST unknown dispatch during OpenBIOS bring-up boot loop"
+ },
+ {
+ "address": "0xBFC0D08C",
+ "label": "miss_BFC0D08C",
+ "rationale": "empirical FAIL-FAST unknown dispatch during OpenBIOS bring-up boot loop"
+ },
+ {
+ "address": "0xBFC0D070",
+ "label": "miss_BFC0D070",
+ "rationale": "empirical FAIL-FAST unknown dispatch during OpenBIOS bring-up boot loop"
+ },
+ {
+ "address": "0xBFC0B3E0",
+ "label": "miss_BFC0B3E0",
+ "rationale": "empirical FAIL-FAST unknown dispatch during OpenBIOS bring-up boot loop"
+ },
+ {
+ "address": "0xBFC0B39C",
+ "label": "miss_BFC0B39C",
+ "rationale": "empirical FAIL-FAST unknown dispatch during OpenBIOS bring-up boot loop"
+ }
+ ]
+}
diff --git a/recompiler/seeds/openbios_elf_seeds.json b/recompiler/seeds/openbios_elf_seeds.json
new file mode 100644
index 00000000..32c77f16
--- /dev/null
+++ b/recompiler/seeds/openbios_elf_seeds.json
@@ -0,0 +1,3237 @@
+{
+ "schema": "psxrecomp phase2 seeds",
+ "source": "gen_bios_seeds_from_elf.py over openbios.elf, shell.elf + 5 empirical seeds from openbios_dispatch_miss.json",
+ "seed_count": 646,
+ "seeds": [
+ {
+ "address": "0xBFC00000",
+ "label": "elf_entry_openbios_elf",
+ "rationale": "ELF entry point (e_entry) of openbios.elf"
+ },
+ {
+ "address": "0xBFC00084",
+ "label": "exit",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC00144",
+ "label": "main",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC0019C",
+ "label": "init_main_thread",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC001AC",
+ "label": "_boot",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC0039C",
+ "label": "buComputeSectorChecksum",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC003C4",
+ "label": "buVerifySectorChecksum",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC003F8",
+ "label": "buValidateEntryAndCorrect",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC00514",
+ "label": "filterInner",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC00520",
+ "label": "filterFirst",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC0052C",
+ "label": "buReallocateBrokenSectorAndRetry",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC006C8",
+ "label": "buFormat",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC00894",
+ "label": "buInit",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC00C48",
+ "label": "initBackupUnit",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC00CFC",
+ "label": "cardInfo",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC00D60",
+ "label": "buWriteTOCInternal.isra.0",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC00E98",
+ "label": "buFinishAndTrigger",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC00EE0",
+ "label": "buLowLevelOpCompleted",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC00F8C",
+ "label": "code_ptr_BFC00F8C",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC00FB0",
+ "label": "code_ptr_BFC00FB0",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC00FB8",
+ "label": "code_ptr_BFC00FB8",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC010C4",
+ "label": "code_ptr_BFC010C4",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC010D0",
+ "label": "code_ptr_BFC010D0",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC01124",
+ "label": "code_ptr_BFC01124",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC011E0",
+ "label": "code_ptr_BFC011E0",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC0153C",
+ "label": "code_ptr_BFC0153C",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC01570",
+ "label": "buLowLevelOpError1",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC015E0",
+ "label": "buLowLevelOpError2",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC01650",
+ "label": "buLowLevelOpError3",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC016C0",
+ "label": "buReadTOC",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC0175C",
+ "label": "buWriteTOC",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC017C0",
+ "label": "buSetAutoFormat",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC017D0",
+ "label": "dev_bu_close",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC01830",
+ "label": "buDevInit",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC019B4",
+ "label": "dev_bu_format",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC01A44",
+ "label": "dev_bu_undelete",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC01A84",
+ "label": "buNextFileInternal",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC01BC4",
+ "label": "dev_bu_open",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC01EEC",
+ "label": "dev_bu_erase",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC02144",
+ "label": "dev_bu_nextFile",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC02258",
+ "label": "dev_bu_firstFile",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC023C8",
+ "label": "dev_bu_rename",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC02554",
+ "label": "dev_bu_deinit",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC02594",
+ "label": "buRelativeToAbsoluteSector",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC025EC",
+ "label": "buGetReallocated",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC02640",
+ "label": "dev_bu_read",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC029A4",
+ "label": "dev_bu_write",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC02DD8",
+ "label": "addMemoryCardDevice",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC02DEC",
+ "label": "deinitCDRom",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC02E70",
+ "label": "initializeCDRomHandlersAndEvents",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC02FB8",
+ "label": "initCDRom",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC03000",
+ "label": "cdromBlockGetStatus",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC030BC",
+ "label": "addCDRomDevice",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC030D0",
+ "label": "strcat.isra.0",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC0310C",
+ "label": "findDirectoryEntryForFilename",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC03404",
+ "label": "cdromReadPathTable",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC035B4",
+ "label": "dev_cd_open",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC036E0",
+ "label": "dev_cd_read",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC037F0",
+ "label": "dev_cd_nextFile",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC038FC",
+ "label": "dev_cd_firstFile",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC03A30",
+ "label": "dev_cd_chdir",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC03A78",
+ "label": "cdromBlockReading",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC03CEC",
+ "label": "getLastCDRomError",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC03D0C",
+ "label": "setCDRomIRQAutoAck",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC03D28",
+ "label": "enqueueCDRomHandlers",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC03DA0",
+ "label": "dequeueCDRomHandlers",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC03DE0",
+ "label": "Krom2Offset",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC03E70",
+ "label": "Krom2RawAdd",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC03F00",
+ "label": "firstFile",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC03FB0",
+ "label": "nextFile",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC03FD4",
+ "label": "format",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC04080",
+ "label": "setupFileIO",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC0414C",
+ "label": "getDeviceStatus",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC0415C",
+ "label": "setDeviceStatus",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC04168",
+ "label": "printInstalledDevices",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC041D0",
+ "label": "findDevice",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC04270",
+ "label": "removeDevice",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC04304",
+ "label": "getFileFromHandle",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC04330",
+ "label": "ioabortraw",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC04348",
+ "label": "ioAbortWithMsg",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC04374",
+ "label": "findEmptyFile",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC043D0",
+ "label": "splitFilepathAndFindDevice",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC044D0",
+ "label": "cdevscan",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC04538",
+ "label": "psxexit",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC0459C",
+ "label": "isFileConsole",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC045E0",
+ "label": "addDevice",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC04668",
+ "label": "xprintfcallback",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC046C0",
+ "label": "psxopen",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC04790",
+ "label": "psxlseek",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC04840",
+ "label": "psxread",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC04974",
+ "label": "psxwrite",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC04AA8",
+ "label": "psxclose",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC04B2C",
+ "label": "reopenStdio",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC04B84",
+ "label": "installStdIo",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC04C0C",
+ "label": "psxioctl",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC04CBC",
+ "label": "psxgetc",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC04CF0",
+ "label": "psxputc",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC04D1C",
+ "label": "psxputchar",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC04DA0",
+ "label": "psxgetchar",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC04DCC",
+ "label": "psxgets",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC04F1C",
+ "label": "psxputs",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC04F64",
+ "label": "psxprintf",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC04FA4",
+ "label": "psxrename",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC050DC",
+ "label": "psxerase",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC05190",
+ "label": "GPU_send",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC051A0",
+ "label": "GPU_abort",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC051CC",
+ "label": "GPU_timeout",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC051F8",
+ "label": "GPU_dw",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC052E4",
+ "label": "GPU_getStatus",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC052F4",
+ "label": "GPU_sync",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC053C4",
+ "label": "GPU_mem2vram",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC05474",
+ "label": "GPU_cw",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC054A0",
+ "label": "GPU_cwb",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC054F4",
+ "label": "GPU_sendPackets",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC05578",
+ "label": "initEvents",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC05600",
+ "label": "getFreeEvCBSlot",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC05648",
+ "label": "openEvent",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC056B4",
+ "label": "enableEvent",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC056EC",
+ "label": "disableEvent",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC05724",
+ "label": "closeEvent",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC0574C",
+ "label": "testEvent",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC0578C",
+ "label": "waitEvent",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC057F0",
+ "label": "flushCache",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC05858",
+ "label": "toupper",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC05878",
+ "label": "tolower",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC05894",
+ "label": "memchr",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC058C8",
+ "label": "strcat",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC0590C",
+ "label": "strcpy",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC05930",
+ "label": "strncpy",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC0596C",
+ "label": "strchr",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC0599C",
+ "label": "strrchr",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC059CC",
+ "label": "strlen",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC059F4",
+ "label": "strncat",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC05A44",
+ "label": "strcmp",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC05A68",
+ "label": "strncmp",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC05AA4",
+ "label": "strstr",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC05AFC",
+ "label": "installHandler",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC05B24",
+ "label": "installKernelHandlers",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC05B64",
+ "label": "installExceptionHandler",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC05B74",
+ "label": "setDefaultExceptionJmpBuf",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC05B88",
+ "label": "setExceptionJmpBuf",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC05B94",
+ "label": "clearFileError",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC05B9C",
+ "label": "getOpenBiosApiVersionImpl",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC05BA4",
+ "label": "getOpenBiosBuildIdImpl",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC05BB0",
+ "label": "getB0table",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC05BD8",
+ "label": "getC0table",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC05C00",
+ "label": "atoi",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC05C0C",
+ "label": "atol",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC05C14",
+ "label": "patchA0table",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC05C8C",
+ "label": "unimplemented",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC05E10",
+ "label": "copyDataAndInitializeBSS",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC05E54",
+ "label": "copyA0table",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC05E6C",
+ "label": "psxdummy",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC05E74",
+ "label": "psxtodigit",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC05EC4",
+ "label": "psxabs",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC05ED4",
+ "label": "psxatob",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC05F08",
+ "label": "psxstrpbrk",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC05F48",
+ "label": "psxstrspn",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC05FA8",
+ "label": "psxstrcspn",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC06008",
+ "label": "psxstrtok",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC0604C",
+ "label": "psxbcopy",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC06084",
+ "label": "psxbzero",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC060C4",
+ "label": "psxbcmp",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC060E4",
+ "label": "psxrand",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC06110",
+ "label": "psxsrand",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC0611C",
+ "label": "psxlsearch",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC06180",
+ "label": "psxbsearch",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC06220",
+ "label": "getSystemInfo",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC06248",
+ "label": "code_ptr_BFC06248",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC06254",
+ "label": "code_ptr_BFC06254",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC06260",
+ "label": "code_ptr_BFC06260",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC06270",
+ "label": "code_ptr_BFC06270",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC06278",
+ "label": "code_ptr_BFC06278",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC06288",
+ "label": "code_ptr_BFC06288",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC06290",
+ "label": "code_ptr_BFC06290",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC06298",
+ "label": "code_ptr_BFC06298",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC062A0",
+ "label": "code_ptr_BFC062A0",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC062A8",
+ "label": "setMemSize",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC06324",
+ "label": "readHeader",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC0637C",
+ "label": "loadExeHeader",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC063E4",
+ "label": "loadExe",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC0648C",
+ "label": "loadAndExec",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC06634",
+ "label": "exec",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC066B0",
+ "label": "psxsetjmp",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC066EC",
+ "label": "psxlongjmp",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC06728",
+ "label": "initThreads",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC06834",
+ "label": "getFreeTCBslot",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC0688C",
+ "label": "openThread",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC068F8",
+ "label": "closeThread",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC06924",
+ "label": "changeThread",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC06950",
+ "label": "exceptionVector",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC06960",
+ "label": "A0Vector",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC06970",
+ "label": "B0Vector",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC06980",
+ "label": "C0Vector",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC06990",
+ "label": "romsyscall_printf",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC0699C",
+ "label": "isspace",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC069B8",
+ "label": "strncmp",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC069F4",
+ "label": "muteSpu",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC06A10",
+ "label": "fatal",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC06A38",
+ "label": "initHandlersArray.constprop.0",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC06A8C",
+ "label": "strcpy.isra.0",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC06AA8",
+ "label": "strcat.isra.0",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC06AE4",
+ "label": "findWordItem.constprop.0",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC06C54",
+ "label": "gameMainThunk",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC06C7C",
+ "label": "boot",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC07390",
+ "label": "bootThunk",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC073E0",
+ "label": "setConfiguration",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC073F8",
+ "label": "getConfiguration",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC07424",
+ "label": "drawSplashScreen",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC0742C",
+ "label": "runExp1PreHook",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC07484",
+ "label": "runExp1PostHook",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC074DC",
+ "label": "startShell",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC07578",
+ "label": "mcResetStatus",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC07604",
+ "label": "mcWaitForStatus",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC0767C",
+ "label": "mcWaitForStatusAndReturnIndex",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC07704",
+ "label": "patch_remove_ChgclrPAD",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC07714",
+ "label": "patch_disable_slotChangeOnAbort",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC07724",
+ "label": "patch_send_pad",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC07734",
+ "label": "patch_startPad",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC07744",
+ "label": "patch_stopPad",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC07750",
+ "label": "mcAllowNewCard",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC07760",
+ "label": "mcReadSector",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC07808",
+ "label": "mcWriteSector",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC078B0",
+ "label": "cardInfoInternal",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC07944",
+ "label": "mcGetLastDevice",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC07968",
+ "label": "dev_tty_init",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC079C8",
+ "label": "dev_tty_open",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC07A24",
+ "label": "ttyGetChar",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC07A74",
+ "label": "dev_tty_action",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC07CA4",
+ "label": "dev_tty_ioctl",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC07D94",
+ "label": "addConsoleDevice",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC07DA8",
+ "label": "addDummyConsoleDevice",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC07DBC",
+ "label": "patch_hash",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC07E84",
+ "label": "patch_hook",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC08060",
+ "label": "clear_card_1_execute",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC08068",
+ "label": "custom_handler_1_execute",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC08070",
+ "label": "initgun_1_execute",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC08078",
+ "label": "patch_card_info_1_execute",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC0809C",
+ "label": "patch_card_1_execute",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC080A4",
+ "label": "patch_card_2_execute",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC080AC",
+ "label": "patch_card2_1_execute",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC080D0",
+ "label": "patch_card2_2_execute",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC080F4",
+ "label": "patch_gte_1_execute",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC080FC",
+ "label": "patch_gte_2_execute",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC08104",
+ "label": "patch_gte_3_execute",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC0810C",
+ "label": "patch_pad_1_execute",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC0817C",
+ "label": "patch_pad_2_execute",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC081EC",
+ "label": "patch_pad_3_execute",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC08220",
+ "label": "remove_ChgclrPAD_1_execute",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC08254",
+ "label": "remove_ChgclrPAD_2_execute",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC08288",
+ "label": "send_pad_1_execute",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC082DC",
+ "label": "send_pad_2_execute",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC08330",
+ "label": "BoardConsoleInit",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC08338",
+ "label": "BoardConsolePuts",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC08360",
+ "label": "BoardConsolePutc",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC08370",
+ "label": "BoardEarlyInit",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC08378",
+ "label": "BoardInit",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC08380",
+ "label": "BoardLateInit",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC08388",
+ "label": "BoardShutdown",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC08390",
+ "label": "BoardExceptionHandler",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC08398",
+ "label": "cpu_early_init",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC083A0",
+ "label": "cpu_init",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC083A8",
+ "label": "cpu_late_init",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC083B0",
+ "label": "__sinit",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC083B8",
+ "label": "libc_init",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC08478",
+ "label": "qsort",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC085D8",
+ "label": "memcpy",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC08604",
+ "label": "memmove",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC08674",
+ "label": "memcmp",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC086C4",
+ "label": "memset",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC086E8",
+ "label": "strtol",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC088F0",
+ "label": "vxprintf",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC08C58",
+ "label": "code_ptr_BFC08C58",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC08C9C",
+ "label": "code_ptr_BFC08C9C",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC08F38",
+ "label": "code_ptr_BFC08F38",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC08F4C",
+ "label": "code_ptr_BFC08F4C",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC08F64",
+ "label": "code_ptr_BFC08F64",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC08F80",
+ "label": "code_ptr_BFC08F80",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC08FD4",
+ "label": "code_ptr_BFC08FD4",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC09028",
+ "label": "code_ptr_BFC09028",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC09108",
+ "label": "_exit",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC0912C",
+ "label": "_ucsdk_start",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC09198",
+ "label": "dbgput",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC091E0",
+ "label": "dbgput.constprop.0",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC091EC",
+ "label": "osDbgPrintf",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC0A500",
+ "label": "elf_entry_shell_elf",
+ "rationale": "ELF entry point (e_entry) of shell.elf"
+ },
+ {
+ "address": "0xBFC0A564",
+ "label": "main",
+ "rationale": "ELF STT_FUNC symbol from shell.elf"
+ },
+ {
+ "address": "0xBFC0B160",
+ "label": "memset",
+ "rationale": "ELF STT_FUNC symbol from shell.elf"
+ },
+ {
+ "address": "0xBFC0B184",
+ "label": "startDmaOneSector",
+ "rationale": "ELF STT_FUNC symbol from shell.elf"
+ },
+ {
+ "address": "0xBFC0B218",
+ "label": "initCD",
+ "rationale": "ELF STT_FUNC symbol from shell.elf"
+ },
+ {
+ "address": "0xBFC0B250",
+ "label": "isCDError",
+ "rationale": "ELF STT_FUNC symbol from shell.elf"
+ },
+ {
+ "address": "0xBFC0B280",
+ "label": "isCDSuccess",
+ "rationale": "ELF STT_FUNC symbol from shell.elf"
+ },
+ {
+ "address": "0xBFC0B298",
+ "label": "isCDAudio",
+ "rationale": "ELF STT_FUNC symbol from shell.elf"
+ },
+ {
+ "address": "0xBFC0B2B0",
+ "label": "checkCD",
+ "rationale": "ELF STT_FUNC symbol from shell.elf"
+ },
+ {
+ "address": "0xBFC0B378",
+ "label": "code_ptr_BFC0B378",
+ "rationale": "code pointer in a data section of shell.elf"
+ },
+ {
+ "address": "0xBFC0B39C",
+ "label": "miss_BFC0B39C",
+ "rationale": "empirical dispatch-miss seed merged from openbios_dispatch_miss.json"
+ },
+ {
+ "address": "0xBFC0B3E0",
+ "label": "miss_BFC0B3E0",
+ "rationale": "empirical dispatch-miss seed merged from openbios_dispatch_miss.json"
+ },
+ {
+ "address": "0xBFC0B410",
+ "label": "miss_BFC0B410",
+ "rationale": "empirical dispatch-miss seed merged from openbios_dispatch_miss.json"
+ },
+ {
+ "address": "0xBFC0B484",
+ "label": "code_ptr_BFC0B484",
+ "rationale": "code pointer in a data section of shell.elf"
+ },
+ {
+ "address": "0xBFC0B4D0",
+ "label": "code_ptr_BFC0B4D0",
+ "rationale": "code pointer in a data section of shell.elf"
+ },
+ {
+ "address": "0xBFC0B514",
+ "label": "code_ptr_BFC0B514",
+ "rationale": "code pointer in a data section of shell.elf"
+ },
+ {
+ "address": "0xBFC0B538",
+ "label": "code_ptr_BFC0B538",
+ "rationale": "code pointer in a data section of shell.elf"
+ },
+ {
+ "address": "0xBFC0B558",
+ "label": "code_ptr_BFC0B558",
+ "rationale": "code pointer in a data section of shell.elf"
+ },
+ {
+ "address": "0xBFC0B594",
+ "label": "code_ptr_BFC0B594",
+ "rationale": "code pointer in a data section of shell.elf"
+ },
+ {
+ "address": "0xBFC0B760",
+ "label": "code_ptr_BFC0B760",
+ "rationale": "code pointer in a data section of shell.elf"
+ },
+ {
+ "address": "0xBFC0B7A0",
+ "label": "code_ptr_BFC0B7A0",
+ "rationale": "code pointer in a data section of shell.elf"
+ },
+ {
+ "address": "0xBFC0B7D4",
+ "label": "code_ptr_BFC0B7D4",
+ "rationale": "code pointer in a data section of shell.elf"
+ },
+ {
+ "address": "0xBFC0B7EC",
+ "label": "code_ptr_BFC0B7EC",
+ "rationale": "code pointer in a data section of shell.elf"
+ },
+ {
+ "address": "0xBFC0B810",
+ "label": "code_ptr_BFC0B810",
+ "rationale": "code pointer in a data section of shell.elf"
+ },
+ {
+ "address": "0xBFC0B844",
+ "label": "code_ptr_BFC0B844",
+ "rationale": "code pointer in a data section of shell.elf"
+ },
+ {
+ "address": "0xBFC0B868",
+ "label": "code_ptr_BFC0B868",
+ "rationale": "code pointer in a data section of shell.elf"
+ },
+ {
+ "address": "0xBFC0B880",
+ "label": "code_ptr_BFC0B880",
+ "rationale": "code pointer in a data section of shell.elf"
+ },
+ {
+ "address": "0xBFC0B888",
+ "label": "code_ptr_BFC0B888",
+ "rationale": "code pointer in a data section of shell.elf"
+ },
+ {
+ "address": "0xBFC0B8CC",
+ "label": "generateCosTable",
+ "rationale": "ELF STT_FUNC symbol from shell.elf"
+ },
+ {
+ "address": "0xBFC0B928",
+ "label": "waitGPU",
+ "rationale": "ELF STT_FUNC symbol from shell.elf"
+ },
+ {
+ "address": "0xBFC0B94C",
+ "label": "flip",
+ "rationale": "ELF STT_FUNC symbol from shell.elf"
+ },
+ {
+ "address": "0xBFC0BAA8",
+ "label": "waitVSync",
+ "rationale": "ELF STT_FUNC symbol from shell.elf"
+ },
+ {
+ "address": "0xBFC0BB94",
+ "label": "initGPU",
+ "rationale": "ELF STT_FUNC symbol from shell.elf"
+ },
+ {
+ "address": "0xBFC0BC24",
+ "label": "orientation",
+ "rationale": "ELF STT_FUNC symbol from shell.elf"
+ },
+ {
+ "address": "0xBFC0BC88",
+ "label": "compare",
+ "rationale": "ELF STT_FUNC symbol from shell.elf"
+ },
+ {
+ "address": "0xBFC0BD6C",
+ "label": "qsort",
+ "rationale": "ELF STT_FUNC symbol from shell.elf"
+ },
+ {
+ "address": "0xBFC0BEAC",
+ "label": "convexHull",
+ "rationale": "ELF STT_FUNC symbol from shell.elf"
+ },
+ {
+ "address": "0xBFC0C0DC",
+ "label": "sendGPUVertex",
+ "rationale": "ELF STT_FUNC symbol from shell.elf"
+ },
+ {
+ "address": "0xBFC0C110",
+ "label": "startLerp",
+ "rationale": "ELF STT_FUNC symbol from shell.elf"
+ },
+ {
+ "address": "0xBFC0C2AC",
+ "label": "idle",
+ "rationale": "ELF STT_FUNC symbol from shell.elf"
+ },
+ {
+ "address": "0xBFC0C2C8",
+ "label": "lerpC.isra.0",
+ "rationale": "ELF STT_FUNC symbol from shell.elf"
+ },
+ {
+ "address": "0xBFC0C354",
+ "label": "dCos",
+ "rationale": "ELF STT_FUNC symbol from shell.elf"
+ },
+ {
+ "address": "0xBFC0C3C0",
+ "label": "generateRotationMatrix3D",
+ "rationale": "ELF STT_FUNC symbol from shell.elf"
+ },
+ {
+ "address": "0xBFC0C4A8",
+ "label": "multiplyMatrix3D",
+ "rationale": "ELF STT_FUNC symbol from shell.elf"
+ },
+ {
+ "address": "0xBFC0C828",
+ "label": "scaleMatrix3D",
+ "rationale": "ELF STT_FUNC symbol from shell.elf"
+ },
+ {
+ "address": "0xBFC0C970",
+ "label": "matrixVertexMul3D",
+ "rationale": "ELF STT_FUNC symbol from shell.elf"
+ },
+ {
+ "address": "0xBFC0CAB0",
+ "label": "matrixVertexMul3Dz",
+ "rationale": "ELF STT_FUNC symbol from shell.elf"
+ },
+ {
+ "address": "0xBFC0CB20",
+ "label": "dDiv",
+ "rationale": "ELF STT_FUNC symbol from shell.elf"
+ },
+ {
+ "address": "0xBFC0CC40",
+ "label": "initSPU",
+ "rationale": "ELF STT_FUNC symbol from shell.elf"
+ },
+ {
+ "address": "0xBFC0CC98",
+ "label": "checkSPU",
+ "rationale": "ELF STT_FUNC symbol from shell.elf"
+ },
+ {
+ "address": "0xBFC0CD74",
+ "label": "uninitSPU",
+ "rationale": "ELF STT_FUNC symbol from shell.elf"
+ },
+ {
+ "address": "0xBFC0CD8C",
+ "label": "spuSchedulePing",
+ "rationale": "ELF STT_FUNC symbol from shell.elf"
+ },
+ {
+ "address": "0xBFC0CDEC",
+ "label": "spuScheduleIdle",
+ "rationale": "ELF STT_FUNC symbol from shell.elf"
+ },
+ {
+ "address": "0xBFC0CE20",
+ "label": "spuScheduleOutro",
+ "rationale": "ELF STT_FUNC symbol from shell.elf"
+ },
+ {
+ "address": "0xBFC0CE78",
+ "label": "spuScheduleError",
+ "rationale": "ELF STT_FUNC symbol from shell.elf"
+ },
+ {
+ "address": "0xBFC0CE9C",
+ "label": "SPUInit",
+ "rationale": "ELF STT_FUNC symbol from shell.elf"
+ },
+ {
+ "address": "0xBFC0CF14",
+ "label": "SPUResetVoice",
+ "rationale": "ELF STT_FUNC symbol from shell.elf"
+ },
+ {
+ "address": "0xBFC0CF50",
+ "label": "SPUSetVoiceVolume",
+ "rationale": "ELF STT_FUNC symbol from shell.elf"
+ },
+ {
+ "address": "0xBFC0CF90",
+ "label": "SPUWaitIdle",
+ "rationale": "ELF STT_FUNC symbol from shell.elf"
+ },
+ {
+ "address": "0xBFC0CFC0",
+ "label": "MOD_SetBPM",
+ "rationale": "ELF STT_FUNC symbol from shell.elf"
+ },
+ {
+ "address": "0xBFC0D024",
+ "label": "MOD_UpdateEffect",
+ "rationale": "ELF STT_FUNC symbol from shell.elf"
+ },
+ {
+ "address": "0xBFC0D070",
+ "label": "miss_BFC0D070",
+ "rationale": "empirical dispatch-miss seed merged from openbios_dispatch_miss.json"
+ },
+ {
+ "address": "0xBFC0D08C",
+ "label": "miss_BFC0D08C",
+ "rationale": "empirical dispatch-miss seed merged from openbios_dispatch_miss.json"
+ },
+ {
+ "address": "0xBFC0D0A4",
+ "label": "code_ptr_BFC0D0A4",
+ "rationale": "code pointer in a data section of shell.elf"
+ },
+ {
+ "address": "0xBFC0D11C",
+ "label": "code_ptr_BFC0D11C",
+ "rationale": "code pointer in a data section of shell.elf"
+ },
+ {
+ "address": "0xBFC0D174",
+ "label": "code_ptr_BFC0D174",
+ "rationale": "code pointer in a data section of shell.elf"
+ },
+ {
+ "address": "0xBFC0D198",
+ "label": "code_ptr_BFC0D198",
+ "rationale": "code pointer in a data section of shell.elf"
+ },
+ {
+ "address": "0xBFC0D1B8",
+ "label": "code_ptr_BFC0D1B8",
+ "rationale": "code pointer in a data section of shell.elf"
+ },
+ {
+ "address": "0xBFC0D214",
+ "label": "code_ptr_BFC0D214",
+ "rationale": "code pointer in a data section of shell.elf"
+ },
+ {
+ "address": "0xBFC0D29C",
+ "label": "code_ptr_BFC0D29C",
+ "rationale": "code pointer in a data section of shell.elf"
+ },
+ {
+ "address": "0xBFC0D2F8",
+ "label": "code_ptr_BFC0D2F8",
+ "rationale": "code pointer in a data section of shell.elf"
+ },
+ {
+ "address": "0xBFC0D3A8",
+ "label": "code_ptr_BFC0D3A8",
+ "rationale": "code pointer in a data section of shell.elf"
+ },
+ {
+ "address": "0xBFC0D4A0",
+ "label": "code_ptr_BFC0D4A0",
+ "rationale": "code pointer in a data section of shell.elf"
+ },
+ {
+ "address": "0xBFC0D4DC",
+ "label": "code_ptr_BFC0D4DC",
+ "rationale": "code pointer in a data section of shell.elf"
+ },
+ {
+ "address": "0xBFC0D588",
+ "label": "SPUUploadInstruments.constprop.0",
+ "rationale": "ELF STT_FUNC symbol from shell.elf"
+ },
+ {
+ "address": "0xBFC0D624",
+ "label": "MOD_Check",
+ "rationale": "ELF STT_FUNC symbol from shell.elf"
+ },
+ {
+ "address": "0xBFC0D698",
+ "label": "loadInternal",
+ "rationale": "ELF STT_FUNC symbol from shell.elf"
+ },
+ {
+ "address": "0xBFC0D884",
+ "label": "MOD_Load",
+ "rationale": "ELF STT_FUNC symbol from shell.elf"
+ },
+ {
+ "address": "0xBFC0D88C",
+ "label": "MOD_Silence",
+ "rationale": "ELF STT_FUNC symbol from shell.elf"
+ },
+ {
+ "address": "0xBFC0D8C4",
+ "label": "MOD_Poll",
+ "rationale": "ELF STT_FUNC symbol from shell.elf"
+ },
+ {
+ "address": "0xBFC0DD00",
+ "label": "code_ptr_BFC0DD00",
+ "rationale": "code pointer in a data section of shell.elf"
+ },
+ {
+ "address": "0xBFC0DD34",
+ "label": "code_ptr_BFC0DD34",
+ "rationale": "code pointer in a data section of shell.elf"
+ },
+ {
+ "address": "0xBFC0DD48",
+ "label": "code_ptr_BFC0DD48",
+ "rationale": "code pointer in a data section of shell.elf"
+ },
+ {
+ "address": "0xBFC0DD84",
+ "label": "code_ptr_BFC0DD84",
+ "rationale": "code pointer in a data section of shell.elf"
+ },
+ {
+ "address": "0xBFC0DDC0",
+ "label": "code_ptr_BFC0DDC0",
+ "rationale": "code pointer in a data section of shell.elf"
+ },
+ {
+ "address": "0xBFC0DDD0",
+ "label": "code_ptr_BFC0DDD0",
+ "rationale": "code pointer in a data section of shell.elf"
+ },
+ {
+ "address": "0xBFC0DDF0",
+ "label": "code_ptr_BFC0DDF0",
+ "rationale": "code pointer in a data section of shell.elf"
+ },
+ {
+ "address": "0xBFC0DE50",
+ "label": "code_ptr_BFC0DE50",
+ "rationale": "code pointer in a data section of shell.elf"
+ },
+ {
+ "address": "0xBFC0DE70",
+ "label": "code_ptr_BFC0DE70",
+ "rationale": "code pointer in a data section of shell.elf"
+ },
+ {
+ "address": "0xBFC0DEA0",
+ "label": "code_ptr_BFC0DEA0",
+ "rationale": "code pointer in a data section of shell.elf"
+ },
+ {
+ "address": "0xBFC0DEF4",
+ "label": "code_ptr_BFC0DEF4",
+ "rationale": "code pointer in a data section of shell.elf"
+ },
+ {
+ "address": "0xBFC0DF00",
+ "label": "code_ptr_BFC0DF00",
+ "rationale": "code pointer in a data section of shell.elf"
+ },
+ {
+ "address": "0xBFC0DF18",
+ "label": "code_ptr_BFC0DF18",
+ "rationale": "code pointer in a data section of shell.elf"
+ },
+ {
+ "address": "0xBFC0DF30",
+ "label": "code_ptr_BFC0DF30",
+ "rationale": "code pointer in a data section of shell.elf"
+ },
+ {
+ "address": "0xBFC0DF68",
+ "label": "code_ptr_BFC0DF68",
+ "rationale": "code pointer in a data section of shell.elf"
+ },
+ {
+ "address": "0xBFC0DF80",
+ "label": "code_ptr_BFC0DF80",
+ "rationale": "code pointer in a data section of shell.elf"
+ },
+ {
+ "address": "0xBFC0DFAC",
+ "label": "code_ptr_BFC0DFAC",
+ "rationale": "code pointer in a data section of shell.elf"
+ },
+ {
+ "address": "0xBFC0DFD4",
+ "label": "code_ptr_BFC0DFD4",
+ "rationale": "code pointer in a data section of shell.elf"
+ },
+ {
+ "address": "0xBFC0DFDC",
+ "label": "code_ptr_BFC0DFDC",
+ "rationale": "code pointer in a data section of shell.elf"
+ },
+ {
+ "address": "0xBFC0E050",
+ "label": "ramsyscall_printf",
+ "rationale": "ELF STT_FUNC symbol from shell.elf"
+ },
+ {
+ "address": "0xBFC1E514",
+ "label": "fastMemset",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC1E6CC",
+ "label": "cdromUndeliverAllExceptAckAndRdy",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC1E6E0",
+ "label": "code_ptr_BFC1E6E0",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC1E748",
+ "label": "cdromUndeliverAll",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC1E7D4",
+ "label": "code_ptr_BFC1E7D4",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC1E7DC",
+ "label": "code_ptr_BFC1E7DC",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC1E7E4",
+ "label": "initiateDMA",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC1E7E8",
+ "label": "code_ptr_BFC1E7E8",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC1E7F0",
+ "label": "code_ptr_BFC1E7F0",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC1E7F4",
+ "label": "code_ptr_BFC1E7F4",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC1E80C",
+ "label": "code_ptr_BFC1E80C",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC1E810",
+ "label": "code_ptr_BFC1E810",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC1E814",
+ "label": "code_ptr_BFC1E814",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC1E834",
+ "label": "code_ptr_BFC1E834",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC1E848",
+ "label": "code_ptr_BFC1E848",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC1E8CC",
+ "label": "genericErrorState",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC1E91C",
+ "label": "demuteAck",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC1E94C",
+ "label": "cdromDMAVerifier",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC1E9E8",
+ "label": "cdromIOHandler",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC1EA24",
+ "label": "cdromDMAHandler",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC1EA60",
+ "label": "cdromIOVerifier",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC1EB50",
+ "label": "code_ptr_BFC1EB50",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC1EBD4",
+ "label": "code_ptr_BFC1EBD4",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC1EBD8",
+ "label": "code_ptr_BFC1EBD8",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC1EBDC",
+ "label": "code_ptr_BFC1EBDC",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC1EBE8",
+ "label": "code_ptr_BFC1EBE8",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC1EBEC",
+ "label": "code_ptr_BFC1EBEC",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC1EBF0",
+ "label": "code_ptr_BFC1EBF0",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC1EBF8",
+ "label": "code_ptr_BFC1EBF8",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC1EC0C",
+ "label": "code_ptr_BFC1EC0C",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC1EC10",
+ "label": "code_ptr_BFC1EC10",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC1EC14",
+ "label": "code_ptr_BFC1EC14",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC1EC20",
+ "label": "code_ptr_BFC1EC20",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC1EC30",
+ "label": "code_ptr_BFC1EC30",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC1EC34",
+ "label": "code_ptr_BFC1EC34",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC1EC44",
+ "label": "code_ptr_BFC1EC44",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC1EC94",
+ "label": "code_ptr_BFC1EC94",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC1ECA0",
+ "label": "code_ptr_BFC1ECA0",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC1ECD4",
+ "label": "code_ptr_BFC1ECD4",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC1ECDC",
+ "label": "code_ptr_BFC1ECDC",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC1EDC4",
+ "label": "code_ptr_BFC1EDC4",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC1EED4",
+ "label": "code_ptr_BFC1EED4",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC1EEE4",
+ "label": "code_ptr_BFC1EEE4",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC1EEFC",
+ "label": "code_ptr_BFC1EEFC",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC1EF04",
+ "label": "code_ptr_BFC1EF04",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC1EF14",
+ "label": "code_ptr_BFC1EF14",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC1EF24",
+ "label": "code_ptr_BFC1EF24",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC1EF34",
+ "label": "code_ptr_BFC1EF34",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC1EFC4",
+ "label": "code_ptr_BFC1EFC4",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC1EFCC",
+ "label": "code_ptr_BFC1EFCC",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC1EFD4",
+ "label": "code_ptr_BFC1EFD4",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC1EFD8",
+ "label": "code_ptr_BFC1EFD8",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC1EFDC",
+ "label": "code_ptr_BFC1EFDC",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC1EFE4",
+ "label": "code_ptr_BFC1EFE4",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC1EFEC",
+ "label": "code_ptr_BFC1EFEC",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC1EFF0",
+ "label": "code_ptr_BFC1EFF0",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC1EFF4",
+ "label": "code_ptr_BFC1EFF4",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC1F004",
+ "label": "code_ptr_BFC1F004",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC1F010",
+ "label": "code_ptr_BFC1F010",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC1F014",
+ "label": "code_ptr_BFC1F014",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC1F034",
+ "label": "code_ptr_BFC1F034",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC1F0C4",
+ "label": "code_ptr_BFC1F0C4",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC1F0FC",
+ "label": "code_ptr_BFC1F0FC",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC1F1F0",
+ "label": "code_ptr_BFC1F1F0",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC1F290",
+ "label": "cdromSeekL",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC1F36C",
+ "label": "cdromGetStatus",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC1F3C8",
+ "label": "cdromRead",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC1F3F0",
+ "label": "code_ptr_BFC1F3F0",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC1F408",
+ "label": "code_ptr_BFC1F408",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC1F49C",
+ "label": "cdromSetMode",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC1F510",
+ "label": "resetAllCDRomIRQs",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC1F54C",
+ "label": "enableAllCDRomIRQs",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC1F568",
+ "label": "cdromInnerInit",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC1F6B8",
+ "label": "IRQVerifier",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC1F7D4",
+ "label": "code_ptr_BFC1F7D4",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC1F7D8",
+ "label": "code_ptr_BFC1F7D8",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC1F7DC",
+ "label": "code_ptr_BFC1F7DC",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC1F7E0",
+ "label": "code_ptr_BFC1F7E0",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC1F7E4",
+ "label": "code_ptr_BFC1F7E4",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC1F7E8",
+ "label": "code_ptr_BFC1F7E8",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC1F7EC",
+ "label": "code_ptr_BFC1F7EC",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC1F7F4",
+ "label": "code_ptr_BFC1F7F4",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC1F804",
+ "label": "code_ptr_BFC1F804",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC1F808",
+ "label": "code_ptr_BFC1F808",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC1F80C",
+ "label": "code_ptr_BFC1F80C",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC1F814",
+ "label": "code_ptr_BFC1F814",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC1F818",
+ "label": "code_ptr_BFC1F818",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC1F81C",
+ "label": "code_ptr_BFC1F81C",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC1F824",
+ "label": "code_ptr_BFC1F824",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC1F834",
+ "label": "code_ptr_BFC1F834",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC1F844",
+ "label": "code_ptr_BFC1F844",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC1F84C",
+ "label": "code_ptr_BFC1F84C",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC1F854",
+ "label": "code_ptr_BFC1F854",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC1FA48",
+ "label": "T3verifier",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC1FAA0",
+ "label": "T2verifier",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC1FAF8",
+ "label": "T1verifier",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC1FB50",
+ "label": "T0verifier",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC1FBA4",
+ "label": "T3handler",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC1FBD4",
+ "label": "code_ptr_BFC1FBD4",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC1FBD8",
+ "label": "T2handler",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC1FBE8",
+ "label": "code_ptr_BFC1FBE8",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC1FBEC",
+ "label": "code_ptr_BFC1FBEC",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC1FBF0",
+ "label": "code_ptr_BFC1FBF0",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC1FC08",
+ "label": "code_ptr_BFC1FC08",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC1FC0C",
+ "label": "T1handler",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC1FC18",
+ "label": "code_ptr_BFC1FC18",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC1FC1C",
+ "label": "code_ptr_BFC1FC1C",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC1FC20",
+ "label": "code_ptr_BFC1FC20",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC1FC2C",
+ "label": "code_ptr_BFC1FC2C",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC1FC40",
+ "label": "T0handler",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC1FC44",
+ "label": "code_ptr_BFC1FC44",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC1FC4C",
+ "label": "code_ptr_BFC1FC4C",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC1FC74",
+ "label": "enqueueIrqHandler",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC1FC9C",
+ "label": "setIrqAutoAck",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC1FCB4",
+ "label": "enqueueRCntIrqs",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC1FD58",
+ "label": "setTimerAutoAck",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC1FD74",
+ "label": "getTimer",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC1FD98",
+ "label": "initTimer",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC1FDF8",
+ "label": "enableTimerIRQ",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC1FE2C",
+ "label": "disableTimerIRQ",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC1FE60",
+ "label": "restartTimer",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC1FE8C",
+ "label": "sysEnqIntRP",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC1FEB0",
+ "label": "sysDeqIntRP",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC1FF40",
+ "label": "syscallVerifier",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC1FFD4",
+ "label": "code_ptr_BFC1FFD4",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC1FFD8",
+ "label": "code_ptr_BFC1FFD8",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC1FFDC",
+ "label": "code_ptr_BFC1FFDC",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC1FFE0",
+ "label": "code_ptr_BFC1FFE0",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC1FFE4",
+ "label": "code_ptr_BFC1FFE4",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC1FFE8",
+ "label": "code_ptr_BFC1FFE8",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC1FFF4",
+ "label": "code_ptr_BFC1FFF4",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC20004",
+ "label": "code_ptr_BFC20004",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC20010",
+ "label": "code_ptr_BFC20010",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC20014",
+ "label": "code_ptr_BFC20014",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC20018",
+ "label": "code_ptr_BFC20018",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC2002C",
+ "label": "code_ptr_BFC2002C",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC20034",
+ "label": "code_ptr_BFC20034",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC20044",
+ "label": "code_ptr_BFC20044",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC20050",
+ "label": "enqueueSyscallHandler",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC2005C",
+ "label": "multi_malloc",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC20184",
+ "label": "multi_free",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC202DC",
+ "label": "multi_realloc",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC204F4",
+ "label": "code_ptr_BFC204F4",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC20554",
+ "label": "user_malloc",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC2055C",
+ "label": "user_free",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC20564",
+ "label": "user_realloc",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC2056C",
+ "label": "user_initheap",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC2059C",
+ "label": "kern_malloc",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC205A4",
+ "label": "kern_free",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC205AC",
+ "label": "kern_realloc",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC205B4",
+ "label": "kern_initheap",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC205E4",
+ "label": "deliverEvent",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC206B8",
+ "label": "undeliverEvent",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC20730",
+ "label": "wrapper_calloc",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC20770",
+ "label": "getCop0CauseAndEPC",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC20780",
+ "label": "exceptionHandler",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC207E4",
+ "label": "code_ptr_BFC207E4",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC20940",
+ "label": "code_ptr_BFC20940",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC209A0",
+ "label": "returnFromException",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC20A54",
+ "label": "A0Handler",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC20A74",
+ "label": "B0Handler",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC20A94",
+ "label": "C0Handler",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC20AB4",
+ "label": "OBHandler",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC20AD0",
+ "label": "unimplementedThunk",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC20AE8",
+ "label": "ramsyscall_printf",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC20AF4",
+ "label": "busyloop",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC20B30",
+ "label": "mcReadHandler",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC20B80",
+ "label": "code_ptr_BFC20B80",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC20BEC",
+ "label": "code_ptr_BFC20BEC",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC20C20",
+ "label": "code_ptr_BFC20C20",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC20CCC",
+ "label": "code_ptr_BFC20CCC",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC20D00",
+ "label": "code_ptr_BFC20D00",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC20D34",
+ "label": "code_ptr_BFC20D34",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC20D48",
+ "label": "code_ptr_BFC20D48",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC20D5C",
+ "label": "code_ptr_BFC20D5C",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC20D8C",
+ "label": "code_ptr_BFC20D8C",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC20DA0",
+ "label": "code_ptr_BFC20DA0",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC20DFC",
+ "label": "code_ptr_BFC20DFC",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC20E48",
+ "label": "code_ptr_BFC20E48",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC20EA4",
+ "label": "code_ptr_BFC20EA4",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC20F18",
+ "label": "mcWriteHandler",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC20F70",
+ "label": "code_ptr_BFC20F70",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC20FD4",
+ "label": "code_ptr_BFC20FD4",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC20FD8",
+ "label": "code_ptr_BFC20FD8",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC20FDC",
+ "label": "code_ptr_BFC20FDC",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC20FE0",
+ "label": "code_ptr_BFC20FE0",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC20FE4",
+ "label": "code_ptr_BFC20FE4",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC20FE8",
+ "label": "code_ptr_BFC20FE8",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC20FEC",
+ "label": "code_ptr_BFC20FEC",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC21004",
+ "label": "code_ptr_BFC21004",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC21014",
+ "label": "code_ptr_BFC21014",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC2101C",
+ "label": "code_ptr_BFC2101C",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC21020",
+ "label": "code_ptr_BFC21020",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC2102C",
+ "label": "code_ptr_BFC2102C",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC21034",
+ "label": "code_ptr_BFC21034",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC21044",
+ "label": "code_ptr_BFC21044",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC2104C",
+ "label": "code_ptr_BFC2104C",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC210C0",
+ "label": "code_ptr_BFC210C0",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC210F4",
+ "label": "code_ptr_BFC210F4",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC21104",
+ "label": "code_ptr_BFC21104",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC2113C",
+ "label": "code_ptr_BFC2113C",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC21190",
+ "label": "code_ptr_BFC21190",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC211B8",
+ "label": "code_ptr_BFC211B8",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC211CC",
+ "label": "code_ptr_BFC211CC",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC211FC",
+ "label": "code_ptr_BFC211FC",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC212E0",
+ "label": "mcInfoHandler",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC214E0",
+ "label": "getCardStatus",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC214F8",
+ "label": "waitCardStatus",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC21528",
+ "label": "exceptionHandlerCardFastTrack",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC216AC",
+ "label": "mcVerifier",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC216F0",
+ "label": "sio0Verifier",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC2171C",
+ "label": "padAbort",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC21794",
+ "label": "setupSIO0",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC217E4",
+ "label": "code_ptr_BFC217E4",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC21808",
+ "label": "mcHandler",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC2180C",
+ "label": "code_ptr_BFC2180C",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC21844",
+ "label": "code_ptr_BFC21844",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC2190C",
+ "label": "code_ptr_BFC2190C",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC21A04",
+ "label": "code_ptr_BFC21A04",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC21A20",
+ "label": "readPad.isra.0",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC21BD4",
+ "label": "code_ptr_BFC21BD4",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC21BDC",
+ "label": "code_ptr_BFC21BDC",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC21BE4",
+ "label": "code_ptr_BFC21BE4",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC21BEC",
+ "label": "code_ptr_BFC21BEC",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC21BFC",
+ "label": "code_ptr_BFC21BFC",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC21C04",
+ "label": "code_ptr_BFC21C04",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC21C0C",
+ "label": "code_ptr_BFC21C0C",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC21C14",
+ "label": "code_ptr_BFC21C14",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC21C1C",
+ "label": "code_ptr_BFC21C1C",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC21C20",
+ "label": "code_ptr_BFC21C20",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC21C34",
+ "label": "code_ptr_BFC21C34",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC21C44",
+ "label": "code_ptr_BFC21C44",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC21E20",
+ "label": "code_ptr_BFC21E20",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC21E40",
+ "label": "sio0Handler",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC21FD4",
+ "label": "code_ptr_BFC21FD4",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC21FD8",
+ "label": "code_ptr_BFC21FD8",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC21FDC",
+ "label": "code_ptr_BFC21FDC",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC21FE0",
+ "label": "code_ptr_BFC21FE0",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC21FE4",
+ "label": "code_ptr_BFC21FE4",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC21FF4",
+ "label": "code_ptr_BFC21FF4",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC22004",
+ "label": "code_ptr_BFC22004",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC22014",
+ "label": "code_ptr_BFC22014",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC22020",
+ "label": "code_ptr_BFC22020",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC22034",
+ "label": "code_ptr_BFC22034",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC22074",
+ "label": "setSIO0AutoAck",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC22084",
+ "label": "patch_setPadOutputData",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC22098",
+ "label": "initPad",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC2218C",
+ "label": "startPad",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC22208",
+ "label": "stopPad",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC2224C",
+ "label": "initCard",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC22330",
+ "label": "startCard",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC223B0",
+ "label": "stopCard",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC223E8",
+ "label": "code_ptr_BFC223E8",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC223FC",
+ "label": "alterUserPadData",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC2240C",
+ "label": "code_ptr_BFC2240C",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC22414",
+ "label": "code_ptr_BFC22414",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC2249C",
+ "label": "initPadHighLevel",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC22578",
+ "label": "readPadHighLevel",
+ "rationale": "ELF STT_FUNC symbol from openbios.elf"
+ },
+ {
+ "address": "0xBFC22674",
+ "label": "code_ptr_BFC22674",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC227D4",
+ "label": "code_ptr_BFC227D4",
+ "rationale": "code pointer in a data section of openbios.elf"
+ },
+ {
+ "address": "0xBFC227E4",
+ "label": "code_ptr_BFC227E4",
+ "rationale": "code pointer in a data section of openbios.elf"
+ }
+ ]
+}
diff --git a/recompiler/seeds/phase2_ghidra_seeds_SCPH101.json b/recompiler/seeds/phase2_ghidra_seeds_SCPH101.json
new file mode 100644
index 00000000..aa5b1311
--- /dev/null
+++ b/recompiler/seeds/phase2_ghidra_seeds_SCPH101.json
@@ -0,0 +1,2654 @@
+{
+ "schema": "psxrecomp phase2 seeds",
+ "source": "phase2_ghidra_seeds.json filtered vs SCPH101 ROM (64-byte window)",
+ "seed_count": 534,
+ "seeds": [
+ {
+ "address": "0xBFC00000",
+ "label": "reset_vector",
+ "rationale": "MIPS R3000A reset vector"
+ },
+ {
+ "address": "0xBFC00180",
+ "label": "general_exception_bev1",
+ "rationale": "BEV=1 general exception vector"
+ },
+ {
+ "address": "0xBFC00420",
+ "label": "ghidra_BFC00420",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC00678",
+ "label": "ghidra_BFC00678",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC006A8",
+ "label": "ghidra_BFC006A8",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC006D8",
+ "label": "ghidra_BFC006D8",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC00778",
+ "label": "ghidra_BFC00778",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC00810",
+ "label": "ghidra_BFC00810",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC008A0",
+ "label": "ghidra_BFC008A0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC00944",
+ "label": "ghidra_BFC00944",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC00B7C",
+ "label": "ghidra_BFC00B7C",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC00DA0",
+ "label": "ghidra_BFC00DA0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC018E0",
+ "label": "ghidra_BFC018E0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC01920",
+ "label": "ghidra_BFC01920",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC01A60",
+ "label": "ghidra_BFC01A60",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC01A90",
+ "label": "ghidra_BFC01A90",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC01ACC",
+ "label": "ghidra_BFC01ACC",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC01B80",
+ "label": "ghidra_BFC01B80",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC01C8C",
+ "label": "ghidra_BFC01C8C",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC01E5C",
+ "label": "ghidra_BFC01E5C",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC020F0",
+ "label": "ghidra_BFC020F0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC02104",
+ "label": "ghidra_BFC02104",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC02240",
+ "label": "ghidra_BFC02240",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0227C",
+ "label": "ghidra_BFC0227C",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC022C0",
+ "label": "ghidra_BFC022C0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC02324",
+ "label": "ghidra_BFC02324",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC02590",
+ "label": "ghidra_BFC02590",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC025B0",
+ "label": "ghidra_BFC025B0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC02738",
+ "label": "ghidra_BFC02738",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC02950",
+ "label": "ghidra_BFC02950",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC02AF0",
+ "label": "ghidra_BFC02AF0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC02B10",
+ "label": "ghidra_BFC02B10",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC02B50",
+ "label": "ghidra_BFC02B50",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC02CC0",
+ "label": "ghidra_BFC02CC0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC02D20",
+ "label": "ghidra_BFC02D20",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC02DAC",
+ "label": "ghidra_BFC02DAC",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC02EA0",
+ "label": "ghidra_BFC02EA0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC02EDC",
+ "label": "ghidra_BFC02EDC",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC02F20",
+ "label": "ghidra_BFC02F20",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC03098",
+ "label": "ghidra_BFC03098",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC03104",
+ "label": "ghidra_BFC03104",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC03190",
+ "label": "ghidra_BFC03190",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC03288",
+ "label": "ghidra_BFC03288",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC03310",
+ "label": "ghidra_BFC03310",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC033C8",
+ "label": "ghidra_BFC033C8",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC03418",
+ "label": "ghidra_BFC03418",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC03494",
+ "label": "ghidra_BFC03494",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC03764",
+ "label": "ghidra_BFC03764",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC03910",
+ "label": "ghidra_BFC03910",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC03968",
+ "label": "ghidra_BFC03968",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC03978",
+ "label": "ghidra_BFC03978",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC03990",
+ "label": "ghidra_BFC03990",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC039B0",
+ "label": "ghidra_BFC039B0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC03A18",
+ "label": "ghidra_BFC03A18",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC03AA4",
+ "label": "ghidra_BFC03AA4",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC03C90",
+ "label": "ghidra_BFC03C90",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC03CF0",
+ "label": "ghidra_BFC03CF0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC03D80",
+ "label": "ghidra_BFC03D80",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC03EA0",
+ "label": "ghidra_BFC03EA0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC03F8C",
+ "label": "ghidra_BFC03F8C",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC03FAC",
+ "label": "ghidra_BFC03FAC",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC03FE0",
+ "label": "ghidra_BFC03FE0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0403C",
+ "label": "ghidra_BFC0403C",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC040EC",
+ "label": "ghidra_BFC040EC",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC04138",
+ "label": "ghidra_BFC04138",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC04260",
+ "label": "ghidra_BFC04260",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC042A0",
+ "label": "ghidra_BFC042A0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC042D0",
+ "label": "ghidra_BFC042D0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC04610",
+ "label": "ghidra_BFC04610",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC04678",
+ "label": "ghidra_BFC04678",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0472C",
+ "label": "ghidra_BFC0472C",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC048D0",
+ "label": "ghidra_BFC048D0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC04ABC",
+ "label": "ghidra_BFC04ABC",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC04BC4",
+ "label": "ghidra_BFC04BC4",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC04C38",
+ "label": "ghidra_BFC04C38",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC04D50",
+ "label": "ghidra_BFC04D50",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC04DEC",
+ "label": "ghidra_BFC04DEC",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC04FBC",
+ "label": "ghidra_BFC04FBC",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0506C",
+ "label": "ghidra_BFC0506C",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC050A4",
+ "label": "ghidra_BFC050A4",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC050FC",
+ "label": "ghidra_BFC050FC",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC05120",
+ "label": "ghidra_BFC05120",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC05194",
+ "label": "ghidra_BFC05194",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC05558",
+ "label": "ghidra_BFC05558",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC057B0",
+ "label": "ghidra_BFC057B0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0593C",
+ "label": "ghidra_BFC0593C",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC05A44",
+ "label": "ghidra_BFC05A44",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC05C18",
+ "label": "ghidra_BFC05C18",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC05C50",
+ "label": "ghidra_BFC05C50",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC05D04",
+ "label": "ghidra_BFC05D04",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC05DB0",
+ "label": "ghidra_BFC05DB0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC05DF4",
+ "label": "ghidra_BFC05DF4",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC05E58",
+ "label": "ghidra_BFC05E58",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC05F08",
+ "label": "ghidra_BFC05F08",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC06010",
+ "label": "ghidra_BFC06010",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC06038",
+ "label": "ghidra_BFC06038",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC060DC",
+ "label": "ghidra_BFC060DC",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0615C",
+ "label": "ghidra_BFC0615C",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC061A0",
+ "label": "ghidra_BFC061A0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC061D4",
+ "label": "ghidra_BFC061D4",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC06218",
+ "label": "ghidra_BFC06218",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC06288",
+ "label": "ghidra_BFC06288",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC062CC",
+ "label": "ghidra_BFC062CC",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC063CC",
+ "label": "ghidra_BFC063CC",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC064D8",
+ "label": "ghidra_BFC064D8",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC06548",
+ "label": "ghidra_BFC06548",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC065C0",
+ "label": "ghidra_BFC065C0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0661C",
+ "label": "ghidra_BFC0661C",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC06680",
+ "label": "ghidra_BFC06680",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC06700",
+ "label": "ghidra_BFC06700",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC06784",
+ "label": "ghidra_BFC06784",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC067E8",
+ "label": "ghidra_BFC067E8",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC06CA4",
+ "label": "ghidra_BFC06CA4",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC06EC4",
+ "label": "ghidra_BFC06EC4",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC06F28",
+ "label": "ghidra_BFC06F28",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC06FA4",
+ "label": "ghidra_BFC06FA4",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC06FF0",
+ "label": "ghidra_BFC06FF0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0703C",
+ "label": "ghidra_BFC0703C",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC070AC",
+ "label": "ghidra_BFC070AC",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0711C",
+ "label": "ghidra_BFC0711C",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC07148",
+ "label": "ghidra_BFC07148",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC071A0",
+ "label": "ghidra_BFC071A0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC072B8",
+ "label": "ghidra_BFC072B8",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC07330",
+ "label": "ghidra_BFC07330",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC07370",
+ "label": "ghidra_BFC07370",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC073A0",
+ "label": "ghidra_BFC073A0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC07410",
+ "label": "ghidra_BFC07410",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC07664",
+ "label": "ghidra_BFC07664",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC07700",
+ "label": "ghidra_BFC07700",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC078A4",
+ "label": "ghidra_BFC078A4",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC07A04",
+ "label": "ghidra_BFC07A04",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC07B88",
+ "label": "ghidra_BFC07B88",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC07C1C",
+ "label": "ghidra_BFC07C1C",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC07F28",
+ "label": "ghidra_BFC07F28",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC08020",
+ "label": "ghidra_BFC08020",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC08098",
+ "label": "ghidra_BFC08098",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC081FC",
+ "label": "ghidra_BFC081FC",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC08354",
+ "label": "ghidra_BFC08354",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC083CC",
+ "label": "ghidra_BFC083CC",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC085B0",
+ "label": "ghidra_BFC085B0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC085D4",
+ "label": "ghidra_BFC085D4",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC086B0",
+ "label": "ghidra_BFC086B0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC086EC",
+ "label": "ghidra_BFC086EC",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC08720",
+ "label": "ghidra_BFC08720",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC087C4",
+ "label": "ghidra_BFC087C4",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0884C",
+ "label": "ghidra_BFC0884C",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC088A0",
+ "label": "ghidra_BFC088A0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0895C",
+ "label": "ghidra_BFC0895C",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC089C0",
+ "label": "ghidra_BFC089C0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC08B3C",
+ "label": "ghidra_BFC08B3C",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC08FFC",
+ "label": "ghidra_BFC08FFC",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC09144",
+ "label": "ghidra_BFC09144",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC091CC",
+ "label": "ghidra_BFC091CC",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC092EC",
+ "label": "ghidra_BFC092EC",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC09540",
+ "label": "ghidra_BFC09540",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC09598",
+ "label": "ghidra_BFC09598",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC09720",
+ "label": "ghidra_BFC09720",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC098A8",
+ "label": "ghidra_BFC098A8",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC09914",
+ "label": "ghidra_BFC09914",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0994C",
+ "label": "ghidra_BFC0994C",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0996C",
+ "label": "ghidra_BFC0996C",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC09CDC",
+ "label": "ghidra_BFC09CDC",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC09D3C",
+ "label": "ghidra_BFC09D3C",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0A080",
+ "label": "ghidra_BFC0A080",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0A3D4",
+ "label": "ghidra_BFC0A3D4",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0A43C",
+ "label": "ghidra_BFC0A43C",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0A4B4",
+ "label": "ghidra_BFC0A4B4",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0A5E4",
+ "label": "ghidra_BFC0A5E4",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0A754",
+ "label": "ghidra_BFC0A754",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0A8BC",
+ "label": "ghidra_BFC0A8BC",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0AB60",
+ "label": "ghidra_BFC0AB60",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0AD08",
+ "label": "ghidra_BFC0AD08",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0B0D0",
+ "label": "ghidra_BFC0B0D0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0B170",
+ "label": "ghidra_BFC0B170",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0B394",
+ "label": "ghidra_BFC0B394",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0B54C",
+ "label": "ghidra_BFC0B54C",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0B5A0",
+ "label": "ghidra_BFC0B5A0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0B5F4",
+ "label": "ghidra_BFC0B5F4",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0B648",
+ "label": "ghidra_BFC0B648",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0BFF0",
+ "label": "ghidra_BFC0BFF0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0C048",
+ "label": "ghidra_BFC0C048",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0C0DC",
+ "label": "ghidra_BFC0C0DC",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0C140",
+ "label": "ghidra_BFC0C140",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0C1FC",
+ "label": "ghidra_BFC0C1FC",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0C220",
+ "label": "ghidra_BFC0C220",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0C248",
+ "label": "ghidra_BFC0C248",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0C270",
+ "label": "ghidra_BFC0C270",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0C298",
+ "label": "ghidra_BFC0C298",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0C2C0",
+ "label": "ghidra_BFC0C2C0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0C2E8",
+ "label": "ghidra_BFC0C2E8",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0C318",
+ "label": "ghidra_BFC0C318",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0C354",
+ "label": "ghidra_BFC0C354",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0C580",
+ "label": "ghidra_BFC0C580",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0C720",
+ "label": "ghidra_BFC0C720",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0C830",
+ "label": "ghidra_BFC0C830",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0C890",
+ "label": "ghidra_BFC0C890",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0C9CC",
+ "label": "ghidra_BFC0C9CC",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0CAB4",
+ "label": "ghidra_BFC0CAB4",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0CC68",
+ "label": "ghidra_BFC0CC68",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0CD80",
+ "label": "ghidra_BFC0CD80",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0CF84",
+ "label": "ghidra_BFC0CF84",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0D0CC",
+ "label": "ghidra_BFC0D0CC",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0D11C",
+ "label": "ghidra_BFC0D11C",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0D34C",
+ "label": "ghidra_BFC0D34C",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0D4CC",
+ "label": "ghidra_BFC0D4CC",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0D570",
+ "label": "ghidra_BFC0D570",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0D600",
+ "label": "ghidra_BFC0D600",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0D640",
+ "label": "ghidra_BFC0D640",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0D664",
+ "label": "ghidra_BFC0D664",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0D688",
+ "label": "ghidra_BFC0D688",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0D72C",
+ "label": "ghidra_BFC0D72C",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0D7BC",
+ "label": "ghidra_BFC0D7BC",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0D850",
+ "label": "ghidra_BFC0D850",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0D880",
+ "label": "ghidra_BFC0D880",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0D890",
+ "label": "ghidra_BFC0D890",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0D8A0",
+ "label": "ghidra_BFC0D8A0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0D8B0",
+ "label": "ghidra_BFC0D8B0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0D8C0",
+ "label": "ghidra_BFC0D8C0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0D8D0",
+ "label": "ghidra_BFC0D8D0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0D950",
+ "label": "ghidra_BFC0D950",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0D960",
+ "label": "ghidra_BFC0D960",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0D970",
+ "label": "ghidra_BFC0D970",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0D980",
+ "label": "ghidra_BFC0D980",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0D990",
+ "label": "ghidra_BFC0D990",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0D9A0",
+ "label": "ghidra_BFC0D9A0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0D9B0",
+ "label": "ghidra_BFC0D9B0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0D9C0",
+ "label": "ghidra_BFC0D9C0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0D9D0",
+ "label": "ghidra_BFC0D9D0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0D9E0",
+ "label": "ghidra_BFC0D9E0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0D9F0",
+ "label": "ghidra_BFC0D9F0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0DA00",
+ "label": "ghidra_BFC0DA00",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0DA10",
+ "label": "ghidra_BFC0DA10",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0DA20",
+ "label": "ghidra_BFC0DA20",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0DA30",
+ "label": "ghidra_BFC0DA30",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0DA40",
+ "label": "ghidra_BFC0DA40",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0DA50",
+ "label": "ghidra_BFC0DA50",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0DA60",
+ "label": "ghidra_BFC0DA60",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0DAA0",
+ "label": "ghidra_BFC0DAA0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0DAB0",
+ "label": "ghidra_BFC0DAB0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0DAC0",
+ "label": "ghidra_BFC0DAC0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0DAD0",
+ "label": "ghidra_BFC0DAD0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0DAE0",
+ "label": "ghidra_BFC0DAE0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0DAF0",
+ "label": "ghidra_BFC0DAF0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0DB00",
+ "label": "ghidra_BFC0DB00",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0DB10",
+ "label": "ghidra_BFC0DB10",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0DB20",
+ "label": "ghidra_BFC0DB20",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0DB30",
+ "label": "ghidra_BFC0DB30",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0DB40",
+ "label": "ghidra_BFC0DB40",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0DB50",
+ "label": "ghidra_BFC0DB50",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0DB60",
+ "label": "ghidra_BFC0DB60",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0DB70",
+ "label": "ghidra_BFC0DB70",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0DB80",
+ "label": "ghidra_BFC0DB80",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0DB90",
+ "label": "ghidra_BFC0DB90",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0DBA0",
+ "label": "ghidra_BFC0DBA0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0DBB0",
+ "label": "ghidra_BFC0DBB0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0DBC0",
+ "label": "ghidra_BFC0DBC0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0DBD0",
+ "label": "ghidra_BFC0DBD0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0DBE0",
+ "label": "ghidra_BFC0DBE0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0DBF0",
+ "label": "ghidra_BFC0DBF0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0DC00",
+ "label": "ghidra_BFC0DC00",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0DC10",
+ "label": "ghidra_BFC0DC10",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0DC20",
+ "label": "ghidra_BFC0DC20",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0DC30",
+ "label": "ghidra_BFC0DC30",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0DC40",
+ "label": "ghidra_BFC0DC40",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0DC50",
+ "label": "ghidra_BFC0DC50",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC10C74",
+ "label": "ghidra_BFC10C74",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC11008",
+ "label": "ghidra_BFC11008",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC11294",
+ "label": "ghidra_BFC11294",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC112F4",
+ "label": "ghidra_BFC112F4",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC11358",
+ "label": "ghidra_BFC11358",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC113BC",
+ "label": "ghidra_BFC113BC",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC11420",
+ "label": "ghidra_BFC11420",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC11458",
+ "label": "ghidra_BFC11458",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC11490",
+ "label": "ghidra_BFC11490",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC114C8",
+ "label": "ghidra_BFC114C8",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC11500",
+ "label": "ghidra_BFC11500",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC11620",
+ "label": "ghidra_BFC11620",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC1188C",
+ "label": "ghidra_BFC1188C",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC11B18",
+ "label": "ghidra_BFC11B18",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC11C8C",
+ "label": "ghidra_BFC11C8C",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC11F58",
+ "label": "ghidra_BFC11F58",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC122C0",
+ "label": "ghidra_BFC122C0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC12370",
+ "label": "ghidra_BFC12370",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC12408",
+ "label": "ghidra_BFC12408",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC12458",
+ "label": "ghidra_BFC12458",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC12564",
+ "label": "ghidra_BFC12564",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC12628",
+ "label": "ghidra_BFC12628",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC12794",
+ "label": "ghidra_BFC12794",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC12900",
+ "label": "ghidra_BFC12900",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC12998",
+ "label": "ghidra_BFC12998",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC129FC",
+ "label": "ghidra_BFC129FC",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC12AC8",
+ "label": "ghidra_BFC12AC8",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC12B00",
+ "label": "ghidra_BFC12B00",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC12B60",
+ "label": "ghidra_BFC12B60",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC12C08",
+ "label": "ghidra_BFC12C08",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC12CE8",
+ "label": "ghidra_BFC12CE8",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC12F6C",
+ "label": "ghidra_BFC12F6C",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC12FB4",
+ "label": "ghidra_BFC12FB4",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC13070",
+ "label": "ghidra_BFC13070",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC130AC",
+ "label": "ghidra_BFC130AC",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC13178",
+ "label": "ghidra_BFC13178",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC13244",
+ "label": "ghidra_BFC13244",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC13310",
+ "label": "ghidra_BFC13310",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC133D8",
+ "label": "ghidra_BFC133D8",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC134A4",
+ "label": "ghidra_BFC134A4",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC135AC",
+ "label": "ghidra_BFC135AC",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC137E0",
+ "label": "ghidra_BFC137E0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC13980",
+ "label": "ghidra_BFC13980",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC13AF0",
+ "label": "ghidra_BFC13AF0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC13B24",
+ "label": "ghidra_BFC13B24",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC13B6C",
+ "label": "ghidra_BFC13B6C",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC13C34",
+ "label": "ghidra_BFC13C34",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC13C64",
+ "label": "ghidra_BFC13C64",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC13E04",
+ "label": "ghidra_BFC13E04",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC13E60",
+ "label": "ghidra_BFC13E60",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC13EE8",
+ "label": "ghidra_BFC13EE8",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC13F98",
+ "label": "ghidra_BFC13F98",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC144BC",
+ "label": "ghidra_BFC144BC",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC145C8",
+ "label": "ghidra_BFC145C8",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC14690",
+ "label": "ghidra_BFC14690",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC14710",
+ "label": "ghidra_BFC14710",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC14770",
+ "label": "ghidra_BFC14770",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC147F4",
+ "label": "ghidra_BFC147F4",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC14A90",
+ "label": "ghidra_BFC14A90",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC14B00",
+ "label": "ghidra_BFC14B00",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC14CF4",
+ "label": "ghidra_BFC14CF4",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC158A8",
+ "label": "ghidra_BFC158A8",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC15B9C",
+ "label": "ghidra_BFC15B9C",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC16170",
+ "label": "ghidra_BFC16170",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC11840",
+ "label": "dmiss_BFC11840",
+ "rationale": "dispatch miss: KP2 function, indirect call"
+ },
+ {
+ "address": "0xBFC11524",
+ "label": "dmiss_BFC11524",
+ "rationale": "dispatch miss: KP2 secondary entry point for exception handler, mid-function split of func_00001A00, called during VBlank"
+ },
+ {
+ "address": "0xBFC11610",
+ "label": "dmiss_BFC11610",
+ "rationale": "dispatch miss: KP2 function, tail-call target from 0xBFC11524 exception handler path"
+ },
+ {
+ "address": "0xBFC14A54",
+ "label": "dmiss_BFC14A54",
+ "rationale": "dispatch miss (Beetle wtrace, docs/CASE_A_AOT_GAP.md): chain BUSY-writer at RAM 0x4F54; alias rule RAM=ROM-0xBFC10000+0x500. Reached via JAL inside FUN_bfc147f4; never emitted as standalone entry."
+ },
+ {
+ "address": "0xBFC158D0",
+ "label": "dmiss_BFC158D0",
+ "rationale": "dispatch miss (Beetle wtrace, docs/CASE_A_AOT_GAP.md): chain init slot-0 BUSY writer at RAM 0x5DD0; alias RAM=ROM-0xBFC10000+0x500. Inside FUN_bfc158a8 body."
+ },
+ {
+ "address": "0xBFC158D8",
+ "label": "dmiss_BFC158D8",
+ "rationale": "dispatch miss (Beetle wtrace, docs/CASE_A_AOT_GAP.md): chain init slot-1 BUSY writer at RAM 0x5DD8; alias RAM=ROM-0xBFC10000+0x500. Inside FUN_bfc158a8 body."
+ },
+ {
+ "address": "0xBFC159F4",
+ "label": "dmiss_BFC159F4",
+ "rationale": "dispatch miss (Beetle wtrace, docs/CASE_A_AOT_GAP.md): chain step handler that writes 0x02 OPEN to [0x7568+slot] at RAM 0x5EF4. Indirect-jr target from RAM 0x6c98 jump table; alias RAM=ROM-0xBFC10000+0x500. KEY entry \u00e2\u20ac\u201d without this the gate never clears."
+ },
+ {
+ "address": "0xBFC15AA8",
+ "label": "dmiss_BFC15AA8",
+ "rationale": "dispatch miss (Beetle wtrace, docs/CASE_A_AOT_GAP.md): chain step handler that writes 0x04 OPEN to [0x7568+slot] at RAM 0x5FA8. Indirect-jr target from RAM 0x6c98 jump table; alias RAM=ROM-0xBFC10000+0x500. KEY entry \u00e2\u20ac\u201d paired with 0xBFC159F4."
+ },
+ {
+ "address": "0xBFC04910",
+ "label": "dmiss_BFC04910",
+ "rationale": "Tomba boot dispatch miss after licensed PlayStation screen. Verified in Ghidra/runtime disassembly as a standalone BIOS init routine between 0xBFC048D0 and 0xBFC04ABC, with return at 0xBFC04A98 and table data reference from 0xBFC04554."
+ },
+ {
+ "address": "0xBFC04850",
+ "label": "dmiss_BFC04850",
+ "rationale": "Tomba boot dispatch miss after seeding 0xBFC04910. Verified in Ghidra/runtime disassembly as a standalone BIOS callback-list init routine between 0xBFC0472C and 0xBFC048D0, with return at 0xBFC048C8 and table data reference from 0xBFC04588."
+ },
+ {
+ "address": "0xBFC079F8",
+ "label": "dmiss_BFC079F8",
+ "rationale": "Tomba boot dispatch miss after game callback coverage fixes. Verified in Ghidra as a tiny BIOS table leaf routine (sw a0,0(sp); jr ra; clear v0) referenced by data at 0xBFC04484 and 0xBFC0E30C."
+ },
+ {
+ "address": "0xBFC15F1C",
+ "label": "dmiss_BFC15F1C",
+ "rationale": "dirty_ram hot: SIO/PAD interrupt handler entry point (RAM 0x641C), called via jalr. 10852+ hits/frame across internal branch targets (0x641C, 0x6430, 0x6444, 0x6458, 0x650C, 0x6514, 0x658C, 0x6594). Seeding entry only; CFG discovers internal labels."
+ },
+ {
+ "address": "0xBFC0193C",
+ "label": "cache_flush_continuation",
+ "rationale": "Jump target of cache-flush jr k0 at BFC01938"
+ },
+ {
+ "address": "0xBFC01E24",
+ "label": "SysInitMemory",
+ "rationale": "Dispatch miss during boot \u00e2\u20ac\u201d kernel init function"
+ },
+ {
+ "address": "0xBFC02200",
+ "label": "jal_BFC02200",
+ "rationale": "JAL target from code region"
+ },
+ {
+ "address": "0xBFC02918",
+ "label": "card_copy_dispatch_miss_2026_05_05",
+ "rationale": "Runtime dispatch miss: 8 hits during YES/NO COPY confirm execution. BANDAID per user permission."
+ },
+ {
+ "address": "0xBFC03FC0",
+ "label": "jal_BFC03FC0",
+ "rationale": "JAL target from code region"
+ },
+ {
+ "address": "0xBFC06FDC",
+ "label": "indirect_BFC06FDC",
+ "rationale": "Reached via indirect dispatch during boot"
+ },
+ {
+ "address": "0xBFC09890",
+ "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 \u00e2\u20ac\u201d 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": "0xBFC0BA8C",
+ "label": "bios_BA8C",
+ "rationale": "BIOS boot code function. Dispatch miss from shell input path."
+ },
+ {
+ "address": "0xBFC0C1DC",
+ "label": "bios_C1DC",
+ "rationale": "BIOS boot code function. Dispatch miss from shell input path."
+ },
+ {
+ "address": "0xBFC0DA70",
+ "label": "jal_BFC0DA70",
+ "rationale": "JAL target from code region"
+ },
+ {
+ "address": "0xBFC0DA80",
+ "label": "jal_BFC0DA80",
+ "rationale": "JAL target from code region"
+ },
+ {
+ "address": "0xBFC0DA90",
+ "label": "jal_BFC0DA90",
+ "rationale": "JAL target from code region"
+ },
+ {
+ "address": "0xBFC10000",
+ "label": "kernel_trampoline_0",
+ "rationale": "Trampoline to FileOpen (0x598)"
+ },
+ {
+ "address": "0xBFC10010",
+ "label": "kernel_trampoline_1",
+ "rationale": "Trampoline to FileRead (0x5C4)"
+ },
+ {
+ "address": "0xBFC10020",
+ "label": "kernel_trampoline_2",
+ "rationale": "Trampoline to FileClose (0x5E0)"
+ },
+ {
+ "address": "0xBFC10030",
+ "label": "kernel_trampoline_3",
+ "rationale": "Trampoline to 0x600"
+ },
+ {
+ "address": "0xBFC10040",
+ "label": "kernel_memcpy",
+ "rationale": "ROM-to-RAM and RAM clear routine"
+ },
+ {
+ "address": "0xBFC10098",
+ "label": "kernel_bzero",
+ "rationale": "RAM clear routine (to 0xBFC100BC)"
+ },
+ {
+ "address": "0xBFC100C4",
+ "label": "a0_vector_dispatch",
+ "rationale": "A0 function table dispatch"
+ },
+ {
+ "address": "0xBFC100E0",
+ "label": "b0_vector_dispatch",
+ "rationale": "B0 function table dispatch"
+ },
+ {
+ "address": "0xBFC10100",
+ "label": "c0_vector_dispatch",
+ "rationale": "C0 function table dispatch"
+ },
+ {
+ "address": "0xBFC10120",
+ "label": "kernel_get_cause",
+ "rationale": "mfc0 v0,Cause; jr ra"
+ },
+ {
+ "address": "0xBFC10130",
+ "label": "kernel_get_sr",
+ "rationale": "mfc0 v0,SR; jr ra"
+ },
+ {
+ "address": "0xBFC10140",
+ "label": "kernel_set_sr",
+ "rationale": "mtc0 a0,SR; jr ra"
+ },
+ {
+ "address": "0xBFC10150",
+ "label": "kernel_syscall",
+ "rationale": "syscall; jr ra"
+ },
+ {
+ "address": "0xBFC1015C",
+ "label": "kernel_get_c0_table",
+ "rationale": "Returns C0 table pointer"
+ },
+ {
+ "address": "0xBFC10168",
+ "label": "kernel_get_b0_table",
+ "rationale": "Returns B0 table pointer"
+ },
+ {
+ "address": "0xBFC10174",
+ "label": "kernel_BFC10174",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC10780",
+ "label": "rmiss_BFC10780",
+ "rationale": "runtime dispatch miss"
+ },
+ {
+ "address": "0xBFC109A0",
+ "label": "kernel_BFC109A0",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC109B0",
+ "label": "kernel_BFC109B0",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC10A20",
+ "label": "kernel_BFC10A20",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC10A2C",
+ "label": "kernel_BFC10A2C",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC10A40",
+ "label": "kernel_BFC10A40",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC10B30",
+ "label": "rmiss_BFC10B30",
+ "rationale": "runtime dispatch miss"
+ },
+ {
+ "address": "0xBFC10BB8",
+ "label": "kernel_BFC10BB8",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC10BE0",
+ "label": "kernel_BFC10BE0",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC10BE4",
+ "label": "rmiss_BFC10BE4",
+ "rationale": "runtime dispatch miss"
+ },
+ {
+ "address": "0xBFC10C3C",
+ "label": "kernel_BFC10C3C",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC10F08",
+ "label": "kernel_BFC10F08",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC10F20",
+ "label": "kernel_BFC10F20",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC10F44",
+ "label": "kernel_BFC10F44",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC110D8",
+ "label": "kernel_BFC110D8",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC110F8",
+ "label": "kernel_BFC110F8",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC11114",
+ "label": "kernel_BFC11114",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC111AC",
+ "label": "kernel_BFC111AC",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC111C4",
+ "label": "kernel_BFC111C4",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC111E4",
+ "label": "kernel_BFC111E4",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC1121C",
+ "label": "kernel_BFC1121C",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC11228",
+ "label": "kernel_BFC11228",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC1125C",
+ "label": "kernel_BFC1125C",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC11274",
+ "label": "kernel_BFC11274",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC1152C",
+ "label": "rmiss_BFC1152C",
+ "rationale": "runtime dispatch miss"
+ },
+ {
+ "address": "0xBFC11560",
+ "label": "rmiss_BFC11560",
+ "rationale": "runtime dispatch miss"
+ },
+ {
+ "address": "0xBFC115E8",
+ "label": "rmiss_BFC115E8",
+ "rationale": "runtime dispatch miss"
+ },
+ {
+ "address": "0xBFC115FC",
+ "label": "rmiss_BFC115FC",
+ "rationale": "runtime dispatch miss"
+ },
+ {
+ "address": "0xBFC11644",
+ "label": "kernel_BFC11644",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC1175C",
+ "label": "kernel_BFC1175C",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC11800",
+ "label": "kernel_BFC11800",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC11870",
+ "label": "kernel_BFC11870",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC1191C",
+ "label": "kernel_BFC1191C",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC11944",
+ "label": "kernel_BFC11944",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC11980",
+ "label": "kernel_BFC11980",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC11998",
+ "label": "kernel_BFC11998",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC119C8",
+ "label": "kernel_BFC119C8",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC11A04",
+ "label": "kernel_BFC11A04",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC11A10",
+ "label": "kernel_BFC11A10",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC11A4C",
+ "label": "kernel_BFC11A4C",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC11A88",
+ "label": "kernel_BFC11A88",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC11AFC",
+ "label": "kernel_BFC11AFC",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC11BA8",
+ "label": "kernel_BFC11BA8",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC11BD4",
+ "label": "kernel_BFC11BD4",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC11C14",
+ "label": "kernel_BFC11C14",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC11C24",
+ "label": "kernel_BFC11C24",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC11C50",
+ "label": "kernel_BFC11C50",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC11C68",
+ "label": "kernel_BFC11C68",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC12224",
+ "label": "kernel_BFC12224",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC1228C",
+ "label": "kernel_BFC1228C",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC122A0",
+ "label": "kernel_BFC122A0",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC129EC",
+ "label": "kernel_BFC129EC",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC12BC8",
+ "label": "kernel_BFC12BC8",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC12BE0",
+ "label": "kernel_BFC12BE0",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC12BE4",
+ "label": "rmiss_BFC12BE4",
+ "rationale": "runtime dispatch miss"
+ },
+ {
+ "address": "0xBFC12EA4",
+ "label": "kernel_BFC12EA4",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC1302C",
+ "label": "kernel_BFC1302C",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC130A4",
+ "label": "kernel_BFC130A4",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC13570",
+ "label": "kernel_BFC13570",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC1372C",
+ "label": "kernel_BFC1372C",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC1376C",
+ "label": "rmiss_BFC1376C",
+ "rationale": "runtime dispatch miss"
+ },
+ {
+ "address": "0xBFC137B4",
+ "label": "indirect_BFC137B4",
+ "rationale": "Reached via indirect dispatch during boot (kernel relocated)"
+ },
+ {
+ "address": "0xBFC137BC",
+ "label": "rmiss_BFC137BC",
+ "rationale": "runtime dispatch miss"
+ },
+ {
+ "address": "0xBFC137D0",
+ "label": "kernel_ram_function",
+ "rationale": "Dispatch miss at 0xBFC137D0 (phys 0x00003CD0) \u00e2\u20ac\u201d 3 misses. Kernel RAM function."
+ },
+ {
+ "address": "0xBFC138AC",
+ "label": "kernel_BFC138AC",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC1395C",
+ "label": "kernel_BFC1395C",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC13968",
+ "label": "kernel_BFC13968",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC13A04",
+ "label": "kernel_BFC13A04",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC13ED0",
+ "label": "kernel_BFC13ED0",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC13F5C",
+ "label": "kernel_BFC13F5C",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC1454C",
+ "label": "kernel_BFC1454C",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC14588",
+ "label": "kernel_BFC14588",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC14594",
+ "label": "kernel_BFC14594",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC14670",
+ "label": "kernel_BFC14670",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC14754",
+ "label": "kernel_BFC14754",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC14764",
+ "label": "kernel_BFC14764",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC1483C",
+ "label": "kernel_BFC1483C",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC1484C",
+ "label": "kernel_BFC1484C",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC14864",
+ "label": "kernel_BFC14864",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC1486C",
+ "label": "kernel_BFC1486C",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC15188",
+ "label": "kernel_BFC15188",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC151E8",
+ "label": "kernel_56E8",
+ "rationale": "Kernel function at RAM 0x56E8. Dispatch miss from shell input path."
+ },
+ {
+ "address": "0xBFC15268",
+ "label": "kernel_5768",
+ "rationale": "Kernel function at RAM 0x5768. Cascade from input path seeds."
+ },
+ {
+ "address": "0xBFC1529C",
+ "label": "kernel_579C",
+ "rationale": "Kernel function at RAM 0x579C. Cascade from input path."
+ },
+ {
+ "address": "0xBFC15334",
+ "label": "kernel_5834",
+ "rationale": "Kernel function at RAM 0x5834. Cascade."
+ },
+ {
+ "address": "0xBFC15370",
+ "label": "kernel_5870",
+ "rationale": "Kernel (CARD driver) function. Cascade."
+ },
+ {
+ "address": "0xBFC153B4",
+ "label": "kernel_58B4",
+ "rationale": "Kernel (CARD driver) function at RAM 0x58B4. Cascade."
+ },
+ {
+ "address": "0xBFC153E8",
+ "label": "kernel_58E8",
+ "rationale": "Kernel (CARD driver) function at RAM 0x58E8. Cascade."
+ },
+ {
+ "address": "0xBFC15418",
+ "label": "kernel_5918",
+ "rationale": "Kernel (CARD driver) leaf function at RAM 0x5918. Cascade."
+ },
+ {
+ "address": "0xBFC15454",
+ "label": "kernel_5954",
+ "rationale": "CARD driver cascade."
+ },
+ {
+ "address": "0xBFC15490",
+ "label": "kernel_5990",
+ "rationale": "CARD driver cascade."
+ },
+ {
+ "address": "0xBFC154CC",
+ "label": "kernel_59CC",
+ "rationale": "CARD driver cascade (predicted)."
+ },
+ {
+ "address": "0xBFC15500",
+ "label": "kernel_5A00",
+ "rationale": "CARD driver cascade."
+ },
+ {
+ "address": "0xBFC15508",
+ "label": "kernel_5A08",
+ "rationale": "CARD driver cascade (predicted)."
+ },
+ {
+ "address": "0xBFC15544",
+ "label": "kernel_5A44",
+ "rationale": "CARD driver cascade (predicted)."
+ },
+ {
+ "address": "0xBFC15558",
+ "label": "kernel_5A58",
+ "rationale": "CARD read chain state 12 (data-byte-store): SIO_RX -> a1[0x7F], XOR-updates checksum at 0x7560+slot*4, returns v0=0. Targeted by jump table at RAM 0x6c98 entry 12."
+ },
+ {
+ "address": "0xBFC15580",
+ "label": "kernel_5A80",
+ "rationale": "CARD driver cascade (predicted)."
+ },
+ {
+ "address": "0xBFC155B0",
+ "label": "kernel_5AB0",
+ "rationale": "CARD read chain state 13 (data-loop completion): reads byte vs checksum at 0x7560+slot*4, polls SIO_STAT for RX_RDY, reads END byte; v0=1 if 'G' else v0=-1. Targeted by jump table at RAM 0x6c98 entry 13."
+ },
+ {
+ "address": "0xBFC155BC",
+ "label": "kernel_5ABC",
+ "rationale": "CARD driver cascade (predicted)."
+ },
+ {
+ "address": "0xBFC155F8",
+ "label": "kernel_5AF8",
+ "rationale": "CARD driver cascade (predicted)."
+ },
+ {
+ "address": "0xBFC15634",
+ "label": "kernel_5B34",
+ "rationale": "CARD driver cascade (predicted)."
+ },
+ {
+ "address": "0xBFC15650",
+ "label": "kernel_5B50",
+ "rationale": "Kernel function. Cascade from input path."
+ },
+ {
+ "address": "0xBFC15654",
+ "label": "kernel_5B54",
+ "rationale": "Kernel function. Cascade from input path."
+ },
+ {
+ "address": "0xBFC15664",
+ "label": "kernel_BFC15664",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC156A4",
+ "label": "kernel_5BA4",
+ "rationale": "Kernel function at RAM 0x5BA4. Dispatch miss from shell input path."
+ },
+ {
+ "address": "0xBFC15724",
+ "label": "kernel_5C24",
+ "rationale": "Kernel function at RAM 0x5C24. Cascade from input path seeds."
+ },
+ {
+ "address": "0xBFC15758",
+ "label": "kernel_5C58",
+ "rationale": "Kernel function at RAM 0x5C58. Cascade."
+ },
+ {
+ "address": "0xBFC15848",
+ "label": "kernel_5D48",
+ "rationale": "Kernel (CARD driver) function. Cascade."
+ },
+ {
+ "address": "0xBFC15894",
+ "label": "kernel_5D94",
+ "rationale": "Kernel function. Cascade from input path."
+ },
+ {
+ "address": "0xBFC15898",
+ "label": "kernel_5D98",
+ "rationale": "Kernel (CARD driver) function. Cascade."
+ },
+ {
+ "address": "0xBFC15930",
+ "label": "kernel_BFC15930",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC15950",
+ "label": "kernel_BFC15950",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC15998",
+ "label": "kernel_BFC15998",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC15A04",
+ "label": "kernel_BFC15A04",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC15A4C",
+ "label": "kernel_BFC15A4C",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC15AB8",
+ "label": "kernel_BFC15AB8",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC15AF0",
+ "label": "kernel_BFC15AF0",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC15B5C",
+ "label": "kernel_BFC15B5C",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC15B70",
+ "label": "kernel_BFC15B70",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC15CC4",
+ "label": "kernel_BFC15CC4",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC15E90",
+ "label": "kernel_BFC15E90",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC15EBC",
+ "label": "kernel_BFC15EBC",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC15F08",
+ "label": "kernel_BFC15F08",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC1609C",
+ "label": "kernel_BFC1609C",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC16690",
+ "label": "kernel_BFC16690",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC166A0",
+ "label": "kernel_BFC166A0",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC166B0",
+ "label": "kernel_BFC166B0",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC166C0",
+ "label": "kernel_BFC166C0",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC166D0",
+ "label": "kernel_BFC166D0",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC166E0",
+ "label": "kernel_BFC166E0",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC166F0",
+ "label": "kernel_BFC166F0",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC16700",
+ "label": "kernel_BFC16700",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC16710",
+ "label": "kernel_BFC16710",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC16720",
+ "label": "kernel_BFC16720",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC02934",
+ "rationale": "kernel jump-table target: A0:0F (unresolved (see psx-spx A0 table)) \u2014 ROM-resident entry of the post-init A0/B0/C0 tables (read from live kernel RAM 0x200/0x874/0x674); reachable only through the vector table, invisible to transitive discovery. Class found via MMX6 card-check: A0:2B memset dispatch-missed silently, killing the save-scan (2026-07-02)."
+ },
+ {
+ "address": "0xBFC03200",
+ "rationale": "kernel jump-table target: A0:16 (strncat) \u2014 ROM-resident entry of the post-init A0/B0/C0 tables (read from live kernel RAM 0x200/0x874/0x674); reachable only through the vector table, invisible to transitive discovery. Class found via MMX6 card-check: A0:2B memset dispatch-missed silently, killing the save-scan (2026-07-02)."
+ },
+ {
+ "address": "0xBFC034D0",
+ "rationale": "kernel jump-table target: A0:1C (atob helper) \u2014 ROM-resident entry of the post-init A0/B0/C0 tables (read from live kernel RAM 0x200/0x874/0x674); reachable only through the vector table, invisible to transitive discovery. Class found via MMX6 card-check: A0:2B memset dispatch-missed silently, killing the save-scan (2026-07-02)."
+ },
+ {
+ "address": "0xBFC03514",
+ "rationale": "kernel jump-table target: A0:1D (strtol-family) \u2014 ROM-resident entry of the post-init A0/B0/C0 tables (read from live kernel RAM 0x200/0x874/0x674); reachable only through the vector table, invisible to transitive discovery. Class found via MMX6 card-check: A0:2B memset dispatch-missed silently, killing the save-scan (2026-07-02)."
+ },
+ {
+ "address": "0xBFC0357C",
+ "rationale": "kernel jump-table target: A0:1E (strtol-family) \u2014 ROM-resident entry of the post-init A0/B0/C0 tables (read from live kernel RAM 0x200/0x874/0x674); reachable only through the vector table, invisible to transitive discovery. Class found via MMX6 card-check: A0:2B memset dispatch-missed silently, killing the save-scan (2026-07-02)."
+ },
+ {
+ "address": "0xBFC035C0",
+ "rationale": "kernel jump-table target: A0:1F (abs) \u2014 ROM-resident entry of the post-init A0/B0/C0 tables (read from live kernel RAM 0x200/0x874/0x674); reachable only through the vector table, invisible to transitive discovery. Class found via MMX6 card-check: A0:2B memset dispatch-missed silently, killing the save-scan (2026-07-02)."
+ },
+ {
+ "address": "0xBFC03628",
+ "rationale": "kernel jump-table target: A0:20 (labs) \u2014 ROM-resident entry of the post-init A0/B0/C0 tables (read from live kernel RAM 0x200/0x874/0x674); reachable only through the vector table, invisible to transitive discovery. Class found via MMX6 card-check: A0:2B memset dispatch-missed silently, killing the save-scan (2026-07-02)."
+ },
+ {
+ "address": "0xBFC03694",
+ "rationale": "kernel jump-table target: A0:21 (atoi) \u2014 ROM-resident entry of the post-init A0/B0/C0 tables (read from live kernel RAM 0x200/0x874/0x674); reachable only through the vector table, invisible to transitive discovery. Class found via MMX6 card-check: A0:2B memset dispatch-missed silently, killing the save-scan (2026-07-02)."
+ },
+ {
+ "address": "0xBFC036FC",
+ "rationale": "kernel jump-table target: A0:22 (atol) \u2014 ROM-resident entry of the post-init A0/B0/C0 tables (read from live kernel RAM 0x200/0x874/0x674); reachable only through the vector table, invisible to transitive discovery. Class found via MMX6 card-check: A0:2B memset dispatch-missed silently, killing the save-scan (2026-07-02)."
+ },
+ {
+ "address": "0xBFC03894",
+ "rationale": "kernel jump-table target: A0:24 (strtol-family) \u2014 ROM-resident entry of the post-init A0/B0/C0 tables (read from live kernel RAM 0x200/0x874/0x674); reachable only through the vector table, invisible to transitive discovery. Class found via MMX6 card-check: A0:2B memset dispatch-missed silently, killing the save-scan (2026-07-02)."
+ },
+ {
+ "address": "0xBFC01B08",
+ "rationale": "kernel jump-table target: A0:29 (bcmp) \u2014 ROM-resident entry of the post-init A0/B0/C0 tables (read from live kernel RAM 0x200/0x874/0x674); reachable only through the vector table, invisible to transitive discovery. Class found via MMX6 card-check: A0:2B memset dispatch-missed silently, killing the save-scan (2026-07-02)."
+ },
+ {
+ "address": "0xBFC02B8C",
+ "rationale": "kernel jump-table target: A0:2B (memset) \u2014 ROM-resident entry of the post-init A0/B0/C0 tables (read from live kernel RAM 0x200/0x874/0x674); reachable only through the vector table, invisible to transitive discovery. Class found via MMX6 card-check: A0:2B memset dispatch-missed silently, killing the save-scan (2026-07-02)."
+ },
+ {
+ "address": "0xBFC02BC8",
+ "rationale": "kernel jump-table target: A0:2C (memmove) \u2014 ROM-resident entry of the post-init A0/B0/C0 tables (read from live kernel RAM 0x200/0x874/0x674); reachable only through the vector table, invisible to transitive discovery. Class found via MMX6 card-check: A0:2B memset dispatch-missed silently, killing the save-scan (2026-07-02)."
+ },
+ {
+ "address": "0xBFC02C50",
+ "rationale": "kernel jump-table target: A0:2D (memcmp) \u2014 ROM-resident entry of the post-init A0/B0/C0 tables (read from live kernel RAM 0x200/0x874/0x674); reachable only through the vector table, invisible to transitive discovery. Class found via MMX6 card-check: A0:2B memset dispatch-missed silently, killing the save-scan (2026-07-02)."
+ },
+ {
+ "address": "0xBFC02230",
+ "rationale": "kernel jump-table target: A0:30 (rand-family) \u2014 ROM-resident entry of the post-init A0/B0/C0 tables (read from live kernel RAM 0x200/0x874/0x674); reachable only through the vector table, invisible to transitive discovery. Class found via MMX6 card-check: A0:2B memset dispatch-missed silently, killing the save-scan (2026-07-02)."
+ },
+ {
+ "address": "0xBFC021A0",
+ "rationale": "kernel jump-table target: A0:37 (rand-family) \u2014 ROM-resident entry of the post-init A0/B0/C0 tables (read from live kernel RAM 0x200/0x874/0x674); reachable only through the vector table, invisible to transitive discovery. Class found via MMX6 card-check: A0:2B memset dispatch-missed silently, killing the save-scan (2026-07-02)."
+ },
+ {
+ "address": "0xBFC0D8E0",
+ "rationale": "kernel jump-table target: A0:40 (SystemErrorUnresolvedException) \u2014 ROM-resident entry of the post-init A0/B0/C0 tables (read from live kernel RAM 0x200/0x874/0x674); reachable only through the vector table, invisible to transitive discovery. Class found via MMX6 card-check: A0:2B memset dispatch-missed silently, killing the save-scan (2026-07-02)."
+ },
+ {
+ "address": "0xBFC04120",
+ "rationale": "kernel jump-table target: A0:4D (ChangeClearRCnt-adjacent) \u2014 ROM-resident entry of the post-init A0/B0/C0 tables (read from live kernel RAM 0x200/0x874/0x674); reachable only through the vector table, invisible to transitive discovery. Class found via MMX6 card-check: A0:2B memset dispatch-missed silently, killing the save-scan (2026-07-02)."
+ },
+ {
+ "address": "0xBFC0D8F0",
+ "rationale": "kernel jump-table target: A0:4F (stub/ret) \u2014 ROM-resident entry of the post-init A0/B0/C0 tables (read from live kernel RAM 0x200/0x874/0x674); reachable only through the vector table, invisible to transitive discovery. Class found via MMX6 card-check: A0:2B memset dispatch-missed silently, killing the save-scan (2026-07-02)."
+ },
+ {
+ "address": "0xBFC0D900",
+ "rationale": "kernel jump-table target: A0:50 (stub/ret) \u2014 ROM-resident entry of the post-init A0/B0/C0 tables (read from live kernel RAM 0x200/0x874/0x674); reachable only through the vector table, invisible to transitive discovery. Class found via MMX6 card-check: A0:2B memset dispatch-missed silently, killing the save-scan (2026-07-02)."
+ },
+ {
+ "address": "0xBFC0D910",
+ "rationale": "kernel jump-table target: A0:52 (stub/ret) \u2014 ROM-resident entry of the post-init A0/B0/C0 tables (read from live kernel RAM 0x200/0x874/0x674); reachable only through the vector table, invisible to transitive discovery. Class found via MMX6 card-check: A0:2B memset dispatch-missed silently, killing the save-scan (2026-07-02)."
+ },
+ {
+ "address": "0xBFC0D920",
+ "rationale": "kernel jump-table target: A0:53 (stub/ret) \u2014 ROM-resident entry of the post-init A0/B0/C0 tables (read from live kernel RAM 0x200/0x874/0x674); reachable only through the vector table, invisible to transitive discovery. Class found via MMX6 card-check: A0:2B memset dispatch-missed silently, killing the save-scan (2026-07-02)."
+ },
+ {
+ "address": "0xBFC0B0C4",
+ "rationale": "kernel jump-table target: A0:6F (card/dev service) \u2014 ROM-resident entry of the post-init A0/B0/C0 tables (read from live kernel RAM 0x200/0x874/0x674); reachable only through the vector table, invisible to transitive discovery. Class found via MMX6 card-check: A0:2B memset dispatch-missed silently, killing the save-scan (2026-07-02)."
+ },
+ {
+ "address": "0xBFC04AA0",
+ "rationale": "kernel jump-table target: A0:94 (card-adjacent service) \u2014 ROM-resident entry of the post-init A0/B0/C0 tables (read from live kernel RAM 0x200/0x874/0x674); reachable only through the vector table, invisible to transitive discovery. Class found via MMX6 card-check: A0:2B memset dispatch-missed silently, killing the save-scan (2026-07-02)."
+ },
+ {
+ "address": "0xBFC0D930",
+ "rationale": "kernel jump-table target: A0:9A (stub/ret) \u2014 ROM-resident entry of the post-init A0/B0/C0 tables (read from live kernel RAM 0x200/0x874/0x674); reachable only through the vector table, invisible to transitive discovery. Class found via MMX6 card-check: A0:2B memset dispatch-missed silently, killing the save-scan (2026-07-02)."
+ },
+ {
+ "address": "0xBFC0D940",
+ "rationale": "kernel jump-table target: A0:9B (stub/ret) \u2014 ROM-resident entry of the post-init A0/B0/C0 tables (read from live kernel RAM 0x200/0x874/0x674); reachable only through the vector table, invisible to transitive discovery. Class found via MMX6 card-check: A0:2B memset dispatch-missed silently, killing the save-scan (2026-07-02)."
+ },
+ {
+ "address": "0xBFC06750",
+ "rationale": "kernel jump-table target: A0:9C (kernel service) \u2014 ROM-resident entry of the post-init A0/B0/C0 tables (read from live kernel RAM 0x200/0x874/0x674); reachable only through the vector table, invisible to transitive discovery. Class found via MMX6 card-check: A0:2B memset dispatch-missed silently, killing the save-scan (2026-07-02)."
+ },
+ {
+ "address": "0xBFC06728",
+ "rationale": "kernel jump-table target: A0:9D (kernel service) \u2014 ROM-resident entry of the post-init A0/B0/C0 tables (read from live kernel RAM 0x200/0x874/0x674); reachable only through the vector table, invisible to transitive discovery. Class found via MMX6 card-check: A0:2B memset dispatch-missed silently, killing the save-scan (2026-07-02)."
+ },
+ {
+ "address": "0xBFC050DC",
+ "rationale": "kernel jump-table target: A0:9E (kernel service) \u2014 ROM-resident entry of the post-init A0/B0/C0 tables (read from live kernel RAM 0x200/0x874/0x674); reachable only through the vector table, invisible to transitive discovery. Class found via MMX6 card-check: A0:2B memset dispatch-missed silently, killing the save-scan (2026-07-02)."
+ },
+ {
+ "address": "0xBFC0D4F0",
+ "rationale": "kernel jump-table target: A0:B4 (GetSystemInfo-family) \u2014 ROM-resident entry of the post-init A0/B0/C0 tables (read from live kernel RAM 0x200/0x874/0x674); reachable only through the vector table, invisible to transitive discovery. Class found via MMX6 card-check: A0:2B memset dispatch-missed silently, killing the save-scan (2026-07-02)."
+ }
+ ],
+ "excluded": [
+ {
+ "address": "0xBFC00100",
+ "label": "utlb_miss_bev1",
+ "rationale": "R3000 UTLB-miss vector; PS1 has no TLB; never executed"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/recompiler/seeds/phase2_ghidra_seeds_SCPH5552.json b/recompiler/seeds/phase2_ghidra_seeds_SCPH5552.json
new file mode 100644
index 00000000..35f7ba8b
--- /dev/null
+++ b/recompiler/seeds/phase2_ghidra_seeds_SCPH5552.json
@@ -0,0 +1,2654 @@
+{
+ "schema": "psxrecomp phase2 seeds",
+ "source": "phase2_ghidra_seeds.json filtered vs SCPH5552 ROM (64-byte window)",
+ "seed_count": 534,
+ "seeds": [
+ {
+ "address": "0xBFC00000",
+ "label": "reset_vector",
+ "rationale": "MIPS R3000A reset vector"
+ },
+ {
+ "address": "0xBFC00180",
+ "label": "general_exception_bev1",
+ "rationale": "BEV=1 general exception vector"
+ },
+ {
+ "address": "0xBFC00420",
+ "label": "ghidra_BFC00420",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC00678",
+ "label": "ghidra_BFC00678",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC006A8",
+ "label": "ghidra_BFC006A8",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC006D8",
+ "label": "ghidra_BFC006D8",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC00778",
+ "label": "ghidra_BFC00778",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC00810",
+ "label": "ghidra_BFC00810",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC008A0",
+ "label": "ghidra_BFC008A0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC00944",
+ "label": "ghidra_BFC00944",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC00B7C",
+ "label": "ghidra_BFC00B7C",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC00DA0",
+ "label": "ghidra_BFC00DA0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC018E0",
+ "label": "ghidra_BFC018E0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC01920",
+ "label": "ghidra_BFC01920",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC01A60",
+ "label": "ghidra_BFC01A60",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC01A90",
+ "label": "ghidra_BFC01A90",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC01ACC",
+ "label": "ghidra_BFC01ACC",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC01B80",
+ "label": "ghidra_BFC01B80",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC01C8C",
+ "label": "ghidra_BFC01C8C",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC01E5C",
+ "label": "ghidra_BFC01E5C",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC020F0",
+ "label": "ghidra_BFC020F0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC02104",
+ "label": "ghidra_BFC02104",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC02240",
+ "label": "ghidra_BFC02240",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0227C",
+ "label": "ghidra_BFC0227C",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC022C0",
+ "label": "ghidra_BFC022C0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC02324",
+ "label": "ghidra_BFC02324",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC02590",
+ "label": "ghidra_BFC02590",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC025B0",
+ "label": "ghidra_BFC025B0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC02738",
+ "label": "ghidra_BFC02738",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC02950",
+ "label": "ghidra_BFC02950",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC02AF0",
+ "label": "ghidra_BFC02AF0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC02B10",
+ "label": "ghidra_BFC02B10",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC02B50",
+ "label": "ghidra_BFC02B50",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC02CC0",
+ "label": "ghidra_BFC02CC0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC02D20",
+ "label": "ghidra_BFC02D20",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC02DAC",
+ "label": "ghidra_BFC02DAC",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC02EA0",
+ "label": "ghidra_BFC02EA0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC02EDC",
+ "label": "ghidra_BFC02EDC",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC02F20",
+ "label": "ghidra_BFC02F20",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC03098",
+ "label": "ghidra_BFC03098",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC03104",
+ "label": "ghidra_BFC03104",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC03190",
+ "label": "ghidra_BFC03190",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC03288",
+ "label": "ghidra_BFC03288",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC03310",
+ "label": "ghidra_BFC03310",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC033C8",
+ "label": "ghidra_BFC033C8",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC03418",
+ "label": "ghidra_BFC03418",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC03494",
+ "label": "ghidra_BFC03494",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC03764",
+ "label": "ghidra_BFC03764",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC03910",
+ "label": "ghidra_BFC03910",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC03968",
+ "label": "ghidra_BFC03968",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC03978",
+ "label": "ghidra_BFC03978",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC03990",
+ "label": "ghidra_BFC03990",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC039B0",
+ "label": "ghidra_BFC039B0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC03A18",
+ "label": "ghidra_BFC03A18",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC03AA4",
+ "label": "ghidra_BFC03AA4",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC03C90",
+ "label": "ghidra_BFC03C90",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC03CF0",
+ "label": "ghidra_BFC03CF0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC03D80",
+ "label": "ghidra_BFC03D80",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC03EA0",
+ "label": "ghidra_BFC03EA0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC03F8C",
+ "label": "ghidra_BFC03F8C",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC03FAC",
+ "label": "ghidra_BFC03FAC",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC03FE0",
+ "label": "ghidra_BFC03FE0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0403C",
+ "label": "ghidra_BFC0403C",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC040EC",
+ "label": "ghidra_BFC040EC",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC04138",
+ "label": "ghidra_BFC04138",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC04260",
+ "label": "ghidra_BFC04260",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC042A0",
+ "label": "ghidra_BFC042A0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC042D0",
+ "label": "ghidra_BFC042D0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC04610",
+ "label": "ghidra_BFC04610",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC04678",
+ "label": "ghidra_BFC04678",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0472C",
+ "label": "ghidra_BFC0472C",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC048D0",
+ "label": "ghidra_BFC048D0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC04ABC",
+ "label": "ghidra_BFC04ABC",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC04BC4",
+ "label": "ghidra_BFC04BC4",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC04C38",
+ "label": "ghidra_BFC04C38",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC04D50",
+ "label": "ghidra_BFC04D50",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC04DEC",
+ "label": "ghidra_BFC04DEC",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC04FBC",
+ "label": "ghidra_BFC04FBC",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0506C",
+ "label": "ghidra_BFC0506C",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC050A4",
+ "label": "ghidra_BFC050A4",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC050FC",
+ "label": "ghidra_BFC050FC",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC05120",
+ "label": "ghidra_BFC05120",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC05194",
+ "label": "ghidra_BFC05194",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC05558",
+ "label": "ghidra_BFC05558",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC057B0",
+ "label": "ghidra_BFC057B0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0593C",
+ "label": "ghidra_BFC0593C",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC05A44",
+ "label": "ghidra_BFC05A44",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC05C18",
+ "label": "ghidra_BFC05C18",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC05C50",
+ "label": "ghidra_BFC05C50",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC05D04",
+ "label": "ghidra_BFC05D04",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC05DB0",
+ "label": "ghidra_BFC05DB0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC05DF4",
+ "label": "ghidra_BFC05DF4",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC05E58",
+ "label": "ghidra_BFC05E58",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC05F08",
+ "label": "ghidra_BFC05F08",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC06010",
+ "label": "ghidra_BFC06010",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC06038",
+ "label": "ghidra_BFC06038",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC060DC",
+ "label": "ghidra_BFC060DC",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0615C",
+ "label": "ghidra_BFC0615C",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC061A0",
+ "label": "ghidra_BFC061A0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC061D4",
+ "label": "ghidra_BFC061D4",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC06218",
+ "label": "ghidra_BFC06218",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC06288",
+ "label": "ghidra_BFC06288",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC062CC",
+ "label": "ghidra_BFC062CC",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC063CC",
+ "label": "ghidra_BFC063CC",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC064D8",
+ "label": "ghidra_BFC064D8",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC06548",
+ "label": "ghidra_BFC06548",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC065C0",
+ "label": "ghidra_BFC065C0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0661C",
+ "label": "ghidra_BFC0661C",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC06680",
+ "label": "ghidra_BFC06680",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC06700",
+ "label": "ghidra_BFC06700",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC06784",
+ "label": "ghidra_BFC06784",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC067E8",
+ "label": "ghidra_BFC067E8",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC06CA4",
+ "label": "ghidra_BFC06CA4",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC06EC4",
+ "label": "ghidra_BFC06EC4",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC06F28",
+ "label": "ghidra_BFC06F28",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC06FA4",
+ "label": "ghidra_BFC06FA4",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC06FF0",
+ "label": "ghidra_BFC06FF0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0703C",
+ "label": "ghidra_BFC0703C",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC070AC",
+ "label": "ghidra_BFC070AC",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0711C",
+ "label": "ghidra_BFC0711C",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC07148",
+ "label": "ghidra_BFC07148",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC071A0",
+ "label": "ghidra_BFC071A0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC072B8",
+ "label": "ghidra_BFC072B8",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC07330",
+ "label": "ghidra_BFC07330",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC07370",
+ "label": "ghidra_BFC07370",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC073A0",
+ "label": "ghidra_BFC073A0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC07410",
+ "label": "ghidra_BFC07410",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC07664",
+ "label": "ghidra_BFC07664",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC07700",
+ "label": "ghidra_BFC07700",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC078A4",
+ "label": "ghidra_BFC078A4",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC07A04",
+ "label": "ghidra_BFC07A04",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC07B88",
+ "label": "ghidra_BFC07B88",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC07C1C",
+ "label": "ghidra_BFC07C1C",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC07F28",
+ "label": "ghidra_BFC07F28",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC08020",
+ "label": "ghidra_BFC08020",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC08098",
+ "label": "ghidra_BFC08098",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC081FC",
+ "label": "ghidra_BFC081FC",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC08354",
+ "label": "ghidra_BFC08354",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC083CC",
+ "label": "ghidra_BFC083CC",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC085B0",
+ "label": "ghidra_BFC085B0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC085D4",
+ "label": "ghidra_BFC085D4",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC086B0",
+ "label": "ghidra_BFC086B0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC086EC",
+ "label": "ghidra_BFC086EC",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC08720",
+ "label": "ghidra_BFC08720",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC087C4",
+ "label": "ghidra_BFC087C4",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0884C",
+ "label": "ghidra_BFC0884C",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC088A0",
+ "label": "ghidra_BFC088A0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0895C",
+ "label": "ghidra_BFC0895C",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC089C0",
+ "label": "ghidra_BFC089C0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC08B3C",
+ "label": "ghidra_BFC08B3C",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC08FFC",
+ "label": "ghidra_BFC08FFC",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC09144",
+ "label": "ghidra_BFC09144",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC091CC",
+ "label": "ghidra_BFC091CC",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC092EC",
+ "label": "ghidra_BFC092EC",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC09540",
+ "label": "ghidra_BFC09540",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC09598",
+ "label": "ghidra_BFC09598",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC09720",
+ "label": "ghidra_BFC09720",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC098A8",
+ "label": "ghidra_BFC098A8",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC09914",
+ "label": "ghidra_BFC09914",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0994C",
+ "label": "ghidra_BFC0994C",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0996C",
+ "label": "ghidra_BFC0996C",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC09CDC",
+ "label": "ghidra_BFC09CDC",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC09D3C",
+ "label": "ghidra_BFC09D3C",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0A080",
+ "label": "ghidra_BFC0A080",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0A3D4",
+ "label": "ghidra_BFC0A3D4",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0A43C",
+ "label": "ghidra_BFC0A43C",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0A4B4",
+ "label": "ghidra_BFC0A4B4",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0A5E4",
+ "label": "ghidra_BFC0A5E4",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0A754",
+ "label": "ghidra_BFC0A754",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0A8BC",
+ "label": "ghidra_BFC0A8BC",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0AB60",
+ "label": "ghidra_BFC0AB60",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0AD08",
+ "label": "ghidra_BFC0AD08",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0B0D0",
+ "label": "ghidra_BFC0B0D0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0B170",
+ "label": "ghidra_BFC0B170",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0B394",
+ "label": "ghidra_BFC0B394",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0B54C",
+ "label": "ghidra_BFC0B54C",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0B5A0",
+ "label": "ghidra_BFC0B5A0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0B5F4",
+ "label": "ghidra_BFC0B5F4",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0B648",
+ "label": "ghidra_BFC0B648",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0BFF0",
+ "label": "ghidra_BFC0BFF0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0C048",
+ "label": "ghidra_BFC0C048",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0C0DC",
+ "label": "ghidra_BFC0C0DC",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0C140",
+ "label": "ghidra_BFC0C140",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0C1FC",
+ "label": "ghidra_BFC0C1FC",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0C220",
+ "label": "ghidra_BFC0C220",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0C248",
+ "label": "ghidra_BFC0C248",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0C270",
+ "label": "ghidra_BFC0C270",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0C298",
+ "label": "ghidra_BFC0C298",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0C2C0",
+ "label": "ghidra_BFC0C2C0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0C2E8",
+ "label": "ghidra_BFC0C2E8",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0C318",
+ "label": "ghidra_BFC0C318",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0C354",
+ "label": "ghidra_BFC0C354",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0C580",
+ "label": "ghidra_BFC0C580",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0C720",
+ "label": "ghidra_BFC0C720",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0C830",
+ "label": "ghidra_BFC0C830",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0C890",
+ "label": "ghidra_BFC0C890",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0C9CC",
+ "label": "ghidra_BFC0C9CC",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0CAB4",
+ "label": "ghidra_BFC0CAB4",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0CC68",
+ "label": "ghidra_BFC0CC68",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0CD80",
+ "label": "ghidra_BFC0CD80",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0CF84",
+ "label": "ghidra_BFC0CF84",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0D0CC",
+ "label": "ghidra_BFC0D0CC",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0D11C",
+ "label": "ghidra_BFC0D11C",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0D34C",
+ "label": "ghidra_BFC0D34C",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0D4CC",
+ "label": "ghidra_BFC0D4CC",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0D570",
+ "label": "ghidra_BFC0D570",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0D600",
+ "label": "ghidra_BFC0D600",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0D640",
+ "label": "ghidra_BFC0D640",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0D664",
+ "label": "ghidra_BFC0D664",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0D688",
+ "label": "ghidra_BFC0D688",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0D72C",
+ "label": "ghidra_BFC0D72C",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0D7BC",
+ "label": "ghidra_BFC0D7BC",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0D850",
+ "label": "ghidra_BFC0D850",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0D880",
+ "label": "ghidra_BFC0D880",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0D890",
+ "label": "ghidra_BFC0D890",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0D8A0",
+ "label": "ghidra_BFC0D8A0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0D8B0",
+ "label": "ghidra_BFC0D8B0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0D8C0",
+ "label": "ghidra_BFC0D8C0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0D8D0",
+ "label": "ghidra_BFC0D8D0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0D950",
+ "label": "ghidra_BFC0D950",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0D960",
+ "label": "ghidra_BFC0D960",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0D970",
+ "label": "ghidra_BFC0D970",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0D980",
+ "label": "ghidra_BFC0D980",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0D990",
+ "label": "ghidra_BFC0D990",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0D9A0",
+ "label": "ghidra_BFC0D9A0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0D9B0",
+ "label": "ghidra_BFC0D9B0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0D9C0",
+ "label": "ghidra_BFC0D9C0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0D9D0",
+ "label": "ghidra_BFC0D9D0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0D9E0",
+ "label": "ghidra_BFC0D9E0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0D9F0",
+ "label": "ghidra_BFC0D9F0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0DA00",
+ "label": "ghidra_BFC0DA00",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0DA10",
+ "label": "ghidra_BFC0DA10",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0DA20",
+ "label": "ghidra_BFC0DA20",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0DA30",
+ "label": "ghidra_BFC0DA30",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0DA40",
+ "label": "ghidra_BFC0DA40",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0DA50",
+ "label": "ghidra_BFC0DA50",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0DA60",
+ "label": "ghidra_BFC0DA60",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0DAA0",
+ "label": "ghidra_BFC0DAA0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0DAB0",
+ "label": "ghidra_BFC0DAB0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0DAC0",
+ "label": "ghidra_BFC0DAC0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0DAD0",
+ "label": "ghidra_BFC0DAD0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0DAE0",
+ "label": "ghidra_BFC0DAE0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0DAF0",
+ "label": "ghidra_BFC0DAF0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0DB00",
+ "label": "ghidra_BFC0DB00",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0DB10",
+ "label": "ghidra_BFC0DB10",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0DB20",
+ "label": "ghidra_BFC0DB20",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0DB30",
+ "label": "ghidra_BFC0DB30",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0DB40",
+ "label": "ghidra_BFC0DB40",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0DB50",
+ "label": "ghidra_BFC0DB50",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0DB60",
+ "label": "ghidra_BFC0DB60",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0DB70",
+ "label": "ghidra_BFC0DB70",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0DB80",
+ "label": "ghidra_BFC0DB80",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0DB90",
+ "label": "ghidra_BFC0DB90",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0DBA0",
+ "label": "ghidra_BFC0DBA0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0DBB0",
+ "label": "ghidra_BFC0DBB0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0DBC0",
+ "label": "ghidra_BFC0DBC0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0DBD0",
+ "label": "ghidra_BFC0DBD0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0DBE0",
+ "label": "ghidra_BFC0DBE0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0DBF0",
+ "label": "ghidra_BFC0DBF0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0DC00",
+ "label": "ghidra_BFC0DC00",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0DC10",
+ "label": "ghidra_BFC0DC10",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0DC20",
+ "label": "ghidra_BFC0DC20",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0DC30",
+ "label": "ghidra_BFC0DC30",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0DC40",
+ "label": "ghidra_BFC0DC40",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC0DC50",
+ "label": "ghidra_BFC0DC50",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC10C74",
+ "label": "ghidra_BFC10C74",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC11008",
+ "label": "ghidra_BFC11008",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC11294",
+ "label": "ghidra_BFC11294",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC112F4",
+ "label": "ghidra_BFC112F4",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC11358",
+ "label": "ghidra_BFC11358",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC113BC",
+ "label": "ghidra_BFC113BC",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC11420",
+ "label": "ghidra_BFC11420",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC11458",
+ "label": "ghidra_BFC11458",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC11490",
+ "label": "ghidra_BFC11490",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC114C8",
+ "label": "ghidra_BFC114C8",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC11500",
+ "label": "ghidra_BFC11500",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC11620",
+ "label": "ghidra_BFC11620",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC1188C",
+ "label": "ghidra_BFC1188C",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC11B18",
+ "label": "ghidra_BFC11B18",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC11C8C",
+ "label": "ghidra_BFC11C8C",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC11F58",
+ "label": "ghidra_BFC11F58",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC122C0",
+ "label": "ghidra_BFC122C0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC12370",
+ "label": "ghidra_BFC12370",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC12408",
+ "label": "ghidra_BFC12408",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC12458",
+ "label": "ghidra_BFC12458",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC12564",
+ "label": "ghidra_BFC12564",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC12628",
+ "label": "ghidra_BFC12628",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC12794",
+ "label": "ghidra_BFC12794",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC12900",
+ "label": "ghidra_BFC12900",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC12998",
+ "label": "ghidra_BFC12998",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC129FC",
+ "label": "ghidra_BFC129FC",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC12AC8",
+ "label": "ghidra_BFC12AC8",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC12B00",
+ "label": "ghidra_BFC12B00",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC12B60",
+ "label": "ghidra_BFC12B60",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC12C08",
+ "label": "ghidra_BFC12C08",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC12CE8",
+ "label": "ghidra_BFC12CE8",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC12F6C",
+ "label": "ghidra_BFC12F6C",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC12FB4",
+ "label": "ghidra_BFC12FB4",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC13070",
+ "label": "ghidra_BFC13070",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC130AC",
+ "label": "ghidra_BFC130AC",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC13178",
+ "label": "ghidra_BFC13178",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC13244",
+ "label": "ghidra_BFC13244",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC13310",
+ "label": "ghidra_BFC13310",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC133D8",
+ "label": "ghidra_BFC133D8",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC134A4",
+ "label": "ghidra_BFC134A4",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC135AC",
+ "label": "ghidra_BFC135AC",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC137E0",
+ "label": "ghidra_BFC137E0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC13980",
+ "label": "ghidra_BFC13980",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC13AF0",
+ "label": "ghidra_BFC13AF0",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC13B24",
+ "label": "ghidra_BFC13B24",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC13B6C",
+ "label": "ghidra_BFC13B6C",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC13C34",
+ "label": "ghidra_BFC13C34",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC13C64",
+ "label": "ghidra_BFC13C64",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC13E04",
+ "label": "ghidra_BFC13E04",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC13E60",
+ "label": "ghidra_BFC13E60",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC13EE8",
+ "label": "ghidra_BFC13EE8",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC13F98",
+ "label": "ghidra_BFC13F98",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC144BC",
+ "label": "ghidra_BFC144BC",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC145C8",
+ "label": "ghidra_BFC145C8",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC14690",
+ "label": "ghidra_BFC14690",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC14710",
+ "label": "ghidra_BFC14710",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC14770",
+ "label": "ghidra_BFC14770",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC147F4",
+ "label": "ghidra_BFC147F4",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC14A90",
+ "label": "ghidra_BFC14A90",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC14B00",
+ "label": "ghidra_BFC14B00",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC14CF4",
+ "label": "ghidra_BFC14CF4",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC158A8",
+ "label": "ghidra_BFC158A8",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC15B9C",
+ "label": "ghidra_BFC15B9C",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC16170",
+ "label": "ghidra_BFC16170",
+ "rationale": "Ghidra function start"
+ },
+ {
+ "address": "0xBFC11840",
+ "label": "dmiss_BFC11840",
+ "rationale": "dispatch miss: KP2 function, indirect call"
+ },
+ {
+ "address": "0xBFC11524",
+ "label": "dmiss_BFC11524",
+ "rationale": "dispatch miss: KP2 secondary entry point for exception handler, mid-function split of func_00001A00, called during VBlank"
+ },
+ {
+ "address": "0xBFC11610",
+ "label": "dmiss_BFC11610",
+ "rationale": "dispatch miss: KP2 function, tail-call target from 0xBFC11524 exception handler path"
+ },
+ {
+ "address": "0xBFC14A54",
+ "label": "dmiss_BFC14A54",
+ "rationale": "dispatch miss (Beetle wtrace, docs/CASE_A_AOT_GAP.md): chain BUSY-writer at RAM 0x4F54; alias rule RAM=ROM-0xBFC10000+0x500. Reached via JAL inside FUN_bfc147f4; never emitted as standalone entry."
+ },
+ {
+ "address": "0xBFC158D0",
+ "label": "dmiss_BFC158D0",
+ "rationale": "dispatch miss (Beetle wtrace, docs/CASE_A_AOT_GAP.md): chain init slot-0 BUSY writer at RAM 0x5DD0; alias RAM=ROM-0xBFC10000+0x500. Inside FUN_bfc158a8 body."
+ },
+ {
+ "address": "0xBFC158D8",
+ "label": "dmiss_BFC158D8",
+ "rationale": "dispatch miss (Beetle wtrace, docs/CASE_A_AOT_GAP.md): chain init slot-1 BUSY writer at RAM 0x5DD8; alias RAM=ROM-0xBFC10000+0x500. Inside FUN_bfc158a8 body."
+ },
+ {
+ "address": "0xBFC159F4",
+ "label": "dmiss_BFC159F4",
+ "rationale": "dispatch miss (Beetle wtrace, docs/CASE_A_AOT_GAP.md): chain step handler that writes 0x02 OPEN to [0x7568+slot] at RAM 0x5EF4. Indirect-jr target from RAM 0x6c98 jump table; alias RAM=ROM-0xBFC10000+0x500. KEY entry \u00e2\u20ac\u201d without this the gate never clears."
+ },
+ {
+ "address": "0xBFC15AA8",
+ "label": "dmiss_BFC15AA8",
+ "rationale": "dispatch miss (Beetle wtrace, docs/CASE_A_AOT_GAP.md): chain step handler that writes 0x04 OPEN to [0x7568+slot] at RAM 0x5FA8. Indirect-jr target from RAM 0x6c98 jump table; alias RAM=ROM-0xBFC10000+0x500. KEY entry \u00e2\u20ac\u201d paired with 0xBFC159F4."
+ },
+ {
+ "address": "0xBFC04910",
+ "label": "dmiss_BFC04910",
+ "rationale": "Tomba boot dispatch miss after licensed PlayStation screen. Verified in Ghidra/runtime disassembly as a standalone BIOS init routine between 0xBFC048D0 and 0xBFC04ABC, with return at 0xBFC04A98 and table data reference from 0xBFC04554."
+ },
+ {
+ "address": "0xBFC04850",
+ "label": "dmiss_BFC04850",
+ "rationale": "Tomba boot dispatch miss after seeding 0xBFC04910. Verified in Ghidra/runtime disassembly as a standalone BIOS callback-list init routine between 0xBFC0472C and 0xBFC048D0, with return at 0xBFC048C8 and table data reference from 0xBFC04588."
+ },
+ {
+ "address": "0xBFC079F8",
+ "label": "dmiss_BFC079F8",
+ "rationale": "Tomba boot dispatch miss after game callback coverage fixes. Verified in Ghidra as a tiny BIOS table leaf routine (sw a0,0(sp); jr ra; clear v0) referenced by data at 0xBFC04484 and 0xBFC0E30C."
+ },
+ {
+ "address": "0xBFC15F1C",
+ "label": "dmiss_BFC15F1C",
+ "rationale": "dirty_ram hot: SIO/PAD interrupt handler entry point (RAM 0x641C), called via jalr. 10852+ hits/frame across internal branch targets (0x641C, 0x6430, 0x6444, 0x6458, 0x650C, 0x6514, 0x658C, 0x6594). Seeding entry only; CFG discovers internal labels."
+ },
+ {
+ "address": "0xBFC0193C",
+ "label": "cache_flush_continuation",
+ "rationale": "Jump target of cache-flush jr k0 at BFC01938"
+ },
+ {
+ "address": "0xBFC01E24",
+ "label": "SysInitMemory",
+ "rationale": "Dispatch miss during boot \u00e2\u20ac\u201d kernel init function"
+ },
+ {
+ "address": "0xBFC02200",
+ "label": "jal_BFC02200",
+ "rationale": "JAL target from code region"
+ },
+ {
+ "address": "0xBFC02918",
+ "label": "card_copy_dispatch_miss_2026_05_05",
+ "rationale": "Runtime dispatch miss: 8 hits during YES/NO COPY confirm execution. BANDAID per user permission."
+ },
+ {
+ "address": "0xBFC03FC0",
+ "label": "jal_BFC03FC0",
+ "rationale": "JAL target from code region"
+ },
+ {
+ "address": "0xBFC06FDC",
+ "label": "indirect_BFC06FDC",
+ "rationale": "Reached via indirect dispatch during boot"
+ },
+ {
+ "address": "0xBFC09890",
+ "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 \u00e2\u20ac\u201d 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": "0xBFC0BA8C",
+ "label": "bios_BA8C",
+ "rationale": "BIOS boot code function. Dispatch miss from shell input path."
+ },
+ {
+ "address": "0xBFC0C1DC",
+ "label": "bios_C1DC",
+ "rationale": "BIOS boot code function. Dispatch miss from shell input path."
+ },
+ {
+ "address": "0xBFC0DA70",
+ "label": "jal_BFC0DA70",
+ "rationale": "JAL target from code region"
+ },
+ {
+ "address": "0xBFC0DA80",
+ "label": "jal_BFC0DA80",
+ "rationale": "JAL target from code region"
+ },
+ {
+ "address": "0xBFC0DA90",
+ "label": "jal_BFC0DA90",
+ "rationale": "JAL target from code region"
+ },
+ {
+ "address": "0xBFC10000",
+ "label": "kernel_trampoline_0",
+ "rationale": "Trampoline to FileOpen (0x598)"
+ },
+ {
+ "address": "0xBFC10010",
+ "label": "kernel_trampoline_1",
+ "rationale": "Trampoline to FileRead (0x5C4)"
+ },
+ {
+ "address": "0xBFC10020",
+ "label": "kernel_trampoline_2",
+ "rationale": "Trampoline to FileClose (0x5E0)"
+ },
+ {
+ "address": "0xBFC10030",
+ "label": "kernel_trampoline_3",
+ "rationale": "Trampoline to 0x600"
+ },
+ {
+ "address": "0xBFC10040",
+ "label": "kernel_memcpy",
+ "rationale": "ROM-to-RAM and RAM clear routine"
+ },
+ {
+ "address": "0xBFC10098",
+ "label": "kernel_bzero",
+ "rationale": "RAM clear routine (to 0xBFC100BC)"
+ },
+ {
+ "address": "0xBFC100C4",
+ "label": "a0_vector_dispatch",
+ "rationale": "A0 function table dispatch"
+ },
+ {
+ "address": "0xBFC100E0",
+ "label": "b0_vector_dispatch",
+ "rationale": "B0 function table dispatch"
+ },
+ {
+ "address": "0xBFC10100",
+ "label": "c0_vector_dispatch",
+ "rationale": "C0 function table dispatch"
+ },
+ {
+ "address": "0xBFC10120",
+ "label": "kernel_get_cause",
+ "rationale": "mfc0 v0,Cause; jr ra"
+ },
+ {
+ "address": "0xBFC10130",
+ "label": "kernel_get_sr",
+ "rationale": "mfc0 v0,SR; jr ra"
+ },
+ {
+ "address": "0xBFC10140",
+ "label": "kernel_set_sr",
+ "rationale": "mtc0 a0,SR; jr ra"
+ },
+ {
+ "address": "0xBFC10150",
+ "label": "kernel_syscall",
+ "rationale": "syscall; jr ra"
+ },
+ {
+ "address": "0xBFC1015C",
+ "label": "kernel_get_c0_table",
+ "rationale": "Returns C0 table pointer"
+ },
+ {
+ "address": "0xBFC10168",
+ "label": "kernel_get_b0_table",
+ "rationale": "Returns B0 table pointer"
+ },
+ {
+ "address": "0xBFC10174",
+ "label": "kernel_BFC10174",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC10780",
+ "label": "rmiss_BFC10780",
+ "rationale": "runtime dispatch miss"
+ },
+ {
+ "address": "0xBFC109A0",
+ "label": "kernel_BFC109A0",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC109B0",
+ "label": "kernel_BFC109B0",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC10A20",
+ "label": "kernel_BFC10A20",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC10A2C",
+ "label": "kernel_BFC10A2C",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC10A40",
+ "label": "kernel_BFC10A40",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC10B30",
+ "label": "rmiss_BFC10B30",
+ "rationale": "runtime dispatch miss"
+ },
+ {
+ "address": "0xBFC10BB8",
+ "label": "kernel_BFC10BB8",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC10BE0",
+ "label": "kernel_BFC10BE0",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC10BE4",
+ "label": "rmiss_BFC10BE4",
+ "rationale": "runtime dispatch miss"
+ },
+ {
+ "address": "0xBFC10C3C",
+ "label": "kernel_BFC10C3C",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC10F08",
+ "label": "kernel_BFC10F08",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC10F20",
+ "label": "kernel_BFC10F20",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC10F44",
+ "label": "kernel_BFC10F44",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC110D8",
+ "label": "kernel_BFC110D8",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC110F8",
+ "label": "kernel_BFC110F8",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC11114",
+ "label": "kernel_BFC11114",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC111AC",
+ "label": "kernel_BFC111AC",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC111C4",
+ "label": "kernel_BFC111C4",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC111E4",
+ "label": "kernel_BFC111E4",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC1121C",
+ "label": "kernel_BFC1121C",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC11228",
+ "label": "kernel_BFC11228",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC1125C",
+ "label": "kernel_BFC1125C",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC11274",
+ "label": "kernel_BFC11274",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC1152C",
+ "label": "rmiss_BFC1152C",
+ "rationale": "runtime dispatch miss"
+ },
+ {
+ "address": "0xBFC11560",
+ "label": "rmiss_BFC11560",
+ "rationale": "runtime dispatch miss"
+ },
+ {
+ "address": "0xBFC115E8",
+ "label": "rmiss_BFC115E8",
+ "rationale": "runtime dispatch miss"
+ },
+ {
+ "address": "0xBFC115FC",
+ "label": "rmiss_BFC115FC",
+ "rationale": "runtime dispatch miss"
+ },
+ {
+ "address": "0xBFC11644",
+ "label": "kernel_BFC11644",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC1175C",
+ "label": "kernel_BFC1175C",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC11800",
+ "label": "kernel_BFC11800",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC11870",
+ "label": "kernel_BFC11870",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC1191C",
+ "label": "kernel_BFC1191C",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC11944",
+ "label": "kernel_BFC11944",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC11980",
+ "label": "kernel_BFC11980",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC11998",
+ "label": "kernel_BFC11998",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC119C8",
+ "label": "kernel_BFC119C8",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC11A04",
+ "label": "kernel_BFC11A04",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC11A10",
+ "label": "kernel_BFC11A10",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC11A4C",
+ "label": "kernel_BFC11A4C",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC11A88",
+ "label": "kernel_BFC11A88",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC11AFC",
+ "label": "kernel_BFC11AFC",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC11BA8",
+ "label": "kernel_BFC11BA8",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC11BD4",
+ "label": "kernel_BFC11BD4",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC11C14",
+ "label": "kernel_BFC11C14",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC11C24",
+ "label": "kernel_BFC11C24",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC11C50",
+ "label": "kernel_BFC11C50",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC11C68",
+ "label": "kernel_BFC11C68",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC12224",
+ "label": "kernel_BFC12224",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC1228C",
+ "label": "kernel_BFC1228C",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC122A0",
+ "label": "kernel_BFC122A0",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC129EC",
+ "label": "kernel_BFC129EC",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC12BC8",
+ "label": "kernel_BFC12BC8",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC12BE0",
+ "label": "kernel_BFC12BE0",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC12BE4",
+ "label": "rmiss_BFC12BE4",
+ "rationale": "runtime dispatch miss"
+ },
+ {
+ "address": "0xBFC12EA4",
+ "label": "kernel_BFC12EA4",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC1302C",
+ "label": "kernel_BFC1302C",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC130A4",
+ "label": "kernel_BFC130A4",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC13570",
+ "label": "kernel_BFC13570",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC1372C",
+ "label": "kernel_BFC1372C",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC1376C",
+ "label": "rmiss_BFC1376C",
+ "rationale": "runtime dispatch miss"
+ },
+ {
+ "address": "0xBFC137B4",
+ "label": "indirect_BFC137B4",
+ "rationale": "Reached via indirect dispatch during boot (kernel relocated)"
+ },
+ {
+ "address": "0xBFC137BC",
+ "label": "rmiss_BFC137BC",
+ "rationale": "runtime dispatch miss"
+ },
+ {
+ "address": "0xBFC137D0",
+ "label": "kernel_ram_function",
+ "rationale": "Dispatch miss at 0xBFC137D0 (phys 0x00003CD0) \u00e2\u20ac\u201d 3 misses. Kernel RAM function."
+ },
+ {
+ "address": "0xBFC138AC",
+ "label": "kernel_BFC138AC",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC1395C",
+ "label": "kernel_BFC1395C",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC13968",
+ "label": "kernel_BFC13968",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC13A04",
+ "label": "kernel_BFC13A04",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC13ED0",
+ "label": "kernel_BFC13ED0",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC13F5C",
+ "label": "kernel_BFC13F5C",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC1454C",
+ "label": "kernel_BFC1454C",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC14588",
+ "label": "kernel_BFC14588",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC14594",
+ "label": "kernel_BFC14594",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC14670",
+ "label": "kernel_BFC14670",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC14754",
+ "label": "kernel_BFC14754",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC14764",
+ "label": "kernel_BFC14764",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC1483C",
+ "label": "kernel_BFC1483C",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC1484C",
+ "label": "kernel_BFC1484C",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC14864",
+ "label": "kernel_BFC14864",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC1486C",
+ "label": "kernel_BFC1486C",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC15188",
+ "label": "kernel_BFC15188",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC151E8",
+ "label": "kernel_56E8",
+ "rationale": "Kernel function at RAM 0x56E8. Dispatch miss from shell input path."
+ },
+ {
+ "address": "0xBFC15268",
+ "label": "kernel_5768",
+ "rationale": "Kernel function at RAM 0x5768. Cascade from input path seeds."
+ },
+ {
+ "address": "0xBFC1529C",
+ "label": "kernel_579C",
+ "rationale": "Kernel function at RAM 0x579C. Cascade from input path."
+ },
+ {
+ "address": "0xBFC15334",
+ "label": "kernel_5834",
+ "rationale": "Kernel function at RAM 0x5834. Cascade."
+ },
+ {
+ "address": "0xBFC15370",
+ "label": "kernel_5870",
+ "rationale": "Kernel (CARD driver) function. Cascade."
+ },
+ {
+ "address": "0xBFC153B4",
+ "label": "kernel_58B4",
+ "rationale": "Kernel (CARD driver) function at RAM 0x58B4. Cascade."
+ },
+ {
+ "address": "0xBFC153E8",
+ "label": "kernel_58E8",
+ "rationale": "Kernel (CARD driver) function at RAM 0x58E8. Cascade."
+ },
+ {
+ "address": "0xBFC15418",
+ "label": "kernel_5918",
+ "rationale": "Kernel (CARD driver) leaf function at RAM 0x5918. Cascade."
+ },
+ {
+ "address": "0xBFC15454",
+ "label": "kernel_5954",
+ "rationale": "CARD driver cascade."
+ },
+ {
+ "address": "0xBFC15490",
+ "label": "kernel_5990",
+ "rationale": "CARD driver cascade."
+ },
+ {
+ "address": "0xBFC154CC",
+ "label": "kernel_59CC",
+ "rationale": "CARD driver cascade (predicted)."
+ },
+ {
+ "address": "0xBFC15500",
+ "label": "kernel_5A00",
+ "rationale": "CARD driver cascade."
+ },
+ {
+ "address": "0xBFC15508",
+ "label": "kernel_5A08",
+ "rationale": "CARD driver cascade (predicted)."
+ },
+ {
+ "address": "0xBFC15544",
+ "label": "kernel_5A44",
+ "rationale": "CARD driver cascade (predicted)."
+ },
+ {
+ "address": "0xBFC15558",
+ "label": "kernel_5A58",
+ "rationale": "CARD read chain state 12 (data-byte-store): SIO_RX -> a1[0x7F], XOR-updates checksum at 0x7560+slot*4, returns v0=0. Targeted by jump table at RAM 0x6c98 entry 12."
+ },
+ {
+ "address": "0xBFC15580",
+ "label": "kernel_5A80",
+ "rationale": "CARD driver cascade (predicted)."
+ },
+ {
+ "address": "0xBFC155B0",
+ "label": "kernel_5AB0",
+ "rationale": "CARD read chain state 13 (data-loop completion): reads byte vs checksum at 0x7560+slot*4, polls SIO_STAT for RX_RDY, reads END byte; v0=1 if 'G' else v0=-1. Targeted by jump table at RAM 0x6c98 entry 13."
+ },
+ {
+ "address": "0xBFC155BC",
+ "label": "kernel_5ABC",
+ "rationale": "CARD driver cascade (predicted)."
+ },
+ {
+ "address": "0xBFC155F8",
+ "label": "kernel_5AF8",
+ "rationale": "CARD driver cascade (predicted)."
+ },
+ {
+ "address": "0xBFC15634",
+ "label": "kernel_5B34",
+ "rationale": "CARD driver cascade (predicted)."
+ },
+ {
+ "address": "0xBFC15650",
+ "label": "kernel_5B50",
+ "rationale": "Kernel function. Cascade from input path."
+ },
+ {
+ "address": "0xBFC15654",
+ "label": "kernel_5B54",
+ "rationale": "Kernel function. Cascade from input path."
+ },
+ {
+ "address": "0xBFC15664",
+ "label": "kernel_BFC15664",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC156A4",
+ "label": "kernel_5BA4",
+ "rationale": "Kernel function at RAM 0x5BA4. Dispatch miss from shell input path."
+ },
+ {
+ "address": "0xBFC15724",
+ "label": "kernel_5C24",
+ "rationale": "Kernel function at RAM 0x5C24. Cascade from input path seeds."
+ },
+ {
+ "address": "0xBFC15758",
+ "label": "kernel_5C58",
+ "rationale": "Kernel function at RAM 0x5C58. Cascade."
+ },
+ {
+ "address": "0xBFC15848",
+ "label": "kernel_5D48",
+ "rationale": "Kernel (CARD driver) function. Cascade."
+ },
+ {
+ "address": "0xBFC15894",
+ "label": "kernel_5D94",
+ "rationale": "Kernel function. Cascade from input path."
+ },
+ {
+ "address": "0xBFC15898",
+ "label": "kernel_5D98",
+ "rationale": "Kernel (CARD driver) function. Cascade."
+ },
+ {
+ "address": "0xBFC15930",
+ "label": "kernel_BFC15930",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC15950",
+ "label": "kernel_BFC15950",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC15998",
+ "label": "kernel_BFC15998",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC15A04",
+ "label": "kernel_BFC15A04",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC15A4C",
+ "label": "kernel_BFC15A4C",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC15AB8",
+ "label": "kernel_BFC15AB8",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC15AF0",
+ "label": "kernel_BFC15AF0",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC15B5C",
+ "label": "kernel_BFC15B5C",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC15B70",
+ "label": "kernel_BFC15B70",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC15CC4",
+ "label": "kernel_BFC15CC4",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC15E90",
+ "label": "kernel_BFC15E90",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC15EBC",
+ "label": "kernel_BFC15EBC",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC15F08",
+ "label": "kernel_BFC15F08",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC1609C",
+ "label": "kernel_BFC1609C",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC16690",
+ "label": "kernel_BFC16690",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC166A0",
+ "label": "kernel_BFC166A0",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC166B0",
+ "label": "kernel_BFC166B0",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC166C0",
+ "label": "kernel_BFC166C0",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC166D0",
+ "label": "kernel_BFC166D0",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC166E0",
+ "label": "kernel_BFC166E0",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC166F0",
+ "label": "kernel_BFC166F0",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC16700",
+ "label": "kernel_BFC16700",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC16710",
+ "label": "kernel_BFC16710",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC16720",
+ "label": "kernel_BFC16720",
+ "rationale": "Kernel ROM function (auto-discovered from tables/boundaries)"
+ },
+ {
+ "address": "0xBFC02934",
+ "rationale": "kernel jump-table target: A0:0F (unresolved (see psx-spx A0 table)) \u2014 ROM-resident entry of the post-init A0/B0/C0 tables (read from live kernel RAM 0x200/0x874/0x674); reachable only through the vector table, invisible to transitive discovery. Class found via MMX6 card-check: A0:2B memset dispatch-missed silently, killing the save-scan (2026-07-02)."
+ },
+ {
+ "address": "0xBFC03200",
+ "rationale": "kernel jump-table target: A0:16 (strncat) \u2014 ROM-resident entry of the post-init A0/B0/C0 tables (read from live kernel RAM 0x200/0x874/0x674); reachable only through the vector table, invisible to transitive discovery. Class found via MMX6 card-check: A0:2B memset dispatch-missed silently, killing the save-scan (2026-07-02)."
+ },
+ {
+ "address": "0xBFC034D0",
+ "rationale": "kernel jump-table target: A0:1C (atob helper) \u2014 ROM-resident entry of the post-init A0/B0/C0 tables (read from live kernel RAM 0x200/0x874/0x674); reachable only through the vector table, invisible to transitive discovery. Class found via MMX6 card-check: A0:2B memset dispatch-missed silently, killing the save-scan (2026-07-02)."
+ },
+ {
+ "address": "0xBFC03514",
+ "rationale": "kernel jump-table target: A0:1D (strtol-family) \u2014 ROM-resident entry of the post-init A0/B0/C0 tables (read from live kernel RAM 0x200/0x874/0x674); reachable only through the vector table, invisible to transitive discovery. Class found via MMX6 card-check: A0:2B memset dispatch-missed silently, killing the save-scan (2026-07-02)."
+ },
+ {
+ "address": "0xBFC0357C",
+ "rationale": "kernel jump-table target: A0:1E (strtol-family) \u2014 ROM-resident entry of the post-init A0/B0/C0 tables (read from live kernel RAM 0x200/0x874/0x674); reachable only through the vector table, invisible to transitive discovery. Class found via MMX6 card-check: A0:2B memset dispatch-missed silently, killing the save-scan (2026-07-02)."
+ },
+ {
+ "address": "0xBFC035C0",
+ "rationale": "kernel jump-table target: A0:1F (abs) \u2014 ROM-resident entry of the post-init A0/B0/C0 tables (read from live kernel RAM 0x200/0x874/0x674); reachable only through the vector table, invisible to transitive discovery. Class found via MMX6 card-check: A0:2B memset dispatch-missed silently, killing the save-scan (2026-07-02)."
+ },
+ {
+ "address": "0xBFC03628",
+ "rationale": "kernel jump-table target: A0:20 (labs) \u2014 ROM-resident entry of the post-init A0/B0/C0 tables (read from live kernel RAM 0x200/0x874/0x674); reachable only through the vector table, invisible to transitive discovery. Class found via MMX6 card-check: A0:2B memset dispatch-missed silently, killing the save-scan (2026-07-02)."
+ },
+ {
+ "address": "0xBFC03694",
+ "rationale": "kernel jump-table target: A0:21 (atoi) \u2014 ROM-resident entry of the post-init A0/B0/C0 tables (read from live kernel RAM 0x200/0x874/0x674); reachable only through the vector table, invisible to transitive discovery. Class found via MMX6 card-check: A0:2B memset dispatch-missed silently, killing the save-scan (2026-07-02)."
+ },
+ {
+ "address": "0xBFC036FC",
+ "rationale": "kernel jump-table target: A0:22 (atol) \u2014 ROM-resident entry of the post-init A0/B0/C0 tables (read from live kernel RAM 0x200/0x874/0x674); reachable only through the vector table, invisible to transitive discovery. Class found via MMX6 card-check: A0:2B memset dispatch-missed silently, killing the save-scan (2026-07-02)."
+ },
+ {
+ "address": "0xBFC03894",
+ "rationale": "kernel jump-table target: A0:24 (strtol-family) \u2014 ROM-resident entry of the post-init A0/B0/C0 tables (read from live kernel RAM 0x200/0x874/0x674); reachable only through the vector table, invisible to transitive discovery. Class found via MMX6 card-check: A0:2B memset dispatch-missed silently, killing the save-scan (2026-07-02)."
+ },
+ {
+ "address": "0xBFC01B08",
+ "rationale": "kernel jump-table target: A0:29 (bcmp) \u2014 ROM-resident entry of the post-init A0/B0/C0 tables (read from live kernel RAM 0x200/0x874/0x674); reachable only through the vector table, invisible to transitive discovery. Class found via MMX6 card-check: A0:2B memset dispatch-missed silently, killing the save-scan (2026-07-02)."
+ },
+ {
+ "address": "0xBFC02B8C",
+ "rationale": "kernel jump-table target: A0:2B (memset) \u2014 ROM-resident entry of the post-init A0/B0/C0 tables (read from live kernel RAM 0x200/0x874/0x674); reachable only through the vector table, invisible to transitive discovery. Class found via MMX6 card-check: A0:2B memset dispatch-missed silently, killing the save-scan (2026-07-02)."
+ },
+ {
+ "address": "0xBFC02BC8",
+ "rationale": "kernel jump-table target: A0:2C (memmove) \u2014 ROM-resident entry of the post-init A0/B0/C0 tables (read from live kernel RAM 0x200/0x874/0x674); reachable only through the vector table, invisible to transitive discovery. Class found via MMX6 card-check: A0:2B memset dispatch-missed silently, killing the save-scan (2026-07-02)."
+ },
+ {
+ "address": "0xBFC02C50",
+ "rationale": "kernel jump-table target: A0:2D (memcmp) \u2014 ROM-resident entry of the post-init A0/B0/C0 tables (read from live kernel RAM 0x200/0x874/0x674); reachable only through the vector table, invisible to transitive discovery. Class found via MMX6 card-check: A0:2B memset dispatch-missed silently, killing the save-scan (2026-07-02)."
+ },
+ {
+ "address": "0xBFC02230",
+ "rationale": "kernel jump-table target: A0:30 (rand-family) \u2014 ROM-resident entry of the post-init A0/B0/C0 tables (read from live kernel RAM 0x200/0x874/0x674); reachable only through the vector table, invisible to transitive discovery. Class found via MMX6 card-check: A0:2B memset dispatch-missed silently, killing the save-scan (2026-07-02)."
+ },
+ {
+ "address": "0xBFC021A0",
+ "rationale": "kernel jump-table target: A0:37 (rand-family) \u2014 ROM-resident entry of the post-init A0/B0/C0 tables (read from live kernel RAM 0x200/0x874/0x674); reachable only through the vector table, invisible to transitive discovery. Class found via MMX6 card-check: A0:2B memset dispatch-missed silently, killing the save-scan (2026-07-02)."
+ },
+ {
+ "address": "0xBFC0D8E0",
+ "rationale": "kernel jump-table target: A0:40 (SystemErrorUnresolvedException) \u2014 ROM-resident entry of the post-init A0/B0/C0 tables (read from live kernel RAM 0x200/0x874/0x674); reachable only through the vector table, invisible to transitive discovery. Class found via MMX6 card-check: A0:2B memset dispatch-missed silently, killing the save-scan (2026-07-02)."
+ },
+ {
+ "address": "0xBFC04120",
+ "rationale": "kernel jump-table target: A0:4D (ChangeClearRCnt-adjacent) \u2014 ROM-resident entry of the post-init A0/B0/C0 tables (read from live kernel RAM 0x200/0x874/0x674); reachable only through the vector table, invisible to transitive discovery. Class found via MMX6 card-check: A0:2B memset dispatch-missed silently, killing the save-scan (2026-07-02)."
+ },
+ {
+ "address": "0xBFC0D8F0",
+ "rationale": "kernel jump-table target: A0:4F (stub/ret) \u2014 ROM-resident entry of the post-init A0/B0/C0 tables (read from live kernel RAM 0x200/0x874/0x674); reachable only through the vector table, invisible to transitive discovery. Class found via MMX6 card-check: A0:2B memset dispatch-missed silently, killing the save-scan (2026-07-02)."
+ },
+ {
+ "address": "0xBFC0D900",
+ "rationale": "kernel jump-table target: A0:50 (stub/ret) \u2014 ROM-resident entry of the post-init A0/B0/C0 tables (read from live kernel RAM 0x200/0x874/0x674); reachable only through the vector table, invisible to transitive discovery. Class found via MMX6 card-check: A0:2B memset dispatch-missed silently, killing the save-scan (2026-07-02)."
+ },
+ {
+ "address": "0xBFC0D910",
+ "rationale": "kernel jump-table target: A0:52 (stub/ret) \u2014 ROM-resident entry of the post-init A0/B0/C0 tables (read from live kernel RAM 0x200/0x874/0x674); reachable only through the vector table, invisible to transitive discovery. Class found via MMX6 card-check: A0:2B memset dispatch-missed silently, killing the save-scan (2026-07-02)."
+ },
+ {
+ "address": "0xBFC0D920",
+ "rationale": "kernel jump-table target: A0:53 (stub/ret) \u2014 ROM-resident entry of the post-init A0/B0/C0 tables (read from live kernel RAM 0x200/0x874/0x674); reachable only through the vector table, invisible to transitive discovery. Class found via MMX6 card-check: A0:2B memset dispatch-missed silently, killing the save-scan (2026-07-02)."
+ },
+ {
+ "address": "0xBFC0B0C4",
+ "rationale": "kernel jump-table target: A0:6F (card/dev service) \u2014 ROM-resident entry of the post-init A0/B0/C0 tables (read from live kernel RAM 0x200/0x874/0x674); reachable only through the vector table, invisible to transitive discovery. Class found via MMX6 card-check: A0:2B memset dispatch-missed silently, killing the save-scan (2026-07-02)."
+ },
+ {
+ "address": "0xBFC04AA0",
+ "rationale": "kernel jump-table target: A0:94 (card-adjacent service) \u2014 ROM-resident entry of the post-init A0/B0/C0 tables (read from live kernel RAM 0x200/0x874/0x674); reachable only through the vector table, invisible to transitive discovery. Class found via MMX6 card-check: A0:2B memset dispatch-missed silently, killing the save-scan (2026-07-02)."
+ },
+ {
+ "address": "0xBFC0D930",
+ "rationale": "kernel jump-table target: A0:9A (stub/ret) \u2014 ROM-resident entry of the post-init A0/B0/C0 tables (read from live kernel RAM 0x200/0x874/0x674); reachable only through the vector table, invisible to transitive discovery. Class found via MMX6 card-check: A0:2B memset dispatch-missed silently, killing the save-scan (2026-07-02)."
+ },
+ {
+ "address": "0xBFC0D940",
+ "rationale": "kernel jump-table target: A0:9B (stub/ret) \u2014 ROM-resident entry of the post-init A0/B0/C0 tables (read from live kernel RAM 0x200/0x874/0x674); reachable only through the vector table, invisible to transitive discovery. Class found via MMX6 card-check: A0:2B memset dispatch-missed silently, killing the save-scan (2026-07-02)."
+ },
+ {
+ "address": "0xBFC06750",
+ "rationale": "kernel jump-table target: A0:9C (kernel service) \u2014 ROM-resident entry of the post-init A0/B0/C0 tables (read from live kernel RAM 0x200/0x874/0x674); reachable only through the vector table, invisible to transitive discovery. Class found via MMX6 card-check: A0:2B memset dispatch-missed silently, killing the save-scan (2026-07-02)."
+ },
+ {
+ "address": "0xBFC06728",
+ "rationale": "kernel jump-table target: A0:9D (kernel service) \u2014 ROM-resident entry of the post-init A0/B0/C0 tables (read from live kernel RAM 0x200/0x874/0x674); reachable only through the vector table, invisible to transitive discovery. Class found via MMX6 card-check: A0:2B memset dispatch-missed silently, killing the save-scan (2026-07-02)."
+ },
+ {
+ "address": "0xBFC050DC",
+ "rationale": "kernel jump-table target: A0:9E (kernel service) \u2014 ROM-resident entry of the post-init A0/B0/C0 tables (read from live kernel RAM 0x200/0x874/0x674); reachable only through the vector table, invisible to transitive discovery. Class found via MMX6 card-check: A0:2B memset dispatch-missed silently, killing the save-scan (2026-07-02)."
+ },
+ {
+ "address": "0xBFC0D4F0",
+ "rationale": "kernel jump-table target: A0:B4 (GetSystemInfo-family) \u2014 ROM-resident entry of the post-init A0/B0/C0 tables (read from live kernel RAM 0x200/0x874/0x674); reachable only through the vector table, invisible to transitive discovery. Class found via MMX6 card-check: A0:2B memset dispatch-missed silently, killing the save-scan (2026-07-02)."
+ }
+ ],
+ "excluded": [
+ {
+ "address": "0xBFC00100",
+ "label": "utlb_miss_bev1",
+ "rationale": "R3000 UTLB-miss vector; PS1 has no TLB; never executed"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/recompiler/src/bios_address_model.cpp b/recompiler/src/bios_address_model.cpp
new file mode 100644
index 00000000..d2d8cb0a
--- /dev/null
+++ b/recompiler/src/bios_address_model.cpp
@@ -0,0 +1,242 @@
+// bios_address_model.cpp — see bios_address_model.h for the design contract.
+
+#include "bios_address_model.h"
+
+#include
+
+#include "fmt/format.h"
+
+#include "config_loader.h"
+
+namespace PSXRecompV4 {
+
+// ---------------------------------------------------------------------------
+// Construction + invariants
+// ---------------------------------------------------------------------------
+
+BiosAddressModel BiosAddressModel::from_config(const BiosConfig& cfg) {
+ BiosAddressModel m;
+ m.copies_ = cfg.address_copies;
+ m.install_slots_ = cfg.install_slots;
+ m.rom_base_phys_ = cfg.load_address & 0x1FFFFFFFu;
+ m.rom_size_ = cfg.text_size;
+
+ const std::string where = cfg.config_path.string();
+
+ auto fail = [&](const std::string& msg) {
+ throw std::runtime_error(fmt::format(
+ "{}: [recompiler.address_model] {}", where, msg));
+ };
+
+ for (size_t i = 0; i < m.copies_.size(); ++i) {
+ const BiosAddrCopy& c = m.copies_[i];
+ if (c.rom_lo >= c.rom_hi)
+ fail(fmt::format("copy '{}': rom_lo >= rom_hi", c.name));
+ if ((c.rom_lo | c.rom_hi | c.ram_lo | c.runtime_base) & 3u)
+ fail(fmt::format("copy '{}': bounds must be 4-aligned", c.name));
+ if (c.rom_lo < m.rom_base_phys_ ||
+ c.rom_hi > m.rom_base_phys_ + m.rom_size_)
+ fail(fmt::format("copy '{}': ROM window outside the image "
+ "[0x{:08X},0x{:08X})",
+ c.name, m.rom_base_phys_,
+ m.rom_base_phys_ + m.rom_size_));
+ if ((c.runtime_base & 0x1FFFFFFFu) != c.ram_lo)
+ fail(fmt::format("copy '{}': runtime_base must be a vaddr of "
+ "ram_lo (phys mismatch)", c.name));
+
+ // Disjoint input windows: a ROM byte belongs to at most one copy, and
+ // a RAM byte to at most one destination ("duplicate logical function
+ // -> refuse to build", docs/RELOCATION_MANIFEST_FORMAT.md).
+ for (size_t j = 0; j < i; ++j) {
+ const BiosAddrCopy& p = m.copies_[j];
+ if (c.rom_lo < p.rom_hi && p.rom_lo < c.rom_hi)
+ fail(fmt::format("copies '{}' and '{}': ROM windows overlap",
+ p.name, c.name));
+ if (c.ram_lo < p.ram_hi() && p.ram_lo < c.ram_hi())
+ fail(fmt::format("copies '{}' and '{}': RAM windows overlap",
+ p.name, c.name));
+ }
+ }
+
+ // normalize() is emitted as SEQUENTIAL ifs with no else; that is only
+ // equivalent to first-match-wins if no copy's fold OUTPUT can land inside
+ // a later copy's fold INPUT. Check every pair, both key directions.
+ for (const BiosAddrCopy& a : m.copies_) {
+ const uint32_t out_lo = a.key_is_ram ? a.ram_lo : a.rom_lo;
+ const uint32_t out_hi = a.key_is_ram ? a.ram_hi() : a.rom_hi;
+ for (const BiosAddrCopy& b : m.copies_) {
+ if (&a == &b) continue;
+ const uint32_t in_lo = b.key_is_ram ? b.rom_lo : b.ram_lo;
+ const uint32_t in_hi = b.key_is_ram ? b.rom_hi : b.ram_hi();
+ if (out_lo < in_hi && in_lo < out_hi)
+ fail(fmt::format(
+ "copy '{}' fold output [0x{:08X},0x{:08X}) intersects "
+ "copy '{}' fold input [0x{:08X},0x{:08X}) — sequential "
+ "normalize() would double-fold",
+ a.name, out_lo, out_hi, b.name, in_lo, in_hi));
+ }
+ }
+
+ for (size_t i = 0; i < m.copies_.size(); ++i) {
+ if (m.copies_[i].kernel_bless) {
+ if (m.kbless_idx_ >= 0)
+ fail("at most one copy may set kernel_bless = true (the "
+ "runtime models a single bless window)");
+ if (!m.copies_[i].key_is_ram)
+ fail(fmt::format("copy '{}': kernel_bless requires "
+ "dispatch_key = \"ram\"", m.copies_[i].name));
+ m.kbless_idx_ = static_cast(i);
+ }
+ if (!m.copies_[i].key_is_ram && m.rom_keyed_idx_ < 0)
+ m.rom_keyed_idx_ = static_cast(i);
+ }
+
+ for (uint32_t slot : m.install_slots_) {
+ if (slot & 3u)
+ fail(fmt::format("install slot 0x{:08X} must be 4-aligned", slot));
+ }
+
+ return m;
+}
+
+// ---------------------------------------------------------------------------
+// Address mapping
+// ---------------------------------------------------------------------------
+
+uint32_t BiosAddressModel::normalize(uint32_t addr) const {
+ uint32_t phys = addr & 0x1FFFFFFFu;
+ // Sequential per-copy folds, same shape as the emitted normalize() —
+ // equivalence to first-match-wins is enforced by from_config.
+ for (const BiosAddrCopy& c : copies_) {
+ if (c.key_is_ram) {
+ if (phys >= c.rom_lo && phys < c.rom_hi)
+ phys = phys - c.rom_lo + c.ram_lo;
+ } else {
+ if (phys >= c.ram_lo && phys < c.ram_hi())
+ phys = phys - c.ram_lo + c.rom_lo;
+ }
+ }
+ return phys;
+}
+
+uint32_t BiosAddressModel::ram_alias_to_rom(uint32_t addr) const {
+ uint32_t phys = addr & 0x1FFFFFFFu;
+ for (const BiosAddrCopy& c : copies_) {
+ if (phys >= c.ram_lo && phys < c.ram_hi())
+ return (0xA0000000u | c.rom_lo) + (phys - c.ram_lo);
+ }
+ return addr;
+}
+
+uint32_t BiosAddressModel::runtime_pc(uint32_t rom_pc) const {
+ uint32_t phys = rom_pc & 0x1FFFFFFFu;
+ for (const BiosAddrCopy& c : copies_) {
+ if (phys >= c.rom_lo && phys < c.rom_hi)
+ return c.runtime_base + (phys - c.rom_lo);
+ }
+ return rom_pc;
+}
+
+uint32_t BiosAddressModel::relocate_j_target(uint32_t rom_addr, uint32_t target) const {
+ uint32_t phys = rom_addr & 0x1FFFFFFFu;
+ for (const BiosAddrCopy& c : copies_) {
+ if (phys >= c.rom_lo && phys < c.rom_hi) {
+ uint32_t runtime_addr = c.runtime_base + (phys - c.rom_lo);
+ return (runtime_addr & 0xF0000000u) | (target & 0x0FFFFFFFu);
+ }
+ }
+ return target;
+}
+
+uint32_t BiosAddressModel::rom_to_ram_phys(uint32_t rom_addr) const {
+ uint32_t phys = rom_addr & 0x1FFFFFFFu;
+ for (const BiosAddrCopy& c : copies_) {
+ if (c.key_is_ram && phys >= c.rom_lo && phys < c.rom_hi)
+ return phys - c.rom_lo + c.ram_lo;
+ }
+ return phys;
+}
+
+uint32_t BiosAddressModel::remap_relocated_j_target(uint32_t target,
+ uint32_t source_rom_addr) const {
+ uint32_t src_phys = source_rom_addr & 0x1FFFFFFFu;
+ for (const BiosAddrCopy& c : copies_) {
+ if (src_phys < c.rom_lo || src_phys >= c.rom_hi) continue;
+
+ // The 26-bit target inherits the RUNTIME region of the source.
+ uint32_t runtime_addr = c.runtime_base + (src_phys - c.rom_lo);
+ uint32_t ram_target = (runtime_addr & 0xF0000000u) | (target & 0x0FFFFFFFu);
+ uint32_t ram_phys = ram_target & 0x1FFFFFFFu;
+
+ // Fold back to a followable KSEG1 ROM address through any copy's RAM
+ // window.
+ for (const BiosAddrCopy& c2 : copies_) {
+ if (ram_phys >= c2.ram_lo && ram_phys < c2.ram_hi())
+ return (0xA0000000u | c2.rom_lo) + (ram_phys - c2.ram_lo);
+ }
+ // Target already inside the ROM image: return it in KSEG1 form.
+ if (ram_phys >= rom_base_phys_ && ram_phys < rom_base_phys_ + rom_size_)
+ return 0xA0000000u | ram_phys;
+ return target;
+ }
+ return target;
+}
+
+// ---------------------------------------------------------------------------
+// kbless / install slots / rom-keyed window accessors
+// ---------------------------------------------------------------------------
+
+uint32_t BiosAddressModel::kbless_ram_lo() const { return copies_[kbless_idx_].ram_lo; }
+uint32_t BiosAddressModel::kbless_ram_hi() const { return copies_[kbless_idx_].ram_hi(); }
+uint32_t BiosAddressModel::kbless_rom_off() const {
+ return copies_[kbless_idx_].rom_lo - rom_base_phys_;
+}
+bool BiosAddressModel::in_kbless(uint32_t norm) const {
+ return has_kbless() && norm >= kbless_ram_lo() && norm < kbless_ram_hi();
+}
+
+bool BiosAddressModel::is_install_slot(uint32_t ram_pc) const {
+ for (uint32_t slot : install_slots_) {
+ if (ram_pc == slot) return true;
+ }
+ return false;
+}
+
+uint32_t BiosAddressModel::rom_keyed_ram_lo() const {
+ return copies_[rom_keyed_idx_].ram_lo;
+}
+uint32_t BiosAddressModel::rom_keyed_ram_hi_incl() const {
+ return copies_[rom_keyed_idx_].ram_hi() - 1u;
+}
+
+// ---------------------------------------------------------------------------
+// Emitted-C text
+// ---------------------------------------------------------------------------
+
+std::string BiosAddressModel::emit_normalize_c() const {
+ std::string out;
+ out += "static uint32_t normalize(uint32_t addr) {\n";
+ out += " uint32_t phys = addr & 0x1FFFFFFFu;\n";
+ for (const BiosAddrCopy& c : copies_) {
+ if (c.key_is_ram) {
+ out += fmt::format(" /* {}: ROM 0x{:X}+ -> RAM 0x{:X}+ */\n",
+ c.name, c.rom_lo, c.ram_lo);
+ out += fmt::format(" if (phys >= 0x{:08X}u && phys <= 0x{:08X}u)\n",
+ c.rom_lo, c.rom_hi - 1u);
+ out += fmt::format(" phys = phys - 0x{:08X}u + 0x{:08X}u;\n",
+ c.rom_lo, c.ram_lo);
+ } else {
+ out += fmt::format(" /* {}: RAM 0x{:X}+ -> ROM physical 0x{:X}+ */\n",
+ c.name, c.ram_lo, c.rom_lo);
+ out += fmt::format(" if (phys >= 0x{:08X}u && phys <= 0x{:08X}u)\n",
+ c.ram_lo, c.ram_hi() - 1u);
+ out += fmt::format(" phys = phys - 0x{:08X}u + 0x{:08X}u;\n",
+ c.ram_lo, c.rom_lo);
+ }
+ }
+ out += " return phys;\n";
+ out += "}\n\n";
+ return out;
+}
+
+} // namespace PSXRecompV4
diff --git a/recompiler/src/bios_address_model.h b/recompiler/src/bios_address_model.h
new file mode 100644
index 00000000..b21bcb65
--- /dev/null
+++ b/recompiler/src/bios_address_model.h
@@ -0,0 +1,126 @@
+// bios_address_model.h — the BIOS profile's address model, as one value type.
+// ----------------------------------------------------------------------------
+// A PS1 BIOS relocates code out of ROM into RAM at boot (SCPH1001: the Kernel
+// Part 2 copy to RAM 0x500 and the shell copy to RAM 0x80030000). Before this
+// existed, those window constants were copy-pasted across ~9 sites in
+// full_function_emitter.cpp / function_discovery.cpp — including as literal C
+// text emitted into the generated dispatch — and the two files had drifted on
+// the shell window's upper bound. This class is the single source of truth,
+// built from the profile's [recompiler.address_model] tables; both the C++
+// helpers and the emitted-C text generators derive from the same table, so
+// they cannot disagree.
+//
+// A profile with NO copy entries (a BIOS that runs entirely from ROM) is a
+// valid, first-class model: normalize() degenerates to the KSEG mask, there is
+// no kernel-bless window, and every generator emits nothing.
+//
+// Every [[recompiler.address_model.copy]] entry is a CLAIM that the BIOS's
+// boot-time copy is byte-verbatim (lw/sw loop or memcpy, no patching or
+// decompression). The runtime's kernel-bless memcmp is the enforcement of
+// that claim for the kernel_bless window.
+
+#pragma once
+
+#include
+#include
+#include
+
+namespace PSXRecompV4 {
+
+struct BiosConfig;
+
+// One [[recompiler.address_model.copy]]: a boot-time bulk copy of code from
+// ROM [rom_lo, rom_hi) to RAM ram_lo+, executed by the CPU at runtime_base+.
+struct BiosAddrCopy {
+ std::string name; // comment label in emitted C, e.g. "Kernel Part 2"
+ uint32_t rom_lo = 0; // physical ROM window [lo, hi) — hi EXCLUSIVE
+ uint32_t rom_hi = 0;
+ uint32_t ram_lo = 0; // physical RAM destination of rom_lo
+ uint32_t runtime_base = 0; // vaddr the CPU executes the copy at (KUSEG/KSEG0)
+ bool key_is_ram = false; // dispatch_key = "ram": functions in this
+ // window are keyed by RAM address (normalize
+ // folds ROM->RAM). "rom": keyed by ROM
+ // (normalize folds RAM->ROM).
+ bool kernel_bless = false; // runtime may byte-verify live RAM vs ROM
+ // and run native (memory.c kbless)
+
+ uint32_t len() const { return rom_hi - rom_lo; }
+ uint32_t ram_hi() const { return ram_lo + len(); } // exclusive
+};
+
+class BiosAddressModel {
+public:
+ // Empty model: pure KSEG-mask normalization, no copies, no install slots.
+ BiosAddressModel() = default;
+
+ // Build from a loaded profile, enforcing the semantic invariants
+ // (disjoint input windows, no output/input intersection, single bless
+ // window, alignment, in-image bounds). Throws std::runtime_error with the
+ // profile path in the message on violation.
+ static BiosAddressModel from_config(const BiosConfig& cfg);
+
+ // --- C++-side address mapping (used while analyzing/emitting) ---------
+
+ // Dispatch-key normalization: KSEG strip, then per-copy key fold.
+ uint32_t normalize(uint32_t addr) const;
+
+ // RAM alias -> KSEG1 ROM virtual address (for reading jump tables etc.
+ // out of the ROM image when a pointer references the relocated RAM copy).
+ // Returns addr unchanged when it is not inside any copy's RAM window.
+ uint32_t ram_alias_to_rom(uint32_t addr) const;
+
+ // ROM PC -> the vaddr the CPU actually executes it from (runtime_base+).
+ // Identity for ROM outside every copy window.
+ uint32_t runtime_pc(uint32_t rom_pc) const;
+
+ // Fix a J/JAL target encoded relative to the RUNTIME region of relocated
+ // code (the 26-bit target inherits (runtime PC & 0xF0000000), not the ROM
+ // region).
+ uint32_t relocate_j_target(uint32_t rom_addr, uint32_t target) const;
+
+ // ROM PC -> physical RAM address, for RAM-keyed copies only (install-slot
+ // detection in kernel RAM). Non-RAM-keyed windows and plain ROM return
+ // the physical address unchanged.
+ uint32_t rom_to_ram_phys(uint32_t rom_addr) const;
+
+ // Discovery-side J/JAL remap: compute the runtime-region target, then fold
+ // it back to a followable KSEG1 ROM address through any copy's RAM window.
+ // Returns the original target when the source is not in a copy window or
+ // the computed target is not resolvable to ROM.
+ uint32_t remap_relocated_j_target(uint32_t target, uint32_t source_rom_addr) const;
+
+ // --- kernel-bless window ---------------------------------------------
+
+ bool has_kbless() const { return kbless_idx_ >= 0; }
+ uint32_t kbless_ram_lo() const; // inclusive
+ uint32_t kbless_ram_hi() const; // exclusive
+ uint32_t kbless_rom_off() const; // ROM file offset of kbless_ram_lo
+ bool in_kbless(uint32_t norm) const;
+
+ // --- install slots ----------------------------------------------------
+
+ bool is_install_slot(uint32_t ram_pc) const;
+
+ // --- ROM-keyed RAM window (shell-style), for the game-overlap text ----
+
+ bool has_rom_keyed_ram_window() const { return rom_keyed_idx_ >= 0; }
+ uint32_t rom_keyed_ram_lo() const; // inclusive
+ uint32_t rom_keyed_ram_hi_incl() const; // INCLUSIVE (matches emitted text)
+
+ // --- emitted-C text generators (same table as the C++ methods) --------
+
+ // The generated dispatch's `static uint32_t normalize(uint32_t)`.
+ std::string emit_normalize_c() const;
+
+ const std::vector& copies() const { return copies_; }
+
+private:
+ std::vector copies_;
+ std::vector install_slots_;
+ uint32_t rom_base_phys_ = 0x1FC00000u;
+ uint32_t rom_size_ = 0x80000u;
+ int kbless_idx_ = -1; // index into copies_
+ int rom_keyed_idx_ = -1; // first dispatch_key="rom" copy
+};
+
+} // namespace PSXRecompV4
diff --git a/recompiler/src/cli_boot_path.cpp b/recompiler/src/cli_boot_path.cpp
new file mode 100644
index 00000000..3dda5843
--- /dev/null
+++ b/recompiler/src/cli_boot_path.cpp
@@ -0,0 +1,36 @@
+#include "cli_boot_path.h"
+
+#include
+#include
+
+namespace PSXRecompCLI {
+
+std::string normalize_boot_path(const std::string& boot) {
+ std::string normalized;
+ normalized.reserve(boot.size());
+ for (char c : boot) normalized += (c == '\\') ? '/' : c;
+ return normalized;
+}
+
+std::string boot_filename(const std::string& boot) {
+ return std::filesystem::path(normalize_boot_path(boot)).filename().string();
+}
+
+std::string serial_from_boot(const std::string& boot) {
+ std::string base = boot_filename(boot);
+ std::string clean;
+ for (char c : base) {
+ if (std::isalnum(static_cast(c))) {
+ clean += static_cast(std::toupper(static_cast(c)));
+ }
+ }
+ size_t split = 0;
+ while (split < clean.size() &&
+ std::isalpha(static_cast(clean[split]))) {
+ split++;
+ }
+ if (split && split < clean.size()) clean.insert(split, "-");
+ return clean.empty() ? "PSX-GAME" : clean;
+}
+
+} // namespace PSXRecompCLI
diff --git a/recompiler/src/cli_boot_path.h b/recompiler/src/cli_boot_path.h
new file mode 100644
index 00000000..ced50d42
--- /dev/null
+++ b/recompiler/src/cli_boot_path.h
@@ -0,0 +1,14 @@
+#pragma once
+
+#include
+
+namespace PSXRecompCLI {
+
+// SYSTEM.CNF uses backslashes for disc-internal paths. Canonicalize them
+// before passing the path to host filesystem utilities.
+std::string normalize_boot_path(const std::string& boot);
+
+std::string boot_filename(const std::string& boot);
+std::string serial_from_boot(const std::string& boot);
+
+} // namespace PSXRecompCLI
diff --git a/recompiler/tests/bios_address_model_test.cpp b/recompiler/tests/bios_address_model_test.cpp
new file mode 100644
index 00000000..4e11395a
--- /dev/null
+++ b/recompiler/tests/bios_address_model_test.cpp
@@ -0,0 +1,238 @@
+// bios_address_model_test.cpp — proves the profile-driven BiosAddressModel is
+// function-level equivalent to the hardcoded SCPH1001 helpers it replaced.
+//
+// The byte-identity check on the regenerated SCPH1001_*.c is artifact-level
+// evidence; this test is the function-level version: every 4-byte address in
+// the RAM and ROM ranges is swept through the model built from
+// bios/SCPH1001.toml and compared against verbatim copies of the pre-refactor
+// implementations (full_function_emitter.cpp / function_discovery.cpp as of
+// the commit this refactor replaced). It also exercises the from_config
+// invariants.
+//
+// Usage: bios_address_model_test [path/to/SCPH1001.toml]
+// default: bios/SCPH1001.toml relative to the CWD (run from framework root)
+
+#include
+#include
+#include
+#include
+
+#include "../src/bios_address_model.h"
+#include "../src/config_loader.h"
+
+using PSXRecompV4::BiosAddressModel;
+using PSXRecompV4::BiosAddrCopy;
+using PSXRecompV4::BiosConfig;
+
+// ---------------------------------------------------------------------------
+// Reference implementations: verbatim pre-refactor SCPH1001 hardcodes.
+// ---------------------------------------------------------------------------
+
+static uint32_t ref_normalize(uint32_t addr) {
+ uint32_t phys = addr & 0x1FFFFFFFu;
+ if (phys >= 0x1FC10000u && phys <= 0x1FC17FFFu) {
+ phys = phys - 0x1FC10000u + 0x00000500u;
+ }
+ if (phys >= 0x00030000u && phys <= 0x0005AFFFu) {
+ phys = phys - 0x00030000u + 0x1FC18000u;
+ }
+ return phys;
+}
+
+static uint32_t ref_ram_alias_to_rom(uint32_t addr) {
+ uint32_t phys = addr & 0x1FFFFFFFu;
+ if (phys >= 0x00000500u && phys < 0x00008500u) {
+ return 0xBFC10000u + (phys - 0x00000500u);
+ }
+ if (phys >= 0x00030000u && phys <= 0x0005AFFFu) {
+ return 0xBFC18000u + (phys - 0x00030000u);
+ }
+ return addr;
+}
+
+static uint32_t ref_runtime_pc(uint32_t rom_pc) {
+ uint32_t phys = rom_pc & 0x1FFFFFFFu;
+ if (phys >= 0x1FC18000u && phys <= 0x1FC42FFFu) {
+ return 0x80030000u + (phys - 0x1FC18000u);
+ }
+ if (phys >= 0x1FC10000u && phys <= 0x1FC17FFFu) {
+ return phys - 0x1FC10000u + 0x00000500u;
+ }
+ return rom_pc;
+}
+
+static uint32_t ref_relocate_j_target(uint32_t rom_addr, uint32_t target) {
+ uint32_t phys = rom_addr & 0x1FFFFFFFu;
+ if (phys >= 0x1FC10000u && phys <= 0x1FC17FFFu) {
+ uint32_t runtime_addr = phys - 0x1FC10000u + 0x00000500u;
+ return (runtime_addr & 0xF0000000u) | (target & 0x0FFFFFFFu);
+ }
+ if (phys >= 0x1FC18000u && phys <= 0x1FC42FFFu) {
+ uint32_t runtime_addr = 0x80030000u + (phys - 0x1FC18000u);
+ return (runtime_addr & 0xF0000000u) | (target & 0x0FFFFFFFu);
+ }
+ return target;
+}
+
+static uint32_t ref_rom_to_ram_phys(uint32_t rom_addr) {
+ uint32_t phys = rom_addr & 0x1FFFFFFFu;
+ if (phys >= 0x1FC10000u && phys <= 0x1FC17FFFu) {
+ return phys - 0x1FC10000u + 0x00000500u;
+ }
+ return phys;
+}
+
+// ---------------------------------------------------------------------------
+
+static int g_failures = 0;
+
+static void expect_eq(uint32_t got, uint32_t want, const char* what, uint32_t at) {
+ if (got != want) {
+ std::fprintf(stderr, "FAIL %s(0x%08X): got 0x%08X want 0x%08X\n",
+ what, at, got, want);
+ if (++g_failures > 20) {
+ std::fprintf(stderr, "too many failures, aborting\n");
+ std::exit(1);
+ }
+ }
+}
+
+static void expect_throw(const char* what, void (*fn)(BiosConfig&), BiosConfig base) {
+ try {
+ fn(base);
+ BiosAddressModel::from_config(base);
+ std::fprintf(stderr, "FAIL invariant '%s': expected from_config to throw\n", what);
+ g_failures++;
+ } catch (const std::runtime_error&) {
+ /* expected */
+ }
+}
+
+static BiosConfig minimal_cfg() {
+ BiosConfig cfg{};
+ cfg.config_path = "test://synthetic";
+ cfg.load_address = 0xBFC00000u;
+ cfg.text_size = 0x80000u;
+ return cfg;
+}
+
+static BiosAddrCopy mk(const char* name, uint32_t rom_lo, uint32_t rom_hi,
+ uint32_t ram_lo, uint32_t runtime_base,
+ bool key_is_ram, bool bless) {
+ BiosAddrCopy c;
+ c.name = name; c.rom_lo = rom_lo; c.rom_hi = rom_hi; c.ram_lo = ram_lo;
+ c.runtime_base = runtime_base; c.key_is_ram = key_is_ram;
+ c.kernel_bless = bless;
+ return c;
+}
+
+int main(int argc, char** argv) {
+ const std::string profile = argc > 1 ? argv[1] : "bios/SCPH1001.toml";
+
+ BiosAddressModel model;
+ try {
+ const auto cfg = PSXRecompV4::load_bios_config(profile);
+ model = BiosAddressModel::from_config(cfg);
+ } catch (const std::exception& ex) {
+ std::fprintf(stderr, "cannot load %s: %s\n(run from the framework root)\n",
+ profile.c_str(), ex.what());
+ return 2;
+ }
+
+ // --- Sweep: RAM range + ROM range, every 4 bytes, all helpers ---------
+ auto sweep = [&](uint32_t lo, uint32_t hi) {
+ for (uint64_t a = lo; a < hi; a += 4) {
+ const uint32_t addr = static_cast(a);
+ expect_eq(model.normalize(addr), ref_normalize(addr), "normalize", addr);
+ expect_eq(model.ram_alias_to_rom(addr), ref_ram_alias_to_rom(addr), "ram_alias_to_rom", addr);
+ expect_eq(model.runtime_pc(addr), ref_runtime_pc(addr), "runtime_pc", addr);
+ expect_eq(model.rom_to_ram_phys(addr), ref_rom_to_ram_phys(addr), "rom_to_ram_phys", addr);
+ // J-target relocation: source sweep with two representative
+ // 26-bit targets (one per runtime region shape).
+ expect_eq(model.relocate_j_target(addr, 0x0FC12344u),
+ ref_relocate_j_target(addr, 0x0FC12344u), "relocate_j(a)", addr);
+ expect_eq(model.relocate_j_target(addr, 0x00030010u),
+ ref_relocate_j_target(addr, 0x00030010u), "relocate_j(b)", addr);
+ }
+ };
+ sweep(0x00000000u, 0x00200000u); // RAM (2 MiB)
+ sweep(0x1FC00000u, 0x1FC80000u); // BIOS ROM phys
+ sweep(0xBFC00000u, 0xBFC80000u); // BIOS ROM KSEG1 (mask path)
+ sweep(0x80000000u, 0x80200000u); // RAM KSEG0 (mask path)
+
+ // --- kbless / rom-keyed accessors match the SCPH1001 constants --------
+ if (!model.has_kbless()) { std::fprintf(stderr, "FAIL: no kbless window\n"); g_failures++; }
+ expect_eq(model.kbless_ram_lo(), 0x500u, "kbless_ram_lo", 0);
+ expect_eq(model.kbless_ram_hi(), 0x8500u, "kbless_ram_hi", 0);
+ expect_eq(model.kbless_rom_off(), 0x10000u, "kbless_rom_off", 0);
+ expect_eq(model.rom_keyed_ram_lo(), 0x30000u, "rom_keyed_ram_lo", 0);
+ expect_eq(model.rom_keyed_ram_hi_incl(), 0x5AFFFu, "rom_keyed_ram_hi_incl", 0);
+ if (!model.is_install_slot(0xCF0u)) { std::fprintf(stderr, "FAIL: 0xCF0 not an install slot\n"); g_failures++; }
+ if (model.is_install_slot(0xCF4u)) { std::fprintf(stderr, "FAIL: 0xCF4 claims install slot\n"); g_failures++; }
+
+ // --- from_config invariants refuse bad profiles -----------------------
+ expect_throw("overlapping ROM windows", [](BiosConfig& c) {
+ c.address_copies = {
+ mk("a", 0x1FC10000u, 0x1FC18000u, 0x500u, 0x500u, true, false),
+ mk("b", 0x1FC14000u, 0x1FC43000u, 0x30000u, 0x80030000u, false, false),
+ };
+ }, minimal_cfg());
+ expect_throw("two bless windows", [](BiosConfig& c) {
+ c.address_copies = {
+ mk("a", 0x1FC10000u, 0x1FC14000u, 0x500u, 0x500u, true, true),
+ mk("b", 0x1FC14000u, 0x1FC18000u, 0x9000u, 0x9000u, true, true),
+ };
+ }, minimal_cfg());
+ expect_throw("misaligned bounds", [](BiosConfig& c) {
+ c.address_copies = {
+ mk("a", 0x1FC10002u, 0x1FC18000u, 0x500u, 0x500u, true, false),
+ };
+ }, minimal_cfg());
+ expect_throw("ROM window outside image", [](BiosConfig& c) {
+ c.address_copies = {
+ mk("a", 0x1FB00000u, 0x1FB10000u, 0x500u, 0x500u, true, false),
+ };
+ }, minimal_cfg());
+ expect_throw("runtime_base phys mismatch", [](BiosConfig& c) {
+ c.address_copies = {
+ mk("a", 0x1FC10000u, 0x1FC18000u, 0x500u, 0x80000600u, true, false),
+ };
+ }, minimal_cfg());
+ expect_throw("bless on rom-keyed copy", [](BiosConfig& c) {
+ c.address_copies = {
+ mk("a", 0x1FC18000u, 0x1FC43000u, 0x30000u, 0x80030000u, false, true),
+ };
+ }, minimal_cfg());
+ expect_throw("overlapping RAM windows / double-fold", [](BiosConfig& c) {
+ // a folds ROM->RAM into [0x500,0x8500); b (rom-keyed) folds RAM
+ // [0x500,...) back out — sequential normalize() would double-fold
+ // a's output. Refused (as overlapping RAM destinations).
+ c.address_copies = {
+ mk("a", 0x1FC10000u, 0x1FC18000u, 0x500u, 0x500u, true, false),
+ mk("b", 0x1FC20000u, 0x1FC28000u, 0x500u, 0x500u, false, false),
+ };
+ }, minimal_cfg());
+
+ // --- empty model degenerates to the KSEG mask -------------------------
+ {
+ BiosAddressModel empty;
+ expect_eq(empty.normalize(0xBFC12345u), 0x1FC12345u, "empty.normalize", 0xBFC12345u);
+ expect_eq(empty.runtime_pc(0xBFC12344u), 0xBFC12344u, "empty.runtime_pc", 0xBFC12344u);
+ if (empty.has_kbless() || empty.has_rom_keyed_ram_window()) {
+ std::fprintf(stderr, "FAIL: empty model claims windows\n");
+ g_failures++;
+ }
+ const std::string n = empty.emit_normalize_c();
+ if (n.find("0x1FC1") != std::string::npos) {
+ std::fprintf(stderr, "FAIL: empty model emits copy folds\n");
+ g_failures++;
+ }
+ }
+
+ if (g_failures == 0) {
+ std::printf("bios_address_model_test: all checks passed\n");
+ return 0;
+ }
+ std::fprintf(stderr, "bios_address_model_test: %d failure(s)\n", g_failures);
+ return 1;
+}
diff --git a/recompiler/tests/cli_boot_path_test.cpp b/recompiler/tests/cli_boot_path_test.cpp
new file mode 100644
index 00000000..8d516f16
--- /dev/null
+++ b/recompiler/tests/cli_boot_path_test.cpp
@@ -0,0 +1,41 @@
+#include "cli_boot_path.h"
+
+#include
+#include
+
+namespace {
+
+int failures = 0;
+
+void check(bool condition, const char* message) {
+ if (condition) return;
+ std::fprintf(stderr, "FAIL: %s\n", message);
+ failures++;
+}
+
+} // namespace
+
+int main() {
+ const std::string windows_style = "EXE\\SLUS_123.45";
+ const std::string posix_style = "EXE/SLUS_123.45";
+
+ check(PSXRecompCLI::normalize_boot_path(windows_style) == posix_style,
+ "normalizes SYSTEM.CNF backslashes independent of the host OS");
+ check(PSXRecompCLI::boot_filename(windows_style) == "SLUS_123.45",
+ "extracts a subdirectory boot filename from backslashes");
+ check(PSXRecompCLI::boot_filename(posix_style) == "SLUS_123.45",
+ "extracts a subdirectory boot filename from forward slashes");
+ check(PSXRecompCLI::serial_from_boot(windows_style) == "SLUS-12345",
+ "does not include the boot directory in the generated game ID");
+ check(PSXRecompCLI::serial_from_boot(windows_style) ==
+ PSXRecompCLI::serial_from_boot(posix_style),
+ "generates the same game ID for Linux and Windows path forms");
+ check(PSXRecompCLI::boot_filename("SCUS_944.23") == "SCUS_944.23",
+ "preserves root-level boot filenames");
+ check(PSXRecompCLI::serial_from_boot("SCUS_944.23") == "SCUS-94423",
+ "preserves root-level game ID generation");
+ check(PSXRecompCLI::serial_from_boot("") == "PSX-GAME",
+ "preserves the fallback game ID for an empty boot path");
+
+ return failures == 0 ? 0 : 1;
+}
diff --git a/recompiler/tests/test_cli_project_packaging.py b/recompiler/tests/test_cli_project_packaging.py
new file mode 100644
index 00000000..5b124889
--- /dev/null
+++ b/recompiler/tests/test_cli_project_packaging.py
@@ -0,0 +1,72 @@
+#!/usr/bin/env python3
+"""Regression guards for the self-contained CLI project pipeline."""
+
+from pathlib import Path
+import sys
+
+
+ROOT = Path(__file__).resolve().parents[2]
+
+
+def require(source: str, needle: str, message: str) -> None:
+ if needle not in source:
+ raise AssertionError(message)
+
+
+def main() -> int:
+ cli = (ROOT / "recompiler/src/main_cli.cpp").read_text(encoding="utf-8")
+ bios = (ROOT / "recompiler/src/main_bios.cpp").read_text(encoding="utf-8")
+ package = (ROOT / "tools/build_cli.py").read_text(encoding="utf-8")
+ launcher = (ROOT / "recomp-ui/src/recomp_launcher.h").read_text(
+ encoding="utf-8")
+
+ require(cli, 'bios_config = \\"psxrecomp/bios/{}\\"',
+ "generated game.toml does not name its BIOS profile")
+ require(cli, '"--config", fs::absolute(profile_destination).string()',
+ "CLI BIOS recompilation does not use the selected profile")
+ require(cli, '"--rom", options.bios.string()',
+ "CLI BIOS recompilation does not pass the user-selected ROM")
+ require(cli, '"--out-dir",',
+ "CLI BIOS recompilation does not override the generated output")
+ require(cli, 'framework_destination / "generated"',
+ "BIOS sources are not emitted where runtime.cmake discovers them")
+ require(cli, 'set(PSXRECOMP_BIOS_STEMS \\"{}\\"',
+ "generated CMake does not select the emitted BIOS backend")
+
+ copy_pos = cli.find('fmt::print("[1/4] Copying build framework')
+ game_pos = cli.find('fmt::print("[2/4] Recompiling game executable')
+ if copy_pos < 0 or game_pos < 0 or copy_pos >= game_pos:
+ raise AssertionError(
+ "framework/profile must be copied before game recompilation")
+
+ for profile in (
+ "OpenBIOS.toml", "SCPH1001.toml",
+ "SCPH101.toml", "SCPH5552.toml"):
+ require(package, f'"{profile}"',
+ f"CLI package omits BIOS profile {profile}")
+ require(package, 'shutil.copy2(ROOT / ".gitignore", framework)',
+ "packaged framework lacks the project-root marker used by profiles")
+ require(package, 'ROOT / "bios" / "openbios.bin"',
+ "packaged framework omits the redistributable OpenBIOS image")
+ require(package, 'ROOT / "bios" / "OpenBIOS.LICENSE"',
+ "packaged framework omits the OpenBIOS license")
+
+ require(bios, 'a == "--rom"',
+ "psxrecomp-bios config mode lacks the ROM override")
+ require(bios, 'a == "--out-dir"',
+ "psxrecomp-bios config mode lacks the output override")
+ require(launcher, "RecompLauncherCModProvider",
+ "pinned recomp-ui predates the runtime Mods provider API")
+ require(launcher, "const RecompLauncherCModProvider* mods;",
+ "pinned recomp-ui GameInfo lacks the Mods provider field")
+
+ print("PASS: CLI profiles, stage order, BIOS overrides, and launcher API agree")
+ return 0
+
+
+if __name__ == "__main__":
+ try:
+ sys.exit(main())
+ except AssertionError as exc:
+ print(f"FAIL: {exc}")
+ sys.exit(1)
diff --git a/recompiler/tests/test_fmv_cycle_batch_codegen.py b/recompiler/tests/test_fmv_cycle_batch_codegen.py
new file mode 100644
index 00000000..6eb16c12
--- /dev/null
+++ b/recompiler/tests/test_fmv_cycle_batch_codegen.py
@@ -0,0 +1,89 @@
+#!/usr/bin/env python3
+"""Verify generated FMV cycle batching has portable scope and IRQ flushes."""
+
+import argparse
+import os
+import struct
+import subprocess
+import sys
+import tempfile
+
+
+LOAD = 0x80010000
+
+
+def generate_source(recompiler):
+ header = bytearray(2048)
+ header[0:8] = b"PS-X EXE"
+ struct.pack_into(" str:
+ candidates = [
+ os.environ.get("CC"),
+ r"C:\msys64\mingw64\bin\gcc.exe" if os.name == "nt" else None,
+ shutil.which("gcc"),
+ shutil.which("cc"),
+ ]
+ for candidate in candidates:
+ if candidate and pathlib.Path(candidate).is_file():
+ return candidate
+ raise SystemExit("gcc/cc is required for the overlay shim compile test")
+
+
+def main() -> int:
+ source = r'''#include "cpu_state.h"
+#include "overlay_dispatch_preamble.c.inc"
+
+void func_80010000(CPUState *cpu) {
+ psx_cyc_bb_defer_begin();
+ psx_cyc_charge(1u);
+ psx_advance_cycles(1u);
+ cpu->gpr[2] = psx_cyc_load_word(cpu, cpu->gpr[4], 2u, 0u);
+ cpu->gpr[3] = psx_cyc_load_half(cpu, cpu->gpr[5], 3u, 0u);
+ psx_cyc_bb_defer_flush();
+ psx_cyc_bb_defer_end();
+ if (psx_slice_block(cpu, 0x80010000u, 1u, 0)) return;
+ debug_server_log_call_entry(0x80010000u);
+}
+'''
+ gcc = find_gcc()
+ with tempfile.TemporaryDirectory() as temp_dir:
+ temp = pathlib.Path(temp_dir)
+ c_path = temp / "overlay_shim_contract.c"
+ out_path = temp / ("overlay_shim_contract.dll" if os.name == "nt"
+ else "overlay_shim_contract.so")
+ c_path.write_text(source, encoding="ascii")
+ command = [
+ gcc, "-shared", "-O2",
+ "-DPSX_OVERLAY_DLL_BUILD",
+ "-DPSX_NO_DEBUG_TOOLS",
+ "-DPSX_ENABLE_BLOCK_CYCLES=1",
+ "-DPSX_OVERLAY_FLAVOR=0",
+ ]
+ if os.name != "nt":
+ command.append("-fPIC")
+ command.extend([
+ str(c_path), "-o", str(out_path), f"-I{INCLUDE}", "-lm",
+ ])
+ result = subprocess.run(command, capture_output=True, text=True)
+ if result.returncode != 0:
+ sys.stderr.write(result.stdout)
+ sys.stderr.write(result.stderr)
+ return result.returncode
+ if not out_path.is_file():
+ raise AssertionError("overlay compiler reported success without output")
+ print("PASS: overlay callback shim compiles against runtime headers")
+ return 0
+
+
+if __name__ == "__main__":
+ raise SystemExit(main())
diff --git a/runtime/check_generated_sources.cmake b/runtime/check_generated_sources.cmake
new file mode 100644
index 00000000..0b82262a
--- /dev/null
+++ b/runtime/check_generated_sources.cmake
@@ -0,0 +1,72 @@
+# check_generated_sources.cmake — fail the build with a HUMAN message when a
+# game's recompiled C is missing, instead of letting the C compiler emit a raw
+# "cc1: fatal error: : No such file or directory".
+#
+# The runtime links generated/_{full,dispatch}.c, which are produced by
+# a SEPARATE step (the recompiler tool) that a from-source builder runs before
+# cmake --build. Those paths are marked GENERATED in CMake so `cmake configure`
+# succeeds before the first generation — which means a skipped generation step
+# is only discovered deep in the build as an inscrutable compiler error naming a
+# file the user has no reason to know how to produce. This script is run as a
+# build-time dependency of the runtime target (see psxrecomp_add_runtime_target)
+# so the failure surfaces first, names the missing files, and states the fix.
+#
+# Invoked via: cmake -DSOURCES=<;-list> -DTARGET=.. -DGAME_CONFIG=.. \
+# -DRECOMPILER=.. -DDOC=.. -P check_generated_sources.cmake
+
+if(NOT DEFINED SOURCES)
+ message(FATAL_ERROR "check_generated_sources.cmake requires SOURCES")
+endif()
+
+set(_missing "")
+foreach(_src IN LISTS SOURCES)
+ if(_src AND NOT EXISTS "${_src}")
+ list(APPEND _missing "${_src}")
+ endif()
+endforeach()
+
+if(NOT _missing)
+ return() # all recompiled sources present — nothing to say.
+endif()
+
+if(NOT GAME_CONFIG)
+ set(GAME_CONFIG "game.toml")
+endif()
+if(NOT RECOMPILER)
+ set(RECOMPILER "psxrecomp/recompiler/build/psxrecomp-game")
+endif()
+if(NOT DOC)
+ set(DOC "psxrecomp/docs/BUILDING.md (\"Build and run a game\")")
+endif()
+if(NOT TARGET)
+ set(TARGET "the runtime")
+endif()
+
+string(REPLACE ";" "\n " _missing_pretty "${_missing}")
+
+message(FATAL_ERROR
+ "\n"
+ "=====================================================================\n"
+ " Recompiled game code is MISSING — cannot build ${TARGET}.\n"
+ "=====================================================================\n"
+ "\n"
+ " These files do not exist yet:\n"
+ " ${_missing_pretty}\n"
+ "\n"
+ " They are not shipped and not produced by this build. They are emitted\n"
+ " by the recompiler tool, which you must run ONCE before building (and\n"
+ " again whenever you change the disc/EXE or gen-time settings):\n"
+ "\n"
+ " 1. Build the recompiler tool (separate CMake tree, one time):\n"
+ " cmake -S psxrecomp/recompiler -B psxrecomp/recompiler/build \\\n"
+ " -G Ninja -DCMAKE_BUILD_TYPE=Release\n"
+ " cmake --build psxrecomp/recompiler/build\n"
+ "\n"
+ " 2. Extract your game's PS-X EXE from your own disc image, then\n"
+ " generate the recompiled C (paths come from ${GAME_CONFIG}):\n"
+ " ${RECOMPILER} --config ${GAME_CONFIG}\n"
+ "\n"
+ " 3. Re-run the build.\n"
+ "\n"
+ " Full walkthrough: ${DOC}\n"
+ "=====================================================================\n")
diff --git a/runtime/check_target_name_collisions.cmake b/runtime/check_target_name_collisions.cmake
new file mode 100644
index 00000000..f20b3df7
--- /dev/null
+++ b/runtime/check_target_name_collisions.cmake
@@ -0,0 +1,70 @@
+# Guard against build targets whose output files collide on a case-insensitive
+# filesystem (Windows and macOS — two of the three platforms we document).
+#
+# WHY THIS EXISTS
+# ---------------
+# Until 2026-07-27 recompiler/CMakeLists.txt defined BOTH `psxrecomp` (the
+# user-facing CLI, from src/main_cli.cpp) and `PSXRecomp` (a legacy duplicate of
+# psxrecomp-game). CMake treats those as two distinct targets and links both
+# happily, but on Windows they write to the SAME path on disk. Whichever linked
+# last silently overwrote the other. The CLI lost, so the binary shipped as
+# `psxrecomp.exe` in the released CLI package was actually the game recompiler,
+# whose arguments match nothing in the README. Nothing failed, nothing warned,
+# and it never reproduced on Linux — so it shipped for a long time.
+#
+# A rename alone would not prevent recurrence. This check does.
+#
+# Compares OUTPUT_NAME (falling back to the target name, which is what CMake
+# uses when OUTPUT_NAME is unset) rather than the target name itself, because
+# the on-disk filename is what actually collides — two differently-named targets
+# can still be given the same OUTPUT_NAME.
+#
+# Usage: call at the END of a CMakeLists.txt, after all targets are declared.
+# include(${CMAKE_CURRENT_SOURCE_DIR}/../runtime/check_target_name_collisions.cmake)
+# psxrecomp_check_output_name_collisions()
+
+function(psxrecomp_check_output_name_collisions)
+ get_property(_targets
+ DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
+ PROPERTY BUILDSYSTEM_TARGETS)
+
+ set(_seen_lower "")
+ set(_seen_target "")
+ set(_seen_output "")
+
+ foreach(_t IN LISTS _targets)
+ get_target_property(_type ${_t} TYPE)
+ # Only real artifacts land on disk under a name we control.
+ if(NOT _type STREQUAL "EXECUTABLE"
+ AND NOT _type STREQUAL "SHARED_LIBRARY"
+ AND NOT _type STREQUAL "STATIC_LIBRARY"
+ AND NOT _type STREQUAL "MODULE_LIBRARY")
+ continue()
+ endif()
+
+ get_target_property(_out ${_t} OUTPUT_NAME)
+ if(NOT _out)
+ set(_out "${_t}")
+ endif()
+
+ string(TOLOWER "${_out}" _lower)
+ list(FIND _seen_lower "${_lower}" _idx)
+ if(NOT _idx EQUAL -1)
+ list(GET _seen_target ${_idx} _other_target)
+ list(GET _seen_output ${_idx} _other_output)
+ message(FATAL_ERROR
+ "Case-insensitive output-name collision in ${CMAKE_CURRENT_SOURCE_DIR}:\n"
+ " target '${_other_target}' produces '${_other_output}'\n"
+ " target '${_t}' produces '${_out}'\n"
+ "These differ only by case, so on Windows and macOS they write the "
+ "SAME file and the last one linked silently wins. Rename one target "
+ "(or give it a distinct OUTPUT_NAME). Do not suppress this check: it "
+ "exists because exactly this bug shipped the wrong binary in the "
+ "released Windows CLI package.")
+ endif()
+
+ list(APPEND _seen_lower "${_lower}")
+ list(APPEND _seen_target "${_t}")
+ list(APPEND _seen_output "${_out}")
+ endforeach()
+endfunction()
diff --git a/runtime/include/bios_hle_plan.h b/runtime/include/bios_hle_plan.h
new file mode 100644
index 00000000..5950b422
--- /dev/null
+++ b/runtime/include/bios_hle_plan.h
@@ -0,0 +1,74 @@
+/* bios_hle_plan.h — the ONE place the BIOS-backend axes are decided.
+ *
+ * The HLE tier has two INDEPENDENT axes, and conflating them was a real bug:
+ *
+ * call_hle — service implemented kernel calls (B0 events) in HLE instead of
+ * the recompiled BIOS. Needs the per-image `deliver_event_ret`
+ * anchor, because a delivered callback has to return through the
+ * exact $ra the kernel's DeliverEvent loop uses. An image that
+ * does not export it CANNOT run call-HLE at all.
+ *
+ * boot_skip — the one-shot shell intercept that skips the BIOS boot animation
+ * and lets Main() proceed straight to SYSTEM.CNF + game EXE load.
+ * Needs only the per-image `shell_entry_phys` anchor, and works
+ * under pure LLE: nothing is synthesized, the shell call simply
+ * returns immediately, exactly as if the player had let the disc
+ * menu exit on its own.
+ *
+ * THE BUG THIS MODULE FIXES (2026-07-27). main.cpp used to force the requested
+ * `bios_hle` flag to false when an image exported no `deliver_event_ret` (the
+ * correct call-HLE decision), and THEN derived boot_skip from that already-
+ * mutated flag. So on OpenBIOS — which deliberately omits deliver_event_ret
+ * until its B0 semantics are validated, but does export shell_entry_phys — the
+ * player's boot-skip silently stopped working: choosing OpenBIOS sat the player
+ * in the boot animation while the same build on retail SCPH-1001 went straight
+ * to the game. One flag, two behaviours, depending on a BIOS detail no player
+ * can see.
+ *
+ * The rule is now explicit and image-independent: `bios_hle` (or the deprecated
+ * `fast_boot` alias) means "skip the BIOS boot and go to the game", on EVERY
+ * BIOS that exports a shell entry. Whether the same flag additionally routes
+ * kernel calls through HLE is a separate, anchor-gated question.
+ *
+ * Kept dependency-free (no CPUState, no psx_bios_image, no runtime globals) so
+ * the whole decision matrix is unit-tested directly — see
+ * runtime/tests/test_bios_hle_plan.c.
+ */
+#ifndef PSXRECOMP_BIOS_HLE_PLAN_H
+#define PSXRECOMP_BIOS_HLE_PLAN_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* What the config/env asked for, plus what the linked BIOS image can support. */
+typedef struct PsxBiosHleRequest {
+ /* ---- requested (game.toml [runtime] / settings.toml / env) ---- */
+ int bios_hle; /* [runtime] bios_hle, PSX_BIOS_HLE */
+ int keep_intro; /* bios_hle_keep_intro, PSX_BIOS_HLE_KEEP_INTRO */
+ int fast_boot; /* deprecated alias: boot-skip only, never call-HLE */
+ /* ---- capabilities of the SELECTED backend (psx_bios_image anchors) ---- */
+ int have_deliver_event_ret; /* image exports the DeliverEvent $ra anchor */
+ int have_shell_entry; /* image exports the shell entry PC */
+ /* ---- session shape ---- */
+ int have_game_entry; /* a game EXE is loaded (0 = BIOS-only run) */
+} PsxBiosHleRequest;
+
+typedef struct PsxBiosHlePlan {
+ int call_hle; /* pass to psx_bios_hle_configure() as call_hle */
+ int boot_skip; /* pass to psx_bios_hle_configure() as boot_skip */
+ /* Denial flags: the feature was asked for and is being refused because the
+ * SELECTED image cannot support it. Purely for the startup banner — a
+ * silent downgrade is what made the original bug invisible. */
+ int call_hle_denied;
+ int boot_skip_denied;
+} PsxBiosHlePlan;
+
+/* Pure function: same request in, same plan out, no globals touched. */
+PsxBiosHlePlan psx_bios_hle_plan(PsxBiosHleRequest req);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PSXRECOMP_BIOS_HLE_PLAN_H */
diff --git a/runtime/include/cue_sheet.h b/runtime/include/cue_sheet.h
new file mode 100644
index 00000000..33f060bc
--- /dev/null
+++ b/runtime/include/cue_sheet.h
@@ -0,0 +1,77 @@
+// cue_sheet.h — the single cue-sheet parser for the runtime.
+//
+// A .cue is the authoritative table of contents for a PS1 disc dump: it names
+// the backing BINARY file(s) and the TRACK/INDEX layout (data track, CD-DA
+// audio tracks, pregaps). Everything that needs to understand a cue goes
+// through here so the mount path (iso_reader.cpp), the identity/verify path
+// (disc_identity.cpp) and the picked-path resolver (disc_path.cpp) can never
+// disagree about what a given cue describes.
+//
+// Parsing is deliberately permissive about surface syntax (case, quoting,
+// leading whitespace) and strict about semantics: a FILE whose type is not
+// BINARY is reported rather than guessed at, because WAVE/MP3 payloads have no
+// fixed sector geometry and mis-mapping the TOC is worse than refusing the
+// image.
+
+#pragma once
+
+#include
+#include
+#include
+#include
+
+namespace PSXRecompV4 {
+
+// One FILE line, resolved against the cue's own directory.
+struct CueFileRef {
+ std::string name; // exactly as written in the cue
+ std::filesystem::path path; // resolved (absolute where possible)
+ bool is_binary = false; // FILE type token was BINARY
+ bool exists = false; // resolved path is a real file
+};
+
+// One TRACK, with its INDEX times still expressed relative to the OWNING FILE
+// (the cue's own convention). Converting to disc-relative LBAs needs each
+// file's sector count, which only the mounting code knows.
+struct CueTrackRef {
+ int number = 0;
+ bool is_audio = false;
+ size_t file_index = 0; // index into CueSheet::files
+ uint32_t index01 = 0; // track start, file-relative LBA
+ uint32_t index00 = 0; // pregap start, file-relative LBA
+ bool has_index00 = false;
+};
+
+struct CueSheet {
+ bool opened = false; // the .cue itself was readable
+
+ std::vector files; // in cue order
+ std::vector tracks; // in cue order
+
+ // True when every FILE line named a BINARY payload that exists on disk —
+ // i.e. this cue can actually be mounted as written.
+ bool usable() const;
+
+ // True when at least one FILE line named a non-BINARY payload.
+ bool has_non_binary_file() const;
+
+ // Index of the file backing the first non-audio track, else 0. This is the
+ // file that carries SYSTEM.CNF / the ISO9660 header, so it is the one
+ // identity and CRC checks must read.
+ size_t data_file_index() const;
+};
+
+// Case-insensitive extension test. `dotted_ext` includes the dot, e.g. ".cue".
+bool path_has_extension_ci(const std::filesystem::path& p, const char* dotted_ext);
+
+// Resolve `name` (as written in a cue) against `dir`. Falls back to a
+// case-insensitive directory scan when the literal name does not exist, so a
+// redump cue written on Windows still mounts on a case-sensitive filesystem.
+// Returns the literal resolution when nothing matches.
+std::filesystem::path resolve_cue_relative_file(const std::filesystem::path& dir,
+ const std::string& name);
+
+// Parse `cue_path`. A sheet whose `opened` is false could not be read at all.
+CueSheet parse_cue_sheet(const std::filesystem::path& cue_path);
+
+} // namespace PSXRecompV4
diff --git a/runtime/include/disc_path.h b/runtime/include/disc_path.h
new file mode 100644
index 00000000..63bd1828
--- /dev/null
+++ b/runtime/include/disc_path.h
@@ -0,0 +1,52 @@
+// disc_path.h — canonicalize whatever disc file the user actually picked.
+//
+// Players do not reliably know whether to hand us the .cue or the .bin, and
+// both are legitimate things to find in a dump folder. Either pick must mount
+// and must verify identically, so every entry point that accepts a disc path
+// (game.toml [runtime] disc, --disc, the cached disc.cfg, the launcher's
+// "Change Disc" picker, the first-run file dialog) routes through
+// resolve_disc_path() first.
+//
+// The rule is "prefer the cue, but never require it":
+//
+// picked a .cue that resolves -> mount the cue (keeps the TOC)
+// picked a .cue with a missing bin -> mount a sibling image, if one exists
+// picked a .bin/.iso/.img owned by a cue -> mount that cue (keeps the TOC)
+// picked a .bin/.iso/.img with no cue -> mount it directly
+//
+// Preferring the cue matters beyond tidiness: the cue is the only place the
+// CD-DA track layout lives. Silently swapping a cue for its same-named .bin
+// (which is what the runtime used to do) drops every audio track and pregap on
+// a single-file multi-track dump.
+//
+// `data` is always the binary carrying the ISO9660 header and SYSTEM.CNF, so
+// header / serial / CRC32 checks read the same bytes no matter which file the
+// user picked — that is what keeps the "Disc verified" verdict stable across
+// picks.
+
+#pragma once
+
+#include
+#include
+
+namespace PSXRecompV4 {
+
+struct DiscPathResolution {
+ std::filesystem::path picked; // what the caller asked for (absolute where possible)
+ std::filesystem::path mount; // hand this to ISOReader::Open / cdrom_init
+ std::filesystem::path data; // data-track binary; read identity/CRC from this
+
+ bool from_cue = false; // `mount` is a cue sheet
+ bool upgraded_to_cue = false; // caller picked a bin; we found the cue that owns it
+ bool cue_fallback = false; // caller picked an unusable cue; fell back to a bin
+
+ // Human-readable note describing any substitution, "" when `mount` is just
+ // `picked`. Suitable for a launcher warning or a startup banner.
+ std::string note;
+};
+
+// Never fails: an unrecognizable or missing path resolves to itself, so the
+// caller's existing "could not open the disc" reporting still fires.
+DiscPathResolution resolve_disc_path(const std::filesystem::path& picked);
+
+} // namespace PSXRecompV4
diff --git a/runtime/include/launcher_device.h b/runtime/include/launcher_device.h
new file mode 100644
index 00000000..bc7ca194
--- /dev/null
+++ b/runtime/include/launcher_device.h
@@ -0,0 +1,47 @@
+// launcher_device.h - controller-source conversion at the recomp-ui C ABI.
+
+#pragma once
+
+#include
+#include
+#include
+
+namespace PSXRecompV4 {
+
+inline std::string trim_launcher_device(const std::string& device) {
+ const auto first = std::find_if_not(device.begin(), device.end(),
+ [](unsigned char c) { return std::isspace(c) != 0; });
+ const auto last = std::find_if_not(device.rbegin(), device.rend(),
+ [](unsigned char c) { return std::isspace(c) != 0; }).base();
+ return first < last ? std::string(first, last) : std::string();
+}
+
+inline std::string normalize_launcher_device(const std::string& device) {
+ std::string normalized = trim_launcher_device(device);
+ std::transform(normalized.begin(), normalized.end(), normalized.begin(),
+ [](unsigned char c) { return static_cast(std::tolower(c)); });
+ return normalized;
+}
+
+inline int launcher_source_from_device(const std::string& device) {
+ const std::string normalized = normalize_launcher_device(device);
+ if (normalized.empty() || normalized == "none") return 0;
+ if (normalized == "keyboard") return 1;
+ return 2;
+}
+
+inline std::string launcher_device_from_source(
+ int source, const std::string& previous_device) {
+ if (source <= 0) return "none";
+ if (source == 1) return "keyboard";
+
+ // recomp-ui's C ABI currently returns a source category, not the selected
+ // controller GUID. Preserve an existing gamepad/GUID assignment; when the
+ // user switched from None/Keyboard, persist the runtime's first-pad alias.
+ if (launcher_source_from_device(previous_device) == 2) {
+ return trim_launcher_device(previous_device);
+ }
+ return "gamepad";
+}
+
+} // namespace PSXRecompV4
diff --git a/runtime/include/mod_packages.h b/runtime/include/mod_packages.h
new file mode 100644
index 00000000..f9953423
--- /dev/null
+++ b/runtime/include/mod_packages.h
@@ -0,0 +1,335 @@
+#pragma once
+
+#include
+#include
+#include
+#include