The stb launcher revamp (8b098bd, ae89a8f, 89e0e0f) uses modern GL 3.0+
without the SDL_GL_GetProcAddress loader ('Phase 2b'), so it fails to
link into game builds, and its rendered UI is not acceptable (user
review 2026-07-10). Revert master to the working RmlUi launcher — the
same revert every recent release (Ape v0.0.2, Tomba2 v0.0.2) already
shipped with. The stb work remains reachable via the original commits
if a Phase-2b GL-loader follow-up ever revives it.
(cherry picked from release/ape-v0.0.2-rmlui 929b78b)
For a title whose controller type is fixed and auto-bound (e.g. Ape Escape =
DualShock analog), exposing a device/mode picker is pointless. New per-game
[controller] lock_device flag hides the Player 1/2 cards in the launcher
entirely (distinct from lock_mode, which only hides the pad-mode segment but
keeps the device dropdown). Plumbed config_loader -> RuntimeConfig ->
GameInfo.lock_device -> launcher model `device_locked` -> rml
`data-if="device_locked == false"` on the players cards-row. Default false, so
every other game is unchanged. The runtime still auto-binds whatever pad/keyboard
is present and presents the game's declared pad type.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A title whose widescreen is unported/unvalidated (MMX4 at bring-up) had no
way to hide the launcher's EXPERIMENTAL Widescreen toggle — ws_eligible was
hardcoded true. Add game.toml [widescreen] offer (default true, runtime-only,
no regen):
- offer=false hides the launcher's Widescreen row and resets a stale
persisted 16:9 aspect back to 4:3 at launcher-model init;
- the runtime also clamps the display aspect to 4:3 after all config
sources (same completeness treatment as the lock_mode clamp), covering
launcher-less builds and hand-edited settings.toml;
- refresh_labels() no longer stomps ws_eligible every refresh — it is set
once from GameInfo.ws_offered at model init.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds a general, opt-in language selector to the RmlUi launcher, driven entirely
by game.toml [localization]:
[localization]
default = "en"
languages = [ {code="en", label="English"}, {code="off", label="Default"} ]
- config_loader: parse [localization].default + languages[] into RuntimeConfig;
add UserSettings.language with settings.toml [localization] parse + write.
- launcher: a "LOCALIZATION" panel in the Settings view (cycle toggle, matching
the Renderer/Screen-model idiom) shown only when the game declares languages.
The chosen code persists to settings.toml and rides UserSettings.
- text_xlate: new text_xlate_set_language() re-applies the language after the
launcher (init runs before it); "off"/"jp"/"" disable APPLY (native game).
- main: seed/read-back the language through the launcher and re-apply on every
boot path (so a settings.toml choice sticks even when the launcher is skipped).
- Settings view laid out in two columns (VIDEO | LOCALIZATION+AUDIO+CONTROLLER)
with SYSTEM full-width below, so it uses the horizontal space and fits without
scrolling; #root gains overflow-y:auto as a safety net.
Only games that declare [localization] get the menu (Tsumu today); byte-identical
for every other title.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
RmlUi launcher grows a Settings surface (renderer / supersampling / AA /
texture filter / screen model / skip-FMV / turbo / fullscreen / widescreen /
SPU-HQ / deadzone / BIOS / disc) and a live Controls rebind page. Bindings
persist to keybinds.ini through the new shared runtime module
runtime/include/psx_keybinds.h + runtime/src/psx_keybinds.c; main.cpp routes
SDL input through the rebindable table. Ape Escape is the first consumer;
the feature is game-agnostic framework code.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Releases ship [runtime] bios_hle = true, so the boot skip is on by
default and no longer a per-user launcher setting (the old toggle also
described the retired snapshot mechanism's "first boot still loads"
behavior, which no longer exists). Power users can still set bios_hle /
fast_boot in settings.toml or game.toml by hand.
Also aligns CLAUDE.md: the session-start mantra now points at the §0
amendments instead of a blanket "No HLE", and the 2026-07-02 amendment
names the real config key ([runtime] bios_hle).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
For a game that supports exactly one pad type, the launcher's 3-way pad-mode
selector (Hybrid | Analog | D-Pad) offers modes that don't work. New
game.toml [controller] lock_mode = true hides the whole selector and forces
every port to default_mode, so the player can't pick a broken mode.
Tomba 2 is the first user: its driver only works as a plain digital pad
(the DualShock config-mode handshake emulation is incomplete — see sio.c
pad_supports_config / commit 1141f62), so analog/hybrid re-trigger the
phantom-input bug. lock_mode supersedes allow_hybrid (which only hid Hybrid).
Plumbing mirrors allow_hybrid: config_loader (controller_lock_mode) -> main.cpp
(ginfo.lock_mode/locked_mode) -> launcher (m.mode_selectable + force p1/p2 mode,
overriding stale settings.toml) -> launcher.rml (data-if="mode_selectable" on
both .seg selectors). Verified: compiles in a PSX_LAUNCHER=ON build (build-t2-launcher).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Surfaces the boot-snapshot feature as a launcher setting instead of a
game.toml-only opt-in. Mirrors the turbo_loads toggle end-to-end: UserSettings
field (has_fast_boot/fast_boot, default false), config_loader read/write,
main.cpp apply/seed/seed-apply, launcher.cpp model field/bind/toggle/io-write.
Reuses the existing [runtime] fast_boot key as the persisted seed; nothing
renamed. A hint (reusing .ws-hint, shown only when On) notes the first boot
still loads to build the cache and is instant after. Launcher/config only; no regen.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds two launcher controls requested for Tomba (issue #6):
- Fullscreen on launch: new [video] fullscreen setting, cloned through the full
turbo_loads pipeline (UserSettings field + parse/save, launcher model/bind/
toggle/commit, main.cpp seed/read), applied as SDL_WINDOW_FULLSCREEN_DESKTOP at
game-window creation. The in-game F11 / Alt+Enter hotkey is unchanged.
- Skip Launcher on Boot: a footer switch (bottom-left) that boots straight into
the game on subsequent launches, with a (first-in-this-launcher) confirmation
modal explaining the --launcher escape hatch. The runtime skip_launcher round-
trip + --launcher / PSX_NO_LAUNCHER already existed; this wires the UI to it.
Also drops the redundant footer Settings/Back links (the top bar already has
them). Mirrors SuperMarioWorldRecomp's feature.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
allow_hybrid (default true) lets a game hide the "Hybrid" pad mode from the
launcher's segmented selector, leaving Analog | D-Pad. Parsed in config_loader,
threaded through GameInfo into the launcher data model, and gated in launcher.rml
with data-if="allow_hybrid" (mirroring ws_eligible). When hidden, a port that
would default to Hybrid is clamped to Analog so the selection stays valid. Games
that hard-require a DualShock (e.g. Mega Man X6) set [controller] allow_hybrid =
false; Tomba and others are unaffected.
Also remove the "much faster loading, audio plays through" hint under the Turbo
loads toggle — the On/Off control is self-explanatory.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Turbo-through-loads no longer mutes audio (the mute/fade was removed), so it is
now the right default: fast loads with audio playing through and all guest
timing preserved. Flip the launcher/settings fallback default to ON
(LauncherModel + UserSettings) and refresh the now-stale "briefly mutes audio"
launcher hint to "much faster loading, audio plays through". The per-game
game.toml value still seeds the launcher toggle and a user choice in
settings.toml still overrides it.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Exposes turbo-through-loads as a launcher Settings option instead of a
game.toml-only knob. Turbo runs the guest unpaced + mutes audio while the CD
data stream is active (faster loads, but a brief audio cut/fade). Now defaults
OFF so loads play at natural 60fps with continuous audio (disc_speed=instant
already keeps data delivery fast); users opt into turbo via the launcher.
Wired through the full settings chain, mirroring auto_skip_fmv: UserSettings
turbo_loads/has_turbo_loads, settings.toml read+write, runtime override/seed/
apply over game.toml [runtime] turbo_loads, launcher data-model + toggle event,
and the RML row. Runtime/launcher only — no regen, codegen unaffected.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Pre-existing uncommitted launcher.cpp work (~50 lines, inert) carried on bug/recursion;
committed for a clean tree as the branch lands. No CPS dependency.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
SW renderer: add the same gated centre-stretch the GL renderer does. wide_bd_get()
resolves a per-prim scale (psx_ws_prim_in_backdrop gate + g_wide_w/native_w about
g_wide_cur_base+native_w/2, matching wide_set_bd_scale) and wide_bd_x() maps each
wide-mirror vertex x. Applied to every wide-mirror prim: flat/gouraud/textured/
shaded-textured triangles (per-vertex), and flat/textured/scaled rects (scale
x+width). Textured rects route through the SCALED rasterizer when stretching so a
widened sprite stretches its texels instead of tiling. Identity for non-backdrop
prims and at 4:3, so software output is unchanged outside native-wide backdrops.
launcher: drop the "Tomba Special Edition" reference from the Hybrid pad tooltip.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replaces the analog on/off boolean with a per-player PadMode {hybrid, analog,
digital}. Hybrid (default) auto-switches the reported pad from the most-recent
input: nudge the stick -> DualShock (0x73, variable sticks); press the D-pad ->
digital pad (0x41) so the game runs its own digital path at true d-pad
sensitivity -- mirroring a DualShock's analog LED and Tomba Special Edition's
auto-detect. analog pins 0x73; digital pins 0x41.
config_loader parses [controller] default_mode / p1_mode / p2_mode (legacy
default_analog / p1_analog / p2_analog booleans still accepted); the launcher
exposes a Hybrid|Analog|D-Pad segmented selector per player with a tooltip;
settings persist as p1_mode / p2_mode.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
.card used flex-basis auto (content-sized), so selecting a controller — which
lengthens that card's device/status text — widened Player 1's box relative to
Player 2. Use flex-basis 0 + min-width 0 so the row always splits evenly
regardless of content.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Controller (item 3): present an analog (DualShock) pad by default for
analog-capable games so the left stick gives variable run speed; fold the
physical D-pad / keyboard arrows onto the left analog axes at full deflection so
they still move the character (avoids the Special Edition full-throttle-D-pad
bug). Add a launcher-editable analog-stick deadzone option (settings.toml +
input.ini + game.toml [controller] default), modeled on snesrecomp.
Settings persistence (item 6): deterministic restore-at-init + save-on-exit for a
game's native OPTION-screen settings, declared per game in a dedicated
game_options.toml. The recompiler rewrites each setting's boot-init store (sb/sh)
to route its value through psx_game_option_store(addr,val), applying a value
persisted from a prior session over the game default (identity when none, so a
fresh install is byte-identical). The runtime saves the live config globals to
<memcard_dir>/<game_id>.options on exit (atexit). Adds a game_options debug
command. Requires a regen for the hook to emit.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Widescreen toggle was hidden on OpenGL (ws_eligible = renderer==software) and
switching to GL force-reset aspect to 4:3, because native-wide was SW-only. The
GL wide compositor now works, so:
- ws_eligible is always true (toggle shown on both renderers),
- cycle_renderer no longer forces aspect to 4:3,
- toggle_widescreen no longer early-returns on GL.
This also fixes the "Window size stays 1280x960" report: with the toggle a no-op
on GL (and aspect force-reset), the aspect never changed, so the size label (and
the actual window) stayed 4:3. Both are already computed from the aspect
(width x width*den/num), so flipping widescreen now yields 1280x720 for the label
and the real window. No hardcoded sizes.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The launcher widescreen toggle is settled at Off / 16:9. 21:9 stays supported in
the engine (all aspect-derived: cull, compositor, present) but is intentionally
NOT exposed: the parallax/far-backdrop pipeline only generates ~16:9 of coverage,
so 21:9 voids the far background (8C generation work, possibly bounded by the
game's authored coverage). When that is widened, promote the 2-state toggle to a
3-way Off / 16:9 / 21:9 (cycle_aspect already cycles aspect_index 0/1/2 = the
scaffold). Comment at toggle_widescreen + status section in WIDESCREEN.md.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a "Widescreen (Experimental)" On/Off toggle to the launcher VIDEO panel,
modelled on snesrecomp's experimental widescreen option. On => 16:9 native-wide
(aspect_index 1), Off => 4:3; default off. One exe serves both.
Native-wide lives in the software compositor only, so the toggle is gated on the
renderer: it is shown (ws_eligible) only when Renderer == Software and hidden
entirely on OpenGL (which would present stretched until the GL compositor port).
Switching the renderer to OpenGL forces aspect back to 4:3 so a wide aspect can
never be carried onto a renderer that cannot present it. The row sits at the
bottom of the VIDEO panel so hiding it never reflows the rows above.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
One build serves both presentations: the launcher Settings panel gets
an Aspect ratio toggle wired to the existing UserSettings aspect
plumbing (persists to settings.toml, overrides game.toml). Window-size
label height now follows the chosen aspect. Default 4:3 (Native).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds an opt-in "skip full-motion videos" feature ([video] auto_skip_fmv,
default off). A streaming FMV is detected as active MDEC decode
(mdec_get_decode_count climbing per vblank) + cdrom_xa_stream_active();
while detected, the frontend injects START into pad slot 0 so the game's
OWN FMV player aborts the movie and jumps cleanly to the next screen —
instant, invisible, and desync-free, with all guest state correct.
Mechanism notes (why this and not the alternatives):
- Flooding XA sectors ('instant' CD during streaming) desyncs the player
and hangs the movie on a white screen — rejected.
- Uncapped-pacing fast-forward is safe but slow and choppy (plays the
whole stream) — rejected.
- Triggering the game's native skip is instant and correct. Injection is
gated on raw xa_stream_active so START stops the moment the FMV ends and
never bleeds into the next screen (verified: title stays interactive).
Present/pacing are suppressed + audio muted as a safety net for the 1-2
frames before the game's abort takes effect.
Plumbed end-to-end: config_loader [video] auto_skip_fmv + UserSettings
(settings.toml load/save), main.cpp detector/injection + settings layering
+ launcher seed/apply, and a "Skip FMVs" toggle in the launcher Settings
(launcher.cpp + launcher.rml), mirroring the existing SPU/AA toggles.
Runtime-only; no emitter/regen impact.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The game window was hardcoded to 640x480, so it booted into a tiny window
while the launcher was much larger — a jarring shrink on LAUNCH. Add a
window-size setting that both windows honor, with a default that matches.
- config_loader: settings.toml [video] window_width (height always w*3/4 to
keep the PSX 4:3 aspect); load/save, validated 640..3840.
- main.cpp: the game window now opens at the chosen size (default 1280x960)
instead of 640x480; the launcher opens at the same size (floored at 1280
wide so the dense dashboard stays usable). clamp_window_4x3() fits an
oversized choice (e.g. 1920 on a 1080p panel) to the display's usable area.
Both present paths already scale a 640x480 logical space to the window, so
the larger window just fills with no aspect distortion.
- launcher: "Window size (4:3)" toggle in the VIDEO settings cycling
960x720 / 1280x960 / 1600x1200 / 1920x1440; seeded from settings, persisted
on launch.
Verified: default launcher and game windows both open at 1280x960 (matched,
no resize jump); settings.toml window_width=960 boots the game at 960x720 and
the PS logo renders correct 4:3 with no distortion.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Per-player input routing with a real device dropdown (None / Keyboard /
each plugged-in SDL controller) and a digital-vs-DualShock pad-type toggle,
wired end-to-end through the runtime.
- sio.c/.h: per-slot pad state (pad_buttons[2]) + per-slot pad type. Analog
poll emulation (id 0x73 + 4 stick axes) when a slot is in DualShock mode;
digital (0x41) stays the default so existing games are unaffected. New
setters: sio_set_pad_state_slot / sio_set_pad_analog / sio_set_pad_connected,
getters sio_get_pad_{buttons,connected,analog}_slot.
- config_loader: settings.toml [controller] p1_device/p2_device (none/keyboard/
<SDL GUID>) + p1_analog/p2_analog; load + save round-trip.
- main.cpp: replaced the single auto-opened controller with a two-slot
PlayerInput model. Devices opened by GUID (with a first-free fallback so a
re-paired pad still works), keyboard or controller feeds each PSX port, and
analog stick axes drive the DualShock poll. Hot-plug re-resolves both slots.
Defaults: P1 keyboard/digital, P2 none.
- launcher: enumerates SDL controllers (name+GUID); each player card has a
custom click-to-open dropdown (option list injected via data-rml, the
data-for path being unreliable in this RmlUi build) and a DualShock switch
that drives the pad art (digital/analog) + status line. Zero-config default
selects the first plugged-in pad. A saved-but-unplugged controller is kept
as an "(offline)" option so the selection survives. Choices persist on launch.
- debug_server: pad_status now reports both slots' buttons + connected + analog.
UI polish from review:
- Removed the redundant disc-verdict widget (the green checks already convey it);
Change ISO now anchors to the disc panel instead of floating at the page top.
- Added a prominent "Settings" button in the top bar (renderer/SS/AA/etc. were
only reachable via the easily-missed footer link).
- Fixed the player-card asymmetry on toggle: status text no longer wraps and the
player card body is a fixed height, so the pad art renders at a consistent size
(measured 216x110 analog vs 219x110 digital) regardless of pad type.
Verified: device dropdown enumerates a real DualSense; selection + DualShock
toggle update the card; pad_status confirms per-slot connect + analog routing
(slot0 analog / slot1 digital and the reverse); default keyboard P1 unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Wire the launcher's Memory Card 1/2 panels to real on-disk data and make
the per-card enable/path choices flow through to the runtime.
- memcard.c/.h: add stateless introspection (memcard_summary_path parses the
directory frames for used-block count + mtime + validity, no live state),
memcard_format_file for "New", and memcard_init_slots for per-slot paths +
enable flags. memcard_init now delegates to it.
- config_loader: settings.toml [memcard] gains card1/card2 (explicit paths)
and enable1/enable2; load + save round-trip them (UserSettings extended).
- main.cpp: resolve the effective memcard dir before the launcher so it can
introspect the real files; seed/apply per-card paths+enable; boot via
memcard_init_slots (a disabled slot is left absent — SIO reports no device).
- launcher: MC cards now show real filename, size, used-block count, last-
modified date, and a live 15-cell block grid; Browse picks an existing
.mcd, New formats a fresh one (overwrite-prompt save dialog). Per-card
enable + paths are committed to settings on launch.
- The block grid is injected via data-rml (the structural data-for view does
not capture inner-xml in this RmlUi build; data-rml is the robust path and
the markup is fully launcher-controlled).
- Add LauncherSystemInterface so RmlUi warnings/errors reach stdout (they
otherwise go to OutputDebugString on Windows); drop an unsupported
text-overflow RCSS declaration it surfaced.
Verified on Tomba: card1 3/15 + card2 1/15 match the on-disk directory
frames; disabling slot 2 in settings.toml leaves it not-present via mc_status.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the procedural placeholder art (disc/controllers/memory card/
logo) with the real PlayStation hardware cropped straight out of the
reference mockup, with the dark background knocked out to transparent so
each asset blends seamlessly on the panels — no seams, no border-glow
artifacts.
tools/crop_launcher_assets.ps1: crops the five assets from the mockup
and removes the background. Photo assets use an edge flood-fill (flood
the dark background inward from the borders; bright object pixels are
walls, so interior dark detail — the disc's PlayStation logo and centre
hole, the controller buttons — is preserved). The logo uses a global
luminance key (thin bright glyphs on near-black). PowerShell [int] of a
division rounds, not floors — the flood uses [Math]::Floor for the row
index.
launcher.rml: add `contain` fit to the photo decorators so the crops
keep their aspect ratio, and nudge the art sizes closer to the mockup's
proportions (disc 216, pads 206x128, memcard 118).
The check/verdict icons remain the generated PNGs (already clean +
transparent). Pixel-verified: dashboard renders with authentic disc,
PS1 digital pad, DualShock, and memory cards, all seamless on the panels.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rebuild the launcher as a card-based dashboard (n64recomp-style) per the
reference design, replacing the flat settings list.
Image pipeline: RenderInterface_GL3 only decodes TGA, so subclass it
(LauncherRenderInterface) and override LoadTexture to decode PNG via a
vendored stb_image (third_party/stb_image.h, STBI_ONLY_PNG, separate
impl TU), falling back to the base TGA path. This is done without
touching the pinned RmlUi submodule. RCSS now uses decorator:image(...)
for all art.
Layout (launcher.rml):
- Top bar: PS-glyph logo + "<game> Recomp".
- Disc Verification panel (Phase 3 — fully wired): disc art, FILE /
REGION / SERIAL, a three-check VERIFICATION RESULTS row
(Header Match / CRC-Hash Match / Disc Verified, driven by the
DiscIdentity fields) and a colour-coded verdict (icon + title +
detail). "Change ISO" re-runs verification.
- Player 1 / 2 and Memory Card 1 / 2 cards: full visual shell with
live enable toggles; controller enumeration (Phase 5) and memcard
introspection (Phase 4) wire in later — the rest is placeholder.
- Settings view (footer "Settings" <-> "Back"): the video / audio /
system controls moved off the home screen.
- Footer: Settings, recompiler-ready status, LAUNCH GAME.
launcher.cpp: model gains disc_file/region/serial + v_header/v_crc/
v_verified + verdict_{title,detail,state}, a view toggle, and card
enable bools; refresh_disc_status maps DiscIdentity onto the three
checks and the verdict; new events change_iso / show_settings /
show_dashboard / toggle_p1.p2.mc1.mc2.
Window bumped to 1280x924 for the dashboard. tools/gen_launcher_assets.ps1
generates the placeholder art (disc, controllers, memory card, PS-glyph
logo, check/verdict icons) into assets/img — swap for real renders later;
the PNG pipeline is identical.
Verified end-to-end on Tomba: dashboard renders with all three checks
green + "Tomba! disc verified"; Settings view and Back work; player /
memcard cards and toggles render.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a shared disc-identity module (runtime/{include,src}/disc_identity)
that opens a .cue/.bin/.iso, checks for the ISO9660 PVD (raw 2352 and
cooked 2048 layouts), reads the PVD volume id, parses the boot serial
from SYSTEM.CNF, derives the region from the serial prefix, and
optionally streams a full-file CRC32 and compares against expected
serial/CRC. This is now the single source of truth for "is this the
right disc?" — main.cpp's launch-time validate_disc_image is rewritten
to delegate to it (dropping its duplicated raw-read logic and the now
dead cue_data_path/is_iso_pvd/psx_exe_id helpers) so the launch check
and the launcher badge can never drift.
game.toml [game] gains optional disc_crc / disc_sha1 (GameConfig +
config_loader). Left unset by default: the serial check is dump-
agnostic, whereas a baked CRC would flag every legitimate dump of a
different revision/region.
Launcher: GameInfo carries the expected serial + CRC; the launcher
recomputes verification on entry and on every Browse, showing a
colour-coded badge (Serial verified / Disc verified / CRC mismatch /
Wrong game? / Not a PlayStation disc) plus a detail line with the
detected serial · region · CRC. CRC is only hashed when an expected
CRC is configured.
Verified all three tiers on Tomba: Serial verified (no CRC),
CRC mismatch (wrong CRC), Disc verified (CRC B00ECB0D match).
tools/shot_launcher.ps1: bring the launcher GL window to front and
screenshot its client area (GL windows screenshot black via PrintWindow).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rework launcher.rml/RCSS so the UI is presentable instead of the
default inline-flow overlap. RmlUi does not default custom elements to
block, so h1/.subtitle/.panel/.section-title now set display:block, and
each setting .row is a flex row: fixed-width label + flexing .control.
Toggle/cycle buttons share a fixed .toggle width so the value column
aligns; BIOS/Disc paths flex-grow with ellipsis overflow and show a red
"(not set)" placeholder when empty. Actions row (flex) makes LAUNCH GAME
the primary full-width button with Quit alongside. Content is centered
in a fixed-width column.
Pixel-verified: three card panels render cleanly with aligned controls,
seeded from the game's effective config.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add an integrated configuration launcher (n64recomp-style) that runs
before the emulator boots, so users pick renderer/SS/AA/colour-model/
SPU/BIOS/disc from a GUI instead of editing TOML by hand.
runtime/launcher/launcher.{h,cpp}: psx_launcher::run(window, glctx,
UserSettings&, game_name, assets_dir) -> {Launch, Quit, Unavailable}.
Base RenderInterface_GL3 (no SDL_image dep) + SystemInterface_SDL +
RmlUi data-model binding; Win32 comdlg32 file picker for BIOS/disc.
Caller owns the window/context (clean seam for a future runtime
"re-open settings" overlay).
assets/launcher.rml (embedded <style>) + bundled LatoLatin fonts.
NB: the TTF family name is "LatoLatin", not "Lato" — RCSS must match it.
runtime.cmake: PSX_LAUNCHER option (ON; never for ORACLE builds);
ensure-launcher-libs adds freetype + RmlUi statically; per-target wiring
links RmlUi::Core + GL3/SDL backends, POST_BUILD copies assets next to
the exe. GL3 backend force-includes <algorithm> (GCC/Clang).
main.cpp: before BIOS resolution, if PSX_LAUNCHER && !PSX_NO_LAUNCHER,
spin up an isolated GL 3.3-core window, run the launcher, apply choices
+ save_user_settings on Launch (return 0 on Quit), then tear the window
down. Isolated window avoids the software-renderer-vs-GL window-flag
conflict; the emulator's own boot path below is untouched.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>