Files
psxrecomp/runtime/include/psx_bios_module.h
T
Alex Vanderveen 170370a440 launcher: prepare a retail BIOS from the wizard with progress, not at Play
A buildable dump used to verify as usable and then block at Play behind a
modal while the module compiled. Now:

- ae_bios_verify reports a buildable, not-yet-cached dump as needs_regen,
  and the runtime wires recomp-ui's new bios_prepare_with_progress job
  (ae_bios_prepare) whenever the toolchain is present and no Generate host
  is: the launcher runs the build as a progress job from the wizard's
  "Prepare BIOS" button or the Play prompt, re-verifies, and resumes the
  wizard. Generate & rebuild is never offered for a BIOS in a bundled build.
- psx_bios_module.c gains a progress sink fed from the builder's output
  lines (dump / emit / compile / self-check / published map to stage
  percentages with readable messages); run_command streams lines on both
  platforms.
- The launch-time build stays as the fallback for --bios / --no-launcher.

Requires recomp-ui 9f86c49 (GameInfo.bios_prepare_with_progress).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-30 20:10:57 -04:00

189 lines
9.8 KiB
C

/* psx_bios_module.h — a recompiled BIOS backend as a separately loaded module.
*
* WHY. A bundled release links only the redistributable OpenBIOS backend: the
* C a retail image recompiles to is a translation of that image's code, so it
* never ships (docs/BIOS_SELECTION.md, docs/ci/BUNDLED_RELEASES.md). For a
* player who owns a retail dump, the release therefore builds that backend on
* THEIR machine, from THEIR dump, with the overlay toolchain the release
* already carries — the same mechanism that turns overlays streamed from the
* player's disc into native code — and loads it here as a shared library.
*
* THE CONTRACT. A BIOS module is an overlay-style self-contained library. It
* imports nothing from the executable (the executable exports no symbols on
* any platform); every call back into the runtime goes through two callback
* tables handed over at init:
*
* OverlayCallbacks (overlay_api.h) — the part a BIOS shares with
* overlay shards: cycles, loads,
* GTE, syscall, dispatch_call…
* PsxBiosModuleCallbacks (this header) — what only a BIOS dispatcher
* needs: traps, the dirty-RAM
* and kernel-bless probes, and
* the runtime globals the
* generated dispatch reads and
* writes (through pointers).
*
* The generated C is compiled UNCHANGED with -DPSX_OVERLAY_DLL_BUILD (so the
* inline RAM-load and cycle fast paths in cpu_state.h / psx_cyc.h /
* psx_cycles.h become out-of-line calls the glue forwards) plus
* -DPSX_BIOS_MODULE_BUILD, with this header prepended: the mapping block
* below renames the handful of runtime globals the emitter declares `extern`
* to pointer dereferences, exactly as cpu_state.h does for the bail state.
* A macro'd `extern T g_x;` becomes `extern T (*g_x_p);`, which is the
* pointer's own declaration — no per-symbol shim is needed in the C.
*
* The module exports four functions (psx_bios_module_glue.c.inc):
* int psx_bios_module_abi(void) == PSX_BIOS_MODULE_ABI_TAG
* uint32_t psx_bios_module_codegen_hash(void) == PSX_OVERLAY_CODEGEN_HASH
* int psx_bios_module_init(const OverlayCallbacks*, const PsxBiosModuleCallbacks*)
* const PsxBiosBackend *psx_bios_module_backend(void)
* and is otherwise opaque. The host (runtime/src/psx_bios_module.c) gates a
* load on the first two, calls init, then registers the descriptor with
* psx_bios_register() so every existing selection path sees it as one more
* linked backend.
*
* ABI. Bump PSX_BIOS_MODULE_ABI_VERSION whenever PsxBiosModuleCallbacks
* changes shape, a mapping is added, or the glue's forwarding semantics
* change. Append-only growth of the table is still a bump: a module built
* against a shorter table must not be handed a longer one it never checks.
* The cache directory name carries this version, the codegen hash, and the
* flavor, so a stale module is never even opened.
*/
#ifndef PSX_BIOS_MODULE_H
#define PSX_BIOS_MODULE_H
#include <stddef.h>
#include <stdint.h>
/* ---- Name mappings (module build only) -------------------------------------
* Pure #defines, deliberately with no declarations: each generated `extern`
* line, and psx_memory.h's own `extern uint32_t g_psx_ram_mask;`, declares
* the pointer with the right type once the name is rewritten. Prepended by
* tools/bios_module_build.py before the first #include, so psx_memory.h sees
* the g_psx_ram_mask mapping too (the emitted normalize() masks with it). */
#if defined(PSX_BIOS_MODULE_BUILD)
/* The one global the emitter uses without its own extern line (cpu_state.h
* declares it only outside PSX_OVERLAY_DLL_BUILD), so declare the pointer. */
extern uint64_t *g_psx_bm_bail_flattened_p;
# define g_psx_bios_hle_hook (*g_psx_bm_hle_hook_p)
# define g_psx_dispatch_depth (*g_psx_bm_dispatch_depth_p)
# define g_dispatch_static_hits (*g_psx_bm_static_hits_p)
# define g_debug_current_func_addr (*g_psx_bm_current_func_addr_p)
# define g_psx_bail_flattened (*g_psx_bm_bail_flattened_p)
# define g_psx_ram_mask (*g_psx_bm_ram_mask_p)
# define s_frame_count (*g_psx_bm_frame_count_p)
#endif
#ifdef __cplusplus
extern "C" {
#endif
struct CPUState;
struct OverlayCallbacks;
struct PsxBiosBackend;
#define PSX_BIOS_MODULE_ABI_VERSION 1
/* Combined tag: ABI version (low 16) | codegen flavor (high 16), like
* PSX_OVERLAY_ABI_TAG. The flavor comes from the same -DPSX_OVERLAY_FLAVOR
* the executable was built with; the glue and the host both evaluate it. */
#ifndef PSX_OVERLAY_FLAVOR
#define PSX_OVERLAY_FLAVOR 0
#endif
#define PSX_BIOS_MODULE_ABI_TAG \
((int)((PSX_BIOS_MODULE_ABI_VERSION & 0xFFFF) | ((PSX_OVERLAY_FLAVOR & 0xFFFF) << 16)))
/* What the BIOS dispatcher needs beyond OverlayCallbacks. Function members
* are the runtime's real implementations; pointer members alias the
* runtime's globals so a module read/write is the runtime's read/write. */
typedef struct PsxBiosModuleCallbacks {
uint32_t size; /* sizeof(PsxBiosModuleCallbacks) on the host side */
/* Traps (runtime/src/traps.c; strict_translator emits these). */
void (*arith_overflow)(struct CPUState *cpu);
void (*brk)(struct CPUState *cpu, uint32_t code, uint32_t pc);
void (*unaligned_access)(struct CPUState *cpu, uint32_t addr, uint32_t pc);
void (*rfe_escape_check)(struct CPUState *cpu);
/* Dispatch probes the generated dispatcher consults per iteration. */
int (*dirty_ram_dispatch)(struct CPUState *cpu, uint32_t addr, uint32_t stop_addr);
int (*dirty_ram_is_dirty)(uint32_t phys);
int (*dirty_ram_text_native_ok)(uint32_t phys);
int (*kernel_bless_dispatchable)(uint32_t phys);
int (*game_address_in_text)(uint32_t addr);
int (*fntrace_is_game_started)(void);
void (*fntrace_record)(struct CPUState *cpu, uint32_t target);
void (*trace_dispatch)(uint32_t func_addr);
/* Retail images emit probe sites (SCPH-1001 does; OpenBIOS does not). */
void (*log_probe)(uint32_t pc, struct CPUState *cpu);
/* Segment-aware dispatch: a PC served by another segment's home body is
* recorded in the runtime's segment-miss ring (psx_segment_miss.h). */
void (*segment_miss_record_kind)(uint32_t addr, uint32_t home, uint32_t ra,
uint32_t sp, uint32_t frame, uint32_t kind);
/* Runtime-owned state the dispatcher reads/writes. Never NULL from a
* host at this ABI; the glue keeps local dummies for a pre-init call. */
int (**hle_hook)(struct CPUState *cpu, uint32_t phys); /* bios_hle.c */
int *dispatch_depth; /* psx_bios_backend.c */
uint64_t *static_hits; /* generated game dispatch counter */
uint32_t *current_func_addr; /* debug_server.c */
uint64_t *bail_flattened; /* traps.c */
uint32_t *ram_mask; /* psx_ram_geometry.c (live 2/8 MiB) */
uint64_t *frame_count; /* debug_server.c s_frame_count (miss records) */
} PsxBiosModuleCallbacks;
/* ---- Host side (runtime/src/psx_bios_module.c) ----------------------------- */
#if !defined(PSX_BIOS_MODULE_BUILD)
/* Can this process turn a retail dump into a loaded backend? True when a
* bundled backend is linked (this is a product build, not a setup host) and
* the overlay toolchain is present beside the executable. */
int psx_bios_module_supported(const char *exe_dir);
/* The profile identity a dump of this size/CRC would build with, or NULL
* when no shipped profile matches (then no module can be built). */
const char *psx_bios_module_known_id(uint32_t crc32, uint32_t size);
/* Load a previously built module for this dump if one is cached, else build
* it with the toolchain (blocking; seconds with tcc, minutes with gcc on a
* large image), then load and register it. Returns the registered backend
* or NULL with `err` filled. `exe_dir` is the executable's directory; the
* cache lives under <exe_dir>/cache/bios/. */
const struct PsxBiosBackend *psx_bios_module_acquire(const char *dump_path,
const char *exe_dir,
int allow_build,
char *err, size_t err_cap);
/* 1 when a module for this dump is already built and cached (no build
* needed to select it), 0 otherwise or when the dump is not a known image. */
int psx_bios_module_is_cached(const char *dump_path, const char *exe_dir);
/* Cached module path for a dump identity, whether or not it exists. */
int psx_bios_module_cache_path(const char *exe_dir, const char *stem,
uint32_t crc32, char *out, size_t cap);
/* Load one module file: ABI + codegen-hash gate, init, register. */
const struct PsxBiosBackend *psx_bios_module_load(const char *path,
char *err, size_t err_cap);
/* Callback tables the host hands to a module (also used by the test host). */
void psx_bios_module_fill_callbacks(PsxBiosModuleCallbacks *out);
/* Last build/load diagnostic line, for logs and the launcher. */
const char *psx_bios_module_last_message(void);
/* Progress sink for a build in flight (launcher progress modal). pct is 0..1
* when a stage boundary is known, negative otherwise; msg is the builder's
* current line. Called on the thread running psx_bios_module_acquire. NULL
* clears it. */
typedef void (*PsxBiosModuleProgressFn)(void *ctx, float pct, const char *msg);
void psx_bios_module_set_progress(PsxBiosModuleProgressFn fn, void *ctx);
#endif /* !PSX_BIOS_MODULE_BUILD */
#ifdef __cplusplus
}
#endif
#endif /* PSX_BIOS_MODULE_H */