Files
psxrecomp/docs/BIOS_SELECTION.md
Matthew Stanley 5ec343ab4f fix(bios): make the boot-skip flag mean the same thing on every BIOS
`bios_hle` (default on) is the player-facing "skip the BIOS and go
straight to the game" flag, and it did nothing under the bundled
OpenBIOS: choosing OpenBIOS sat the player in the boot animation while
the identical build on retail SCPH-1001 went straight to the game.

Root cause was ordering, not policy. The tier has two axes with
different per-image requirements:

  * boot-skip     needs only `shell_entry_phys`, works under pure LLE
                  (nothing is synthesized — the shell call just returns
                  immediately), so any image exporting it can skip
  * kernel-call    needs `deliver_event_ret`, the kernel's own
                  DeliverEvent `$ra`, or a delivered callback returns to
                  the wrong PC

main.cpp correctly refused the call axis on an image without
`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 an available skip was cancelled by an unrelated
refusal, and one flag meant two things depending on a BIOS detail no
player can see.

Both axes are now decided by one pure, dependency-free function,
`psx_bios_hle_plan()` (runtime/src/bios_hle_plan.c): the boot decision
reads the REQUESTED `bios_hle`, each axis is gated on only the anchor it
needs, and refusals are reported at startup instead of downgrading
silently. `psx_bios_hle_configure()` clamps `boot_skip` to
`shell_entry_phys != 0` as a backstop so the banner and `hle_dump`
cannot overstate what can fire. The main.cpp local is renamed
`bios_hle_requested` and is no longer written back from the plan — the
`session_reboot` path re-enters that block, so folding a refusal into
the request would have made a netplay rematch differ from first launch.

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 on OpenBIOS. It was not. `load_bios_config` now REJECTS
  `[runtime]` in a BIOS profile: a profile states facts about an image,
  never preferences.
* `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 for a TU supplying its own advance.

Tests: new `bios_hle_plan_test` pins the matrix, including a 64-case
sweep asserting the boot decision is independent of
`deliver_event_ret` and the call decision independent of
`shell_entry_phys`. Runtime ctest 15/15, recompiler ctest 30/30.

Validated on Ape Escape (SCUS-94423) built against this framework with
both backends linked, headless, all four cases:

  OpenBIOS default   bios_boot=skip to game; route=2 fire at vec 0x30000
                     ra=0xBFC06EA8 (the continuation OpenBIOS.toml
                     documents); in-game on screen
  retail default     bios_boot=skip to game; route=2 at vec 0x30000
                     ra=0xBFC0702C; in-game on screen; differs only in
                     bios_backend=HLE (LLE fallback)
  PSX_BIOS_HLE=0     bios_boot=real intro; ring empty (no hook)
  KEEP_INTRO=1       bios_boot=real intro; ring empty

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-27 16:36:48 -07:00

7.1 KiB

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 <path> 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:

[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.