Files
Alex Vanderveen 60919f5a87 bios: build a retail backend from the player's own dump as a loadable module
Bundled releases link OpenBIOS only; retail-BIOS-derived C never ships.
"Bring your own BIOS" now works without it: when a dump matching a shipped
profile (psx_bios_known_images.h) is selected, the runtime builds that
backend on the player's machine with the overlay toolchain the release
already carries, caches it, and loads it as one more registered backend.

- runtime/include/psx_bios_module.h: the module contract. An overlay-style
  self-contained shared library (no imports from the executable) exporting
  psx_bios_module_{abi,codegen_hash,init,backend}; reaches the runtime only
  through OverlayCallbacks plus a new PsxBiosModuleCallbacks table (traps,
  dirty-RAM/kernel-bless probes, fntrace, log_probe) and pointer aliases of
  the globals the generated dispatcher touches. Its #defines rewrite the
  emitter's own extern lines, so the generated C compiles unchanged.
- runtime/include/psx_bios_module_glue.c.inc: module-side forwarders and the
  four exports, compiled after overlay_dispatch_preamble.c.inc.
- runtime/src/psx_bios_module.c: host side. Cache path
  <exe>/cache/bios/<os-arch>/bm<abi>_<codegen hash>_f<flavor>/<STEM>_<crc>,
  ABI + codegen-hash gated load, callback fill, and a blocking build via
  overlay_toolchain/bios_module_build.py (gcc via compiler.txt or PATH,
  else tcc), then psx_bios_register().
- psx_bios_backend.c: the registry is now a mutable table seeded from the
  CMake-generated psx_bios_builtin_registry by a constructor, with
  psx_bios_register(); every existing walker keeps working.
- main.cpp: validate_bios_for_launch acquires a module (announcing a first
  build) before declaring a mismatch; discovery accepts a buildable dump;
  the launcher verify reports it usable ("ready" / "compiled on first
  launch") with no Generate & rebuild; the BIOS row shows whenever the
  toolchain is present; bundled_only respects module support.
- emitter: the CPS-marker constructor is not emitted in a module build.
- overlay_api.h: OverlayCallbacks gains a struct tag;
  overlay_loader_callbacks() exposes the host table before loader init.
- tools/bios_module_build.py (ships in the toolchain): identity + profile
  SHA-256 gate, staged emitter tree, emit, three-TU compile, ctypes
  self-check, atomic publish. release_stage.py stages psxrecomp-bios, every
  bios/*.toml, the seeds and the builder into overlay_toolchain/.
- Tests: runtime/tests/test_bios_module_host.c against three fake modules
  (good / wrong ABI / wrong hash) on real dlopen; recompiler/tests/
  test_bios_module_build.py builds the bundled OpenBIOS end to end and reads
  the descriptor back; guards extended.

Verified on Twisted Metal 4 (bundled build + staged toolchain, headless):
first launch with --bios SCPH1001.BIN builds and loads the module and boots
on image=SCPH-1001; the second launch loads it from cache.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-30 19:26:29 -04:00

106 lines
4.3 KiB
C

/* psx_bios_backend.h — the routing seam between the runtime and whichever
* recompiled BIOS is active.
*
* Every build links more than one recompiled BIOS (bundled OpenBIOS plus a
* retail image). Each one exports exactly ONE symbol — its backend descriptor,
* named <STEM>_psx_bios_backend — and everything else it defines is either
* static or stem-prefixed, so the images cannot collide at link time.
*
* The runtime picks a backend at startup (docs/BIOS_SELECTION.md: no explicit
* player choice -> OpenBIOS; an explicit, identity-matching choice -> that
* image) and publishes it as psx_bios_active.
*
* Callers do not go through this struct. The unprefixed psx_dispatch() /
* psx_dispatch_call() the game's generated C and the runtime already call are
* thin forwarders (psx_bios_backend.c) that route to the active backend, and
* psx_bios_image is assigned from it at selection time — so adding a second
* BIOS changed no call sites.
*
* Adding another BIOS later is one more descriptor and one more registry
* entry; it is not another round of symbol collisions.
*/
#ifndef PSX_BIOS_BACKEND_H
#define PSX_BIOS_BACKEND_H
#include <stdint.h>
#include "psx_bios_image.h"
#ifdef __cplusplus
extern "C" {
#endif
struct CPUState;
typedef struct PsxBiosBackend {
/* Identity, kernel-bless window and HLE anchors for this image. Copied
* into the global psx_bios_image when this backend is selected. */
const PsxBiosImageInfo *image;
/* Dispatch entry points for this image. */
void (*dispatch)(struct CPUState *cpu, uint32_t addr);
void (*dispatch_call)(struct CPUState *cpu, uint32_t addr,
uint32_t return_addr);
/* Kernel body-extent table used by the kernel-image bless mechanism
* (memory.c). Published as psx_bios_kernel_bodies/_count on selection.
*
* The native call-stub table is deliberately absent: it is only read by
* the generated dispatch itself, so it stays static there. */
const PsxKernelBody *kernel_bodies;
uint32_t kernel_body_count;
/* Kernel-RAM ranges the guest legitimately patches at runtime, from this
* image's [[recompiler.install_slots]] (psx_bios_image.h). The bless
* verifier skips them and the dirty-RAM interpreter resumes native at
* each range's hi. Null/0 for an image that declares no slots. */
const PsxKernelPatchRange *kernel_patch_ranges;
uint32_t kernel_patch_range_count;
} PsxBiosBackend;
/* Dispatch nesting depth, shared by every backend (psx_bios_backend.c). */
extern int g_psx_dispatch_depth;
/* The backend in use. Null before psx_bios_select() runs; every forwarder and
* every consumer of psx_bios_image depends on it being set first. */
extern const PsxBiosBackend *psx_bios_active;
/* Backends compiled into this binary, in preference order (bundled OpenBIOS
* first). Emitted by the build as psx_bios_registry.c. */
extern const PsxBiosBackend *const psx_bios_builtin_registry[];
extern const uint32_t psx_bios_builtin_registry_count;
/* The live registry: the builtins plus any backend registered at run time
* (a BIOS module built from the player's dump, psx_bios_module.h). Walk it
* only after psx_bios_registry_seeded_count() or any lookup below has run;
* the count is 0 until then in a process that never looked. */
extern const PsxBiosBackend *psx_bios_registry[];
extern uint32_t psx_bios_registry_count;
uint32_t psx_bios_registry_seeded_count(void);
/* Add a backend loaded at run time. Idempotent for a pointer already present;
* 0 when the table is full or the descriptor has no image. */
int psx_bios_register(const PsxBiosBackend *backend);
/* Look up a compiled-in backend by its profile id ("SCPH-1001", "OPENBIOS").
* Null if this build does not carry it. */
const PsxBiosBackend *psx_bios_find(const char *image_id);
/* Select a backend and publish it (also assigns the global psx_bios_image).
* Returns 0 if backend is null. */
int psx_bios_activate(const PsxBiosBackend *backend);
/* The bundled, redistributable backend (image_bundled != 0), or null if this
* build has none. */
const PsxBiosBackend *psx_bios_bundled(void);
/* True if a player-supplied image is meaningful for this build (some linked
* backend is not the bundled one). */
int psx_bios_has_selectable(void);
#ifdef __cplusplus
}
#endif
#endif /* PSX_BIOS_BACKEND_H */