Commit Graph

671 Commits

Author SHA1 Message Date
Matthew Stanley da093d4374 fix(build): link a BIOS backend only when its generated C provides one
PSXRECOMP_BIOS_STEMS defaults to "OpenBIOS;SCPH1001", so every game link
required a SCPH1001 backend. That backend is compiled from generated C which is
a derivative of a copyrighted Sony BIOS, so it is gitignored and only exists
after a developer regenerates it from their own dump. A checkout that has only
the bundled MIT OpenBIOS -- the documented default, and what a fresh clone gets
-- configured fine and then failed at link with:

    undefined reference to `SCPH1001_psx_bios_backend'

Hit while building Tomba 1, Tomba 2 and MMX6 to validate an unrelated branch;
all three needed -DPSXRECOMP_BIOS_STEMS=OpenBIOS as a workaround.

Filter the stem list to those that actually supply a backend, rather than
flipping the default. Flipping it would silently drop SCPH1001 for developers
who HAVE regenerated it; filtering keeps it fully supported when present and
simply absent when not.

Presence of the files is not a sufficient test. A generated/ tree produced
before the backend-descriptor mechanism existed has both <stem>_full.c and
<stem>_dispatch.c yet defines no descriptor -- exactly the local state that
produced the error above. The check therefore probes for the descriptor symbol
itself, in <stem>_dispatch.c (~1MB, where the emitter puts it) rather than the
multi-megabyte <stem>_full.c, so configure stays fast.

Also report what happened instead of leaving it implicit:
  - STATUS line naming the linked backends
  - STATUS line naming skipped stems with the regen command to fix them
  - FATAL_ERROR, with the OpenBIOS regen command, if no backend resolves at all
    (previously an empty registry would have surfaced as a confusing link error)

Verified: with default flags and a stale local SCPH1001 tree, Tomba 2 now
reports "BIOS backends skipped ... SCPH1001" / "BIOS backends linked: OpenBIOS",
links, and boots.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-27 12:32:30 -07:00
Matthew Stanley 49b57f4178 docs(gpu): correct the MMX6 widescreen comments; record what is safe to delete
Comment-only. No compiled code changes -- the sole non-comment diff line moves
a trailing comment off the g_mmx6_hostside declaration into a block above it;
the declaration itself is byte-identical.

A survey of the mmx6-named widescreen symbols found the existing comments
misleading in a way that invites a dangerous cleanup, so the findings are
recorded in the source next to the code they describe.

1. "Compatibility entry points for already-generated MMX6 sources" (gpu.h) was
   wrong. These are not MMX6 compat shims and are not safe to delete. They are
   the widescreen-2D entry points the recompiler emits for ANY title with a
   [widescreen.bg2d] block, and live generated code in several non-MMX6 repos
   links them today:
     cols/startcol/startx/stream_left/stream_right
         -> CrashBash, MegaManX5, Tsumu (+ -release)
     undercap
         -> Ape, CrashBash, MMX4, MMX5, Tsumu, Vigilante8, THPS2
   Each forwards to its psx_ws_bg2d_* twin, which is the real implementation.
   Only the name is historical. Renaming is not runtime-only: the names come
   from code_generator.cpp, which is in codegen_hash_sources.cmake, so it rolls
   the overlay cache tag and forces regen + reshard of every title.

2. Host-side reveal (g_mmx6_hostside) is dead in every shipping build, which
   the previous "kept inert pending removal" note understated. Nothing sets it
   but the mmx6_reveal debug command, PSX_DEBUG_TOOLS is OFF for Release, and
   no generated source anywhere references a host-side symbol. Noted that
   removing it is the last step decoupling the generic psx_ws_bg2d_* API from
   MMX6 -- mmx6_hostside_active() is currently embedded in the ternaries of
   psx_ws_bg2d_cols/startcol/startx -- and listed the symbols that go with it.

3. BG buffer relocation is a parked FEATURE behind a load-bearing SYMBOL, a
   distinction the old comment did not draw. With g_mmx6_reloc == 0 both hooks
   return their argument unchanged, but MegaManX6Recomp/game.toml names the
   bufbase/cap instruction sites, so the emitter writes calls into generated
   code and psx_ws_mmx6_bg_bufbase is genuinely called by MMX6's generated C.
   The supportable cleanup is to gut the bodies to identity and keep the
   symbols, not to delete the functions.

Removal of (2) and (3) is deliberately deferred: it touches the renderer and
wants its own session with MMX6 revalidation.

Verified: gcc -fsyntax-only on gpu.c exits clean (an earlier draft embedded
"*/" inside a block comment and broke the parse -- caught before commit);
ctest 29/29 green.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-27 11:49:31 -07:00
Matthew Stanley 45408d60f1 fix(build): stop shipping the wrong binary as psxrecomp.exe
recompiler/CMakeLists.txt defined both `psxrecomp` (the user-facing CLI,
src/main_cli.cpp) and `PSXRecomp` (a legacy duplicate of psxrecomp-game, built
from the identical PSXRECOMP_GAME_SOURCES list). CMake treats those as distinct
targets, but Windows and macOS filesystems are case-insensitive, so both wrote
the same file and the last one linked won.

The CLI lost. After a clean build, psxrecomp.exe was the game recompiler,
answering `--config <game.toml>` instead of the documented
`build --disc ... --bios ... --output ...`. tools/build_cli.py packages that
binary into psxrecomp-cli-windows-x86_64.zip, and README's first instruction to
a new user is to download and run it, so the primary onboarding path handed
people a program matching no documentation. Invisible on Linux, which is why it
survived.

Remove the legacy target (psxrecomp-game already covers those sources) and add a
configure-time guard so it cannot recur: the guard compares OUTPUT_NAME
case-insensitively across a directory's targets and fails with an actionable
message. A rename alone would not have prevented recurrence.

The PSXRecomp.exe named in README's Release Package section is a different
artifact, the BIOS-only runtime named via OUTPUT_NAME in runtime/runtime.cmake,
and is unaffected.

Verified: clean build now yields psxrecomp.exe (791KB) printing the documented
`build --disc` interface; no case-colliding pair remains; guard confirmed to
fire by temporarily reintroducing a collision.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-27 11:49:31 -07:00
Matthew Stanley bb5fccea51 fix: make FPS telemetry opt-in 2026-07-27 11:44:31 -07:00
Matthew Stanley 79baa76d0a fix: stop overlay autocompile during shutdown 2026-07-27 11:01:50 -07:00
Matthew Stanley 2696c391b8 feat: add aspect-aware world participation 2026-07-27 10:48:41 -07:00
Matthew Stanley d116e0da3b feat(mods): add presentation interpolation rates 2026-07-27 10:40:49 -07:00
Matthew Stanley d34af54141 feat(mods): support native VBlank rate selection 2026-07-27 10:32:33 -07:00
Matthew Stanley 964cdb3870 feat(mods): expose adaptive display activation 2026-07-27 10:19:07 -07:00
Matthew Stanley fa408aae50 Merge trusted mod plugin support 2026-07-26 21:05:03 -07:00
Matthew Stanley 45dd4210cf Remove Tomba legacy hybrid controller path 2026-07-26 20:52:12 -07:00
Matthew Stanley 9683a1cf8c feat(mods): support mod-owned FMV skipping 2026-07-26 20:34:20 -07:00
Matthew Stanley 27a05dc09e fix(launcher): hide native-only aspect control 2026-07-26 20:26:28 -07:00
Matthew Stanley 99abda239f feat(mods): add pre-runtime activation plugins 2026-07-26 20:18:00 -07:00
Matthew Stanley a4afb828ac feat(mods): add trusted static vblank plugins 2026-07-26 18:59:10 -07:00
Matthew Stanley 8b77d8a730 fix: make overlay autocompile cache-safe 2026-07-26 09:39:24 -07:00
Matthew Stanley 436fe14101 widescreen: support guarded object participation overrides 2026-07-25 23:24:41 -07:00
Matthew Stanley 05e569c121 runtime: add sparse guarded mod fields 2026-07-25 21:42:35 -07:00
Matthew Stanley 62a80eab30 fix(mods): fingerprint CUE data track only 2026-07-25 21:14:28 -07:00
Matthew Stanley 35c1859cf4 Handle feature prerequisites in mod packages 2026-07-25 21:14:28 -07:00
Matthew Stanley 66f2c7be9e runtime: pass active mod context to resolvers 2026-07-25 21:14:28 -07:00
Matthew Stanley ddf3ca8962 feat: add ordered mod values and MIPS pair patches 2026-07-25 21:14:28 -07:00
Matthew Stanley bd460f3ce1 fix: guard complete dynamic patch records 2026-07-25 21:14:28 -07:00
Matthew Stanley eb3b4db599 feat: add bounded integer mod patches 2026-07-25 21:14:28 -07:00
Matthew Stanley 82bb5a21af fix: compose compatible partial mod overlaps 2026-07-25 21:14:28 -07:00
Matthew Stanley 968c9c23ce feat: add feature-oriented mod resolution 2026-07-25 21:14:28 -07:00
Matthew Stanley 50d4f456bf fix: preserve independent mod toggles 2026-07-25 21:14:28 -07:00
Matthew Stanley 9ff287c1fa feat: support multi-option mod conditions 2026-07-25 21:14:28 -07:00
Matthew Stanley 162194dc9b fix: auto-disable conflicting mod packages 2026-07-25 21:14:27 -07:00
Matthew Stanley 6c1bd61a1f feat: derive modded discs from verified stock images 2026-07-25 21:14:27 -07:00
Matthew Stanley 316eaa44a7 feat: add versioned runtime mod packages 2026-07-25 21:14:27 -07:00
Matthew Stanley ef38122068 fix(launcher): honor CLI disc before verification 2026-07-25 21:14:09 -07:00
Rick G 2715b7483a gpu: union native-wide host reject with guest draw area (#79)
Preserve wider guest staging and composition regions while extending framebuffer draw bounds for native-wide margins. Regression-tested in 16:9 on Tomba and Mega Man X6 with bundled OpenBIOS.
2026-07-25 19:42:30 -07:00
OpokXeno 0a8f8ac98c widescreen: stretch backdrop X-stores about screen centre in native-wide mode (#78)
Stretch explicitly configured backdrop X-store sites across the native-wide surface while leaving 4:3, FMV, menu, and unconfigured games unchanged.\n\nValidated by the contributor on Xenogears Battle Mountain and regression-tested on Tomba's Watch Tower flower backdrop with bundled OpenBIOS.
2026-07-25 19:15:30 -07:00
Matthew Stanley 4bf868da4f feat(bios): ship OpenBIOS as a runtime dependency 2026-07-25 18:11:57 -07:00
Matthew Stanley a162aa7fb1 fix(interp): scope load-delay values to BIOS-owned code 2026-07-25 17:02:33 -07:00
Matthew Stanley 986d8be94c Merge remote-tracking branch 'origin/master' into integrate/openbios-bundle
# Conflicts:
#	recompiler/src/full_function_emitter.cpp
#	recompiler/src/full_function_emitter.h
#	recompiler/src/main_bios.cpp
#	runtime/runtime.cmake
#	runtime/src/main.cpp
2026-07-25 12:31:05 -07:00
Matthew Stanley 33589d9c43 feat(bios): show the BIOS row on bundled builds and honour a chosen image
Two runtime-side changes the launcher's SYSTEM row needs.

The row was hidden whenever the linked BIOS was bundled, on the reasoning
that there was nothing to select. With OpenBIOS AND a retail image linked
that strands the player: choosing retail — and clearing back — is exactly
what the row is for. Visibility now asks the registry (is any linked
backend NOT the bundled one?) via psx_bios_has_selectable(), which is
also correct before selection, unlike psx_bios_image.

A BIOS named in settings.toml is likewise now honoured on every build.
It was previously ignored on bundled builds to stop a stale path pointing
at a foreign image, but that guard would now silently discard the only
in-app way to opt into retail. The identity gate is the real protection,
and it explains a mismatch rather than ignoring it. An EMPTY value stays
meaningful in the other direction: that is the launcher's Clear, "go back
to the bundled BIOS", so it is not adopted as a path.

Pairs with recomp-ui feat/bios-row-clear.

Not yet compile-verified against the launcher: building with
PSX_RECOMP_UI=ON on this branch fails in the recomp-ui netplay callback
ABI (main.cpp:4495), which traces to 87491cb and is inherited from this
branch's base predating master's matching integration — the same skew
that stops MMX4/MMX5 building on current master with their pinned
recomp-ui. Needs master merged in, which this branch requires before
merge regardless.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-25 12:23:48 -07:00
Matthew Stanley b0f514c57e feat(bios): pick the BIOS at runtime — OpenBIOS unless the player chose one
Implements docs/BIOS_SELECTION.md on top of the backend ABI. One build
now carries both images and decides at startup, so the per-BIOS build
flavour (and its three uncross-checked restatements) is gone.

  - runtime.cmake takes PSXRECOMP_BIOS_STEMS (default "OpenBIOS;SCPH1001")
    and generates psx_bios_registry.c from it, so the stem list is the
    single source of truth for what a build carries.
  - [runtime] openbios (default true) records whether a title may use the
    bundled image. Not exposed to settings.toml: it is a developer's
    compatibility finding, not a player preference.
  - Selection: an explicit choice that matches a linked image wins;
    otherwise the bundled image; otherwise prompt. A mismatched explicit
    choice is explained and falls back to OpenBIOS when the title allows
    it, instead of dead-ending.
  - validate_bios_for_launch now ASKS WHICH backend an image is, rather
    than checking against one hardcoded identity, and activates it.

Crucially, "no BIOS" means "the player chose none" — not "no BIOS file
was discoverable". The compile-time DEFAULT_BIOS_PATH is no longer
treated as a choice: it made a stray file near the executable produce a
BIOS Mismatch dialog before the bundled image was considered.

Also fixed while wiring this: the settings.toml BIOS-adoption check read
psx_bios_image.image_bundled, which now runs before any backend is
active (all-zero), so it asks the registry instead.

Verified on Tomba! with ONE binary, both paths:
  no --bios          -> OPENBIOS, no dialog, boots to in-game save
  --bios SCPH1001.BIN-> SCPH-1001 (crc 37157331, bundled=0)
and the per-image state follows the selection, not just the label:
kernel-bless window 0x500-0x4BA0 vs 0x500-0x8500, deliver_event_ret
0x0 vs 0x80001720.

Still outstanding: savestates must record the active BIOS and refuse to
load under another (kernel RAM layout differs); a player-facing way to
clear a remembered choice; retiring PSXRECOMP_BIOS_STEM/_PROFILE, which
now only feed the staleness stamp.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-25 12:12:33 -07:00
Matthew Stanley 7e4adff41f feat(bios): backend ABI so two recompiled BIOSes link into one binary
Groundwork for "no BIOS chosen -> OpenBIOS, explicit choice -> that BIOS"
(docs/BIOS_SELECTION.md). Shipping both images in one build removes the
per-BIOS build flavour, and with it the three restatements of the same
choice that nothing cross-checked.

Two recompiled BIOSes could not previously coexist: each generated
dispatch exported 16 globals, 14 of which were identical between images
(16 func_ bodies at coincident normalized addresses, the two dispatch
entry points, the per-image tables, and the dispatch depth).

Each image now exports exactly one descriptor, PsxBiosBackend
<STEM>_psx_bios_backend, carrying its image info, dispatch entry points
and kernel-body table. Everything else it defines is static or
stem-prefixed:

  - func_ bodies and continuations take the stem prefix (fn_sym/cont_sym).
  - psx_dispatch / psx_dispatch_call are renamed in the generated
    preamble rather than at ~20 emit sites; the runtime keeps the
    unprefixed names as forwarders through the active backend, so the
    game's generated C and ~66 runtime call sites are untouched.
  - the image info, kernel-body and native-stub tables become static.
    psx_bios_image and psx_bios_kernel_bodies/_count are now published
    by the runtime from the selected backend, so memory.c and the other
    52 read sites are unchanged (the table is a pointer, indexing is
    identical).
  - g_psx_dispatch_depth moves to the runtime: it is shared dispatch
    state, not per-image, and its duplicate definition was one of the
    collisions.

The generated dispatch now includes psx_bios_backend.h instead of
re-declaring PsxKernelBody / PsxBiosImageInfo / PsxNativeStub locally —
those layouts had to be kept in sync with the runtime by hand, which the
old header comment admitted.

Verified by compiling both images and diffing their exported symbols:
dispatch objects export 3 stem-prefixed symbols each (was 16, 14
shared), full-body objects export 3911 and 13070, and the intersection
in both cases is empty.

Selection policy and the [runtime] openbios setting are not wired yet;
this is the linkability seam only.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-25 11:52:22 -07:00
Matthew Stanley 46f06a5d46 build: make netplay opt-in instead of opt-out
PSX_NETPLAY defaulted ON and auto-discovered a recomp-net checkout from
four candidate paths, so every build linked the delay-sync library. The
lobby WebSocket client was worse: gated on nothing but its source files
existing, i.e. always compiled.

These are single-player titles. Neither belongs in their binaries.

Default PSX_NETPLAY to OFF and gate the lobby client on it too. A
multiplayer title opts in with -DPSX_NETPLAY=ON, which still links
recomp-net and the lobby client exactly as before (verified both
directions on Tomba).

ws2_32 stays unconditional: the TCP debug server needs sockets
regardless of netplay.

psx_netplay.c / psx_lobby_client.c still compile as their existing
no-op stubs rather than being #ifdef'd out — main.cpp has 74 call sites
and they are already written to stub cleanly, so guarding every one of
them would be a large, riskier change for a small object-size win.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-25 11:11:56 -07:00
Matthew Stanley 7ee8266c75 fix(interp): forward the pending load to a paired LWL/LWR
Regression from the load-delay commit: Tomba! 2 wedged at boot on a black
screen (steady ~62fps, so running but rendering nothing).

`lwl rt,x` immediately followed by `lwr rt,y` is THE unaligned-load idiom
every MIPS compiler emits, and MIPS-I explicitly permits the pair
back-to-back -- the second half merges with the first's result, not with
the pre-load register value. Deferring the writeback past the slot made
the merge read a stale rt, silently corrupting every unaligned load that
went through the interpreter.

Retire the pending write early when the delay-slot instruction is
LWL/LWR targeting the same register. That is forwarding, matching the
hardware special case, not a shortcut around the delay slot.

Bisected against Tomba! 2's pinned framework commit (68667cd), which
boots to gameplay: same fresh worktree and cold overlay cache, so the
overlay-cache difference was ruled out as the cause. Reverting only the
load-delay commit on this branch also booted, which isolated it here.

Verified after the fix:
  - Tomba! 2 (SCUS-94454) boots to gameplay again.
  - Tomba! (SCUS-94236) on OpenBIOS still shows the memory-card pointer
    advancing 1:1 (129 distinct addresses per frame, not 2), so the
    original card fix is intact, and still reaches its title screen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-25 01:00:54 -07:00
Matthew Stanley fdfad8f567 cdrom: fail loudly when a requested disc does not mount
Root cause of the "black screen on a .cue that works as a .bin" reports.

cdrom_init() calls iso_open() and, on failure, simply leaves the drive empty
(stat_reg = CDSTAT_SHELL). That is correct for a BIOS-only target, which runs
with no disc on purpose -- but when a game was GIVEN a disc path and the image
does not mount, the game boots against an empty drive and renders nothing. No
error, no log, just black.

The earlier validate_disc_for_launch() pass cannot catch this, because the two
sides read the disc differently: identify_disc() opens the data-track FILE
directly, so it reported a green "Disc verified" badge for cues that the
MOUNTING reader then rejected. Verified-but-unmountable was a silent state.

Demonstrated against the pre-fix reader (a8aa640) on one cue:

    lowercase/unquoted cue -> FAILS  (empty drive -> black screen)
    canonical cue          -> MOUNTS
    bare .bin              -> MOUNTS  <- why picking the .bin "fixed" it

So the symptom was never about cue-vs-bin semantics; it was cue PARSING
strictness. The old mount-side parser required the literal uppercase "BINARY"
and a quoted filename, while the verify side uppercased the line first. The
shared cue parser landed in 68667cd already fixes that class (case-insensitive
keywords, unquoted names, CRLF, case-insensitive payload lookup), which is why
all four titles now boot from either file.

This commit closes the remaining hole -- any OTHER reason a mount fails must
not present as a black screen:

- cdrom_has_disc() exposes the drive state (has_disc() was file-static, so no
  caller could tell a successful mount from an empty drive).
- main() checks it right after cdrom_init() and, when a disc path was supplied
  but nothing mounted, reports what was selected, what it was mounted as, the
  resolver's note, and that the .bin can be selected directly -- then exits
  instead of booting into black.

Also pins the regression: the lowercase/unquoted cue must MOUNT, not merely
parse, so the verify side and the mount side can never silently disagree again.
test_disc_path_resolve: 36 -> 38 checks.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-25 00:52:55 -07:00
Matthew Stanley 5534406f10 debug: report interpreter fail-closed detail and kernel scratch regs
Two observability gaps hit while debugging the memory-card corruption
above; both are serializer-side, the data was already recorded.

crash_trace: the report carried cpu->pc but not the dirty-RAM
interpreter's unsupported-instruction block. A run that died there
showed only pc == 0, and ten separate sites set that, so the cause was
unattributable. Emit interp_unsupported{pc,insn,reason,block_entry,
entry_ra,entry_sp,insns_into_block,midblock_count}.

dirty_ram_interp log: the per-instruction ring captured v0/v1/a0-a3/
t0-t2/sp/ra but not $at or $k0/$k1 -- precisely the registers that
matter for BIOS exception-handler code, which is most of what this
interpreter runs, and the ones the cardfasttrack.s load-delay idiom
turns on.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-25 00:12:08 -07:00
Matthew Stanley 5e73b1c18c fix(interp): implement R3000A load-delay value semantics
The dirty-RAM interpreter modelled the load-delay *interlock timing*
(psx_cyc_load_*) but wrote the destination register immediately, so the
instruction in the load-delay slot observed the loaded value. On MIPS-I
there is no interlock: the slot still sees the PREVIOUS value and the
writeback lands one instruction later.

That silently diverged from both real hardware and our own compiled
backend, which defers the writeback past the successor for dependent
pairs (full_function_emitter). Hand-written kernel asm depends on it.

Concrete casualty: OpenBIOS's cardfasttrack.s stashes the card pointer
variable's BASE with `or $at,$k0,$zero` in the delay slot of
`lw $k0,off($k0)`, then writes the advanced pointer back through $at.
With an immediate writeback $at became the pointer itself, the write-back
landed in unrelated memory, and the pointer never advanced -- every byte
of a 128-byte memory-card frame was stored to one address. Cards read as
permanently unformatted (reformatting could not fix it, because the READ
was what was broken) and the send side transmitted one byte 128x,
corrupting the card header to 'M' repeated.

Measured on Tomba! (SCUS-94236) against the card_data_writes ring:
before, one address repeated 124-127x per frame; after, 129 distinct
addresses advancing 1:1 with the protocol index -- matching a retail
SCPH1001 build byte for byte in behaviour. Save-and-load across two
memory-card slots verified in-game.

Implemented as a shim around the decoder so no individual load case
changes. Notes on the semantics:

  - A branch's delay slot is executed nested inside the branch, and is
    N+2 relative to the load, so it must observe the LOADED value. The
    retire therefore happens on entry to the next instruction rather
    than after the previous one returns.
  - Back-to-back loads retire the older writeback before reusing the
    pending slot, otherwise its register write is dropped.
  - Same-register conflict: the load wins (it retires later), matching
    what the compiled backend emits for a dependent pair.
  - Pending state is retired on interpreter exit and before exception
    delivery, where the pipeline would have drained.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-25 00:11:52 -07:00
Matthew Stanley 68667cd58b disc+bios: either file the user picks resolves, mounts, and verifies
Two path-resolution defects where the runtime silently accepted only one of
two equally-valid spellings of the same input.

1. Disc: picking the .cue threw away the disc's TOC.

normalize_disc_path_for_launch() swapped any .cue for its same-named .bin
unconditionally, and every disc entry point (game.toml, --disc, the cached
disc.cfg, the launcher's Change Disc, the first-run dialog) went through it.
That discarded the cue's table of contents -- every CD-DA track and pregap
with it -- even though iso_reader.cpp has full multi-FILE cue support that
the swap defeated before it was ever reached. In the other direction,
picking a redump audio-track bin mounted that track as the whole disc, which
cannot boot.

resolve_disc_path() (runtime/include/disc_path.h) now canonicalizes any pick:

  .cue that resolves            -> mount the cue (TOC intact)
  .cue with a missing bin       -> the payload it still names, else a
                                   stem-matched sibling image
  .bin/.iso/.img owned by a cue -> mount that cue
  bare image, no cue            -> mount it

identify_disc() resolves through the same code, so the header/serial/CRC32
verdict is read from the data track no matter which member was picked.

While here: the mount path and the verify path had two divergent private cue
parsers (quoted-only + case-sensitive vs quoted-or-bare + case-insensitive),
so they could disagree about the same file. Both now use one parser
(runtime/include/cue_sheet.h), which additionally handles CRLF, backslash
paths, and case-insensitive payload lookup for case-sensitive filesystems.

2. BIOS: the multi-BIOS rename made configs and dump folders incompatible.

SCPH*.toml name "US-PSX-SCPH1001.BIN" while every pre-existing dump folder,
bios.cfg and tools/regen_bios.sh default name "SCPH1001.BIN". Either
combination fataled with "cannot open BIOS file" despite the right ROM being
present. resolve_bios_rom() (recompiler/include/bios_rom_alias.h) matches on
the model token -- the last '-'-separated component of the stem -- so both
conventions resolve in both directions without a hardcoded alias table.
Applied at the config loader, the positional psxrecomp-bios CLI, and each
rung of the runtime's BIOS search.

The positional CLI also derived out_stem from the raw filename, so a
region-qualified ROM would have emitted US-PSX-SCPH1001_full.c -- a name
runtime.cmake cannot link. It now derives the model token.

Also fixes SCPH5552.toml, whose rom field omitted the bios/ directory that
its two siblings have.

Tests: test_disc_path_resolve (36 checks, five dump layouts) and
test_bios_rom_alias (15 checks, both directions + negative cases), both
registered in runtime/CMakeLists.txt. Registered the pre-existing
test_iso_reader_cdda, which was wired into no build.

Verified on real dumps: Ape Escape .cue and .bin both give
SCUS-94423 / NTSC-U / crc=C6F455BC; Tomba 2's multi-track dump gives
crc=1FB8C151 and a 2-track TOC from the .cue, the data bin, and the CD-DA
bin alike.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-24 22:17:52 -07:00
Matthew Stanley a8aa640b79 runtime: name the exact CLI flag when no file picker is available
On macOS/Linux there is no native GUI file picker, and in headless mode a
picker would block, so pick_runtime_file() bails when it can't resolve the
BIOS/disc interactively. The old message ("<title> requires a command-line
path on this platform") never said WHICH flag to pass, so a first-time
from-source user on Linux hit "no BIOS selected; exiting." with no idea what
to do.

Thread the responsible flag (--bios / --disc) into pick_runtime_file and print
an actionable message with the flag and a full example command, on both the
headless and the no-GUI-picker paths. Points at the game README's "Building
From Source" for the full run line.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-24 13:02:44 -07:00
Matthew Stanley bfa760f43b build: fail early with a clear message when recompiled game C is missing
The runtime links generated/<serial>_{full,dispatch}.c, produced by a separate
recompiler step run before the build. Those paths are marked GENERATED so cmake
configure succeeds before that step has run — which meant skipping generation
surfaced only as a raw "cc1: fatal error: <file>: No such file or directory"
deep in the build, with no hint that a step was skipped or what produces the file
(exactly the trap a from-source builder on a fresh checkout hits).

Add runtime/check_generated_sources.cmake and wire it into
psxrecomp_add_runtime_target as a build-time dependency of the runtime target:
a missing game source now aborts at build start with the files named and the
exact fix (build recompiler -> extract EXE -> generate -> rebuild), and configure
emits an early non-fatal warning. Framework-wide, so every game inherits it.

Also fix the docs/BUILDING.md "build and run a game" recompiler invocation (the
framework is a submodule at psxrecomp/, not ../psxrecomp/) and add the
recompiler-build step.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-24 09:36:14 -07:00
NyperYuhgard 2ebb2b1a6b Multi-Bios (seeds required) 2026-07-24 01:38:56 -05:00
Alex Vanderveen 09067f29dc fix title bar separator character 2026-07-23 19:05:30 -04:00