Files
psxrecomp/runtime/include/psx_bios_module_glue.c.inc
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

128 lines
5.7 KiB
C++

/* psx_bios_module_glue.c.inc — the module side of a loadable BIOS backend.
*
* Compiled ONCE per module as its own translation unit by
* tools/bios_module_build.py, after overlay_dispatch_preamble.c.inc (which
* defines g_cbs, overlay_flush_cycles and every forwarder a BIOS shares with
* overlay shards). This file adds what only a BIOS dispatcher needs — see
* psx_bios_module.h — and the four exported entry points.
*
* Contract: nothing here may reach a runtime symbol directly. Every call and
* every global goes through g_bcbs / g_cbs, or through the pointers the
* generated C's macro-rewritten externs declare. PSX_BIOS_MODULE_STEM is the
* image stem (-DPSX_BIOS_MODULE_STEM=SCPH1001) and names the descriptor the
* generated dispatch exports.
*/
#include "psx_bios_backend.h"
#include "psx_bios_module.h"
#ifndef PSX_BIOS_MODULE_STEM
#error "PSX_BIOS_MODULE_STEM must name the image stem (e.g. -DPSX_BIOS_MODULE_STEM=SCPH1001)"
#endif
#define PSX_BM_CAT2(a, b) a##b
#define PSX_BM_CAT(a, b) PSX_BM_CAT2(a, b)
#define PSX_BM_DESCRIPTOR PSX_BM_CAT(PSX_BIOS_MODULE_STEM, _psx_bios_backend)
extern const PsxBiosBackend PSX_BM_DESCRIPTOR;
static PsxBiosModuleCallbacks g_bcbs;
/* Local dummies so a call before init cannot fault; init repoints them. */
static int (*s_hle_hook_dummy)(CPUState *cpu, uint32_t phys);
static int s_depth_dummy;
static uint64_t s_hits_dummy;
static uint32_t s_func_addr_dummy;
static uint64_t s_flattened_dummy;
static uint32_t s_ram_mask_dummy = 0x1FFFFFu;
static uint64_t s_frame_count_dummy;
/* The pointers the generated C declares (via the psx_bios_module.h name
* mappings): `extern int g_psx_dispatch_depth;` became
* `extern int (*g_psx_bm_dispatch_depth_p);`, and so on. Defined here. */
int (**g_psx_bm_hle_hook_p)(CPUState *cpu, uint32_t phys) = &s_hle_hook_dummy;
int *g_psx_bm_dispatch_depth_p = &s_depth_dummy;
uint64_t *g_psx_bm_static_hits_p = &s_hits_dummy;
uint32_t *g_psx_bm_current_func_addr_p = &s_func_addr_dummy;
uint64_t *g_psx_bm_bail_flattened_p = &s_flattened_dummy;
uint32_t *g_psx_bm_ram_mask_p = &s_ram_mask_dummy;
uint64_t *g_psx_bm_frame_count_p = &s_frame_count_dummy;
/* ---- Forwarders for what OverlayCallbacks does not carry ----------------- */
void psx_arith_overflow(CPUState *cpu) {
overlay_flush_cycles();
if (g_bcbs.arith_overflow) g_bcbs.arith_overflow(cpu);
}
void psx_break(CPUState *cpu, uint32_t code, uint32_t pc) {
overlay_flush_cycles();
if (g_bcbs.brk) g_bcbs.brk(cpu, code, pc);
}
void psx_unaligned_access(CPUState *cpu, uint32_t addr, uint32_t pc) {
overlay_flush_cycles();
if (g_bcbs.unaligned_access) g_bcbs.unaligned_access(cpu, addr, pc);
}
void psx_rfe_escape_check(CPUState *cpu) {
overlay_flush_cycles();
if (g_bcbs.rfe_escape_check) g_bcbs.rfe_escape_check(cpu);
}
int dirty_ram_dispatch(CPUState *cpu, uint32_t addr, uint32_t stop_addr) {
overlay_flush_cycles();
return g_bcbs.dirty_ram_dispatch ? g_bcbs.dirty_ram_dispatch(cpu, addr, stop_addr) : 0;
}
int dirty_ram_is_dirty(uint32_t phys) {
return g_bcbs.dirty_ram_is_dirty ? g_bcbs.dirty_ram_is_dirty(phys) : 0;
}
int dirty_ram_text_native_ok(uint32_t phys) {
return g_bcbs.dirty_ram_text_native_ok ? g_bcbs.dirty_ram_text_native_ok(phys) : 0;
}
int psx_kernel_bless_dispatchable(uint32_t phys) {
return g_bcbs.kernel_bless_dispatchable ? g_bcbs.kernel_bless_dispatchable(phys) : 0;
}
int psx_game_address_in_text(uint32_t addr) {
return g_bcbs.game_address_in_text ? g_bcbs.game_address_in_text(addr) : 0;
}
int fntrace_is_game_started(void) {
return g_bcbs.fntrace_is_game_started ? g_bcbs.fntrace_is_game_started() : 0;
}
void fntrace_record(CPUState *cpu, uint32_t target) {
if (g_bcbs.fntrace_record) g_bcbs.fntrace_record(cpu, target);
}
void debug_server_trace_dispatch(uint32_t func_addr) {
if (g_bcbs.trace_dispatch) g_bcbs.trace_dispatch(func_addr);
}
void debug_server_log_probe(uint32_t pc, CPUState *cpu) {
if (g_bcbs.log_probe) g_bcbs.log_probe(pc, cpu);
}
void psx_segment_miss_record_kind(uint32_t addr, uint32_t home, uint32_t ra,
uint32_t sp, uint32_t frame, uint32_t kind) {
if (g_bcbs.segment_miss_record_kind)
g_bcbs.segment_miss_record_kind(addr, home, ra, sp, frame, kind);
}
/* ---- Exports ------------------------------------------------------------- */
PSX_OVERLAY_EXPORT int psx_bios_module_abi(void) {
return PSX_BIOS_MODULE_ABI_TAG;
}
PSX_OVERLAY_EXPORT uint32_t psx_bios_module_codegen_hash(void) {
return (uint32_t)PSX_OVERLAY_CODEGEN_HASH;
}
PSX_OVERLAY_EXPORT int psx_bios_module_init(const OverlayCallbacks *cbs,
const PsxBiosModuleCallbacks *bcbs) {
if (!cbs || !bcbs) return 0;
/* The host's table must be at least as long as ours: a shorter one would
* leave our tail members reading past its end. The ABI tag already pins
* the version; this is the belt to that suspender. */
if (bcbs->size < sizeof(PsxBiosModuleCallbacks)) return 0;
overlay_init(cbs);
g_bcbs = *bcbs;
if (g_bcbs.hle_hook) g_psx_bm_hle_hook_p = g_bcbs.hle_hook;
if (g_bcbs.dispatch_depth) g_psx_bm_dispatch_depth_p = g_bcbs.dispatch_depth;
if (g_bcbs.static_hits) g_psx_bm_static_hits_p = g_bcbs.static_hits;
if (g_bcbs.current_func_addr) g_psx_bm_current_func_addr_p = g_bcbs.current_func_addr;
if (g_bcbs.bail_flattened) g_psx_bm_bail_flattened_p = g_bcbs.bail_flattened;
if (g_bcbs.ram_mask) g_psx_bm_ram_mask_p = g_bcbs.ram_mask;
if (g_bcbs.frame_count) g_psx_bm_frame_count_p = g_bcbs.frame_count;
return 1;
}
PSX_OVERLAY_EXPORT const PsxBiosBackend *psx_bios_module_backend(void) {
return &PSX_BM_DESCRIPTOR;
}