Decision (Alex, 2026-09-30): the recompiled BIOS carries the same risk as
the recompiled game, which titles already commit, so the retail backend is
compiled in rather than built on the player's machine. The retail image is
still never shipped: the runtime needs its data at run time and accepts only
the exact image the backend was built from, so the player supplies their
own dump as before.
- generated/{OpenBIOS,SCPH1001}_{full,dispatch}.c and their .emitter.sha
stamps are tracked (.gitignore un-ignores exactly those); regenerated with
the current emitter.
- bios_emitter_fingerprint.sh no longer hashes the ROM image: the profile's
SHA-256 pin stands for it and the emitter refuses any other image, and CI
has no dump, so hashing it made every CI configure read "stale".
- runtime.cmake: the staleness check covers every LINKED stem and
-DPSXRECOMP_BIOS_STALE_FATAL=ON makes a stale stamp fail configure.
- game-release.yml: no CI-side OpenBIOS emission (generate_openbios.sh is
gone); configure with BIOS_STALE_FATAL and assert both backends linked.
check_generated.sh asserts the submodule carries both backends tracked
and no dump is tracked anywhere.
- The player-side module build stays for a known image a build does not
link (SCPH-5552).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
30 KiB
Bringing up a game project on PSXRecomp
This guide is for title developers: you keep game code and config in your
own repository, pin this framework and recomp-ui as root-level submodules,
commit the recompiled game C under generated/, and ship a bundled zip:
the compiled game, built by CI from that committed C. Players bring their own
disc and play; nothing is generated or built on their machine
(docs/ci/BUNDLED_RELEASES.md).
Related docs:
| Doc | When |
|---|---|
[BUILDING.md](BUILDING.md) |
Local build of the framework / BIOS runtime |
[LOCAL_CODEGEN_SDK.md](LOCAL_CODEGEN_SDK.md) |
CLI contract for Generate & rebuild |
[NETPLAY.md](NETPLAY.md) |
Read more about netplay (rollback, SFU/ICE, dual-raster, disc gates) |
[ci/README.md](ci/README.md) |
Shared CI scripts and composite actions |
[CONTRIBUTING.md](../CONTRIBUTING.md) |
Framework contribution rules |
New Project Layout (preview)
The preferred title-repo shape is root-level psxrecomp/ + recomp-ui/
submodules. Scaffolding scripts create that layout, pin submodule SHAs, fill
CI + packager stubs, and (with --disc) autofill identity, TOC fingerprint,
and a first-pass seed list from the boot EXE.
| Platform | Script |
|---|---|
| Linux / macOS | [tools/new_project_layout/setup_project.sh](../tools/new_project_layout/setup_project.sh) |
| Windows | [tools/new_project_layout/setup_project.ps1](../tools/new_project_layout/setup_project.ps1) |
| Disc probe (standalone) | [tools/new_project_layout/probe_disc.py](../tools/new_project_layout/probe_disc.py) |
| Boxart fetch | [tools/new_project_layout/fetch_boxart.py](../tools/new_project_layout/fetch_boxart.py) |
| Token fill helper | [tools/new_project_layout/fill_tokens.py](../tools/new_project_layout/fill_tokens.py) |
Scaffold flow (paths on CLI, rest prompted)
Required path flag (use your shell’s tab-completion): --disc / -Disc
pointing at a legal Redump .cue. Missing --disc exits with a nag — no
prompt for that path.
# Interactive (TTY): prompts for name, players, zip prefix, recomp-ui, …
sh tools/new_project_layout/setup_project.sh \
--disc /path/to/legal/game.cue \
--dir ~/src \
--bios /path/to/SCPH1001.BIN # optional; only used if you answer Y to Generate
powershell -File tools\new_project_layout\setup_project.ps1 `
-Disc C:\dumps\game.cue -Dir C:\src
Non-interactive / CI (--yes / -Yes or PSXRECOMP_SETUP_YES=1): requires
--name + --disc; Y/N options default off unless you pass enable flags.
| Path / CLI | Role |
|---|---|
--disc / -Disc |
Required. Stage cue+bins, probe identity / seeds / TOC fp |
--dir / -Dir |
Parent directory for the new repo (default .) |
--bios / -Bios |
Optional SCPH1001.BIN when Generate runs |
--boot-exe |
Optional until probe overwrites from SYSTEM.CNF |
| Prompted (or flag override) | Notes |
|---|---|
| Project name | Required; --name / -Name skips prompt |
| Players | Default 2, max 8 |
| Zip / CI prefix | Default acronym from name (e.g. MotK → motk) |
| Description / publisher / year / region | Optional marketing → catalog_identity.json + README |
| Include recomp-ui? | N → no recomp-ui submodule, PSX_RECOMP_UI=OFF, skip wizard/netplay |
| Setup wizard? / Netplay? | Only asked if recomp-ui = Y; netplay auto-off (no prompts) when players = 1 |
| Netplay lobby URL? | Only if netplay = Y (players ≥ 2); default host netplay.retcomm.net → ws://…:8765 |
| GitHub Actions release workflow? | Y → .github/workflows/release.yml (logs submodule SHAs via record_pins.sh) |
| Fetch libretro boxart? | Needs network |
| Run Generate now? | Emitters + OpenBIOS + game C; generated/ is committed (release CI builds from it) |
| Configure & build after Generate? | Only if Generate = Y |
Create GitHub repo (gh)? |
Opt-in; needs gh auth |
Flag overrides (non-interactive): --enable-recomp-ui / --no-recomp-ui,
--enable-wizard / --no-wizard, --enable-netplay / --no-netplay,
--lobby-url, --description / --publisher / --year / --region,
--enable-ci / --no-ci, --fetch-boxart / --no-fetch-boxart,
--generate / --no-generate, --enable-build / --no-build,
--create-github / --no-github / --github-visibility, … (PowerShell:
-EnableRecompUi, -LobbyUrl, -EnableBuild, -CreateGithub, …).
What runs after answers:
- Create repo stubs (
CMakeLists.txt,game.toml, codegen, rich.gitignore,symbols.toml,README, emptymods/preloaded/packages/, …) + copytools/sync_symbols.py - Submodule
psxrecomp(+recomp-uionly if accepted); detach at fetched SHAs; optional snapshot →framework_pins.txt - Packager stub; optional CI workflow (submodule gitlinks are the pin;
record_pins.shlogs SHAs) - Stage disc →
probe_disc.py(marketing + seeds) →psx_symbols.h→ optional boxart → initial commit → optionalgh repo create(no push) → optional Generate → optional cmake/ninja build → singlegit push. If Actions has not indexedrelease.yml(common after a private first push), the scaffold force-publishes when visibility is public and nudges with a tiny follow-up commit that retouches the workflow file.
Standalone helpers: probe_disc.py, fetch_boxart.py, fill_tokens.py,
sync_symbols.py.
Disc autofill (probe_disc.py)
With a Redump-style **.cue**:
| Output | Contents |
|---|---|
game.toml |
Serial / boot EXE / EXE header; [prepare_disc] Track 01 digests; [netplay] require_cue, required_tracks, required_disc_fp (psxrecomp-toc-v1) |
seeds/ghidra_funcs.txt |
Entry PC + direct JAL targets inside the boot EXE text (first-pass; MotK-class ~850 addrs) |
catalog_identity.json |
Digests + track_counts + disc_fp + optional marketing (description / publisher / year / region / players) |
disc_probe.json |
Full probe dump |
.recomp.json |
Community metadata (recomp.fyi spec): game, system, type, toolchain, status: exploring, and original region + serial. Written with or without --disc; region comes from the serial prefix when known. write_recomp_json.py only fills missing fields, so re-running it never overwrites your edits |
symbols.toml + psx_symbols.h |
Boot entry stub + PSX_FN_* header (tools/sync_symbols.py; see SYMBOLS.md) |
Re-run without scaffolding:
python3 tools/new_project_layout/probe_disc.py disc/game.cue \
--write-game-toml game.toml \
--write-catalog catalog_identity.json \
--write-seeds seeds/ghidra_funcs.txt \
--disc-rel disc/game.cue --out-dir disc --players 2
Prefer a full multi-track Redump cue. A single-TRACK dump will warn and will
fail online multi-track gates (see [NETPLAY.md](NETPLAY.md)).
EXE-only autofill (psxrecomp-toml)
When you have a PS-X EXE but no cue — or you just want a game.toml skeleton
without running the full disc probe — psxrecomp-toml (built alongside the
other CLI tools from recompiler/, source recompiler/src/main_toml.cpp)
reads the EXE header and writes the config for you. It saves opening a hex
editor or Ghidra just to recover the entry point, load address and text size.
What it produces:
game.toml—load_address,entry_pcandtext_sizetaken from the actual PS-X EXE header, plus the game name and ID.- Seeds — the destinations of every direct jump-and-link (
JAL) it finds in the code segment, optionally widened with the addresses immediately following eachjr $ra.
Usage: psxrecomp-toml <PS1-EXE> [options]
Options:
--output <path> Write game.toml to <path> (default: stdout)
--seeds <path> Also write JAL-target seed file to <path>
--name <str> Game name in TOML (default: derived from EXE)
--id <str> Game ID (default: auto-detect or empty)
--stdout Force output to stdout even with --output
--include-after-return
Add addresses after jr $ra to seeds (more coverage,
may include some data addresses)
-h, --help Show this help
Example:
psxrecomp-toml ./isos/SCES_028.34 \
--output ./projects/CrashBash/game.toml \
--seeds ./projects/CrashBash/recompiler/seeds/seeds.txt \
--name "Crash-Bash-EUR"
It also prints the EXE, game ID, entry PC, load address, text size, stack base and seed count to stderr, so you can sanity-check the header it read.
--include-after-return trades precision for coverage: an address just past a
jr $ra is usually the next function, but sometimes it is data, so those seeds
can introduce junk entries.
This is a first pass, not full discovery. Static JAL scanning cannot
resolve dynamic function tables or indirect register dispatches. When the
recompiler later reports discovery gaps or unknown dispatches at runtime, those
newly-found addresses still have to be fed back into the seeds file. Prefer
probe_disc.py above when you have a cue — it does the same header work plus
disc identity, digests and netplay gates.
Project Studio — migrate / update existing titles
Older titles (e.g. psxrecomp-v4 submodule, psxrecomp_add_runtime_target,
packaging/ prebuilt zips) can be audited and migrated onto this layout with
Project Studio. Releases are bundled: the tool un-ignores and expects
committed generated/, and emits the packager + CI that ship the compiled game.
| Entry | Role |
|---|---|
tools/new_project_layout/migrate_project.py |
CLI: audit / plan / apply / gui / ops |
tools/new_project_layout/project_studio_gui.py |
Tkinter GUI |
tools/new_project_layout/project_studio/ |
Shared detect / plan / ops library |
tools/new_project_layout/README.md |
Short usage |
# From a psxrecomp checkout (or any title that vendors this toolkit path):
python3 tools/new_project_layout/migrate_project.py audit --root ~/src/ApeEscapeRecomp
python3 tools/new_project_layout/migrate_project.py apply --root ~/src/ApeEscapeRecomp --dry-run
python3 tools/new_project_layout/migrate_project.py gui
Typical apply order: rename psxrecomp-v4 → emit codegen_setup → rewrite
CMake (psxrecomp_add_game_runtime + wizard) → bundled packager + CI →
optional probe_disc / pins. CMake rewrites keep
CMakeLists.txt.pre_migrate.bak plus CMakeLists.migrate_extras.txt when the
old file had tests / EXTRAS_SOURCES / mod POST_BUILD hooks.
apply always enables recomp-ui + setup wizard (the shipped game's
first-run disc picker). Netplay stays opt-in (--enable-netplay / GUI).
After scaffold (still not automatic)
If you answered Y to boxart + Generate (+ optional build), you get a local playable tree (OpenBIOS / optional retail BIOS C). You still must:
- Boot / soak — fix missing seeds, overlays, FMV/runtime quirks in
game.toml - Netplay QA — LAN then lobby; confirm digests + TOC fp; pin
VERSION - Polish — more symbols in
symbols.toml, boxart name mismatches - Ship — commit
generated/(the scaffold does this after a successful Generate), push, enable Actions, tagvX.Y.Z. CI builds the committed C and attaches the compiled game for every platform.
Legacy layout (CLI psxrecomp build, nested UI under older trees,
tools/setup_dev.sh for framework-only boots) remains supported. Prefer the
New Project Layout for new titles until the project fully adopts it.
Opt-in launcher features (build flags)
Both surfaces are OFF by default. The New Project Layout can enable them
when you accept recomp-ui and answer Y to wizard/netplay (or pass
--enable-wizard / --enable-netplay). Declining recomp-ui sets
PSX_RECOMP_UI=OFF and skips those prompts. You can also set the flags in
CMakeLists.txt before include(runtime.cmake), or pass -D…=ON. Do not
enable them until tested — other recomp-ui consumers stay dark when GameInfo
is zero-init.
| CMake option | Default | Effect |
|---|---|---|
PSX_NETPLAY |
OFF | Link recomp-net + lobby client; advertise full netplay UI when the title has 2+ players. Also defaults RNET_ENABLE_ICE=ON (libjuice via URL FetchContent, or third_party/libjuice / RNET_LIBJUICE_ROOT) unless -DRNET_ENABLE_ICE=OFF. |
PSX_SETUP_WIZARD |
OFF | Advertise first-run setup wizard + Generate & rebuild (GameInfo.setup_wizard_supported) |
psxrecomp_add_game_runtime helpers:
ENABLE_NETPLAY_IF_PRESENT— supplemental only; still setPSX_NETPLAY ONbeforeinclude(runtime.cmake)whenlib/recomp-netexists (otherwiseoption(PSX_NETPLAY)stays OFF andpsx_netplay_sched.ccannot findrecomp_net/session.h)NETPLAY_LOBBY_URL "ws://host:port"— compile-time default lobby URL (PSX_NET_LOBBY_DEFAULT_URL; envPSX_NET_LOBBY_URLstill wins at runtime)ENABLE_SETUP_WIZARD— forcePSX_SETUP_WIZARD=ONfor that target
Minimal opt-in example:
set(PSXRECOMP_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/psxrecomp")
if(EXISTS "${PSXRECOMP_ROOT}/lib/recomp-net/CMakeLists.txt")
set(PSX_NETPLAY ON CACHE BOOL "…" FORCE)
endif()
set(PSX_SETUP_WIZARD ON CACHE BOOL "…" FORCE)
include("${PSXRECOMP_ROOT}/runtime/runtime.cmake")
psxrecomp_add_game_runtime(psx-runtime
ENABLE_NETPLAY_IF_PRESENT
NETPLAY_LOBBY_URL "ws://netplay.retcomm.net:8765"
ENABLE_SETUP_WIZARD
…
)
Without PSX_SETUP_WIZARD, recomp-ui never opens the first-run modal and never
shows Generate & rebuild — even if prepare callbacks are present. Without
PSX_NETPLAY, the NETPLAY button stays hidden.
Read more about netplay: [NETPLAY.md](NETPLAY.md) — rollback, hybrid
OpenGL+1× SW authority, ICE/TURN, SFU online vs LAN star, multitap, and
multi-track .cue/.bin requirements.
Recommended repository layout
Everything game-specific lives at the root of your title repo. Framework and UI are submodules next to that code — not nested under each other.
The Retro / Generate & rebuild CLI (psxrecomp_cli.py, tools/prepare_disc.py,
pack helpers) ships inside the psxrecomp submodule. There is no separate
psxrecomp-sdk/ tree.
YourGameRecomp/ # your git repo
├── .gitmodules
├── CMakeLists.txt # thin: psxrecomp_add_game_runtime(...)
├── game.toml # probe autofills identity / netplay gates
├── catalog_identity.json # Retro / catalog digests + track_counts + disc_fp
├── .recomp.json # community metadata (recomp.fyi); keep `status` current
├── framework_pins.txt # optional scaffold snapshot (gitlinks are authoritative)
├── README.md # scaffold stub (legal + quick start)
├── VERSION # release pin (e.g. 0.1.0)
├── assets/
│ ├── psxrecomp.ico # default Windows app icon (Retro-themed pad)
│ ├── psxrecomp.png # staged beside exe / packaging
│ └── psxrecomp.svg # source mark (see psxrecomp/assets/)
├── seeds/ghidra_funcs.txt # probe: boot-EXE JAL seeds (grow over time)
├── codegen_setup.c / .h # title PsxrecompCodegenHostConfig (+ apply hooks)
├── launcher_assets/img/
│ ├── boxart.tga # optional: --fetch-boxart (libretro Named_Boxarts)
│ └── BOXART_SOURCE.txt # attribution URL
├── mods/preloaded/ # scaffold: empty catalog for shipped .psxmod packages
│ ├── README.md # staged to <exe>/mods/README.md
│ └── packages/ # packages/<id>/<version>/manifest.toml …
│ # declared to the framework as
│ # PRELOADED_MODS_DIR "${CMAKE_CURRENT_SOURCE_DIR}/mods/preloaded"
│ # which stages it into <exe>/mods/bundled beside
│ # the framework's builtins. NEVER copy it with
│ # your own POST_BUILD command — see
│ # docs/MOD_PACKAGES.md ("How bundled/ gets staged").
├── scripts/
│ └── package_release.sh # scaffold fills from package_release.sh.in
├── .github/workflows/
│ └── release.yml # scaffold fills from docs/ci/templates/game-release.yml
├── psxrecomp/ # submodule → framework + CLI + codegen host + CI tools
│ ├── host/psxrecomp_codegen_host.*
│ └── lib/recomp-net/ # nested submodule (netplay)
├── recomp-ui/ # submodule → launcher UI
├── generated/ # COMMITTED recompiled game C — release CI builds it
└── disc/ # local disc working tree — gitignore
Minimal CMakeLists.txt shape. The wizard is the shipped game's first-run disc
picker; GEN_MARKER / GEN_FULL_GLOB name the committed generated/ C, which
release CI requires (-DPSXRECOMP_REQUIRE_GAME_C=ON).
set(PSXRECOMP_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/psxrecomp")
# set(PSX_NETPLAY ON CACHE BOOL "…" FORCE) # multiplayer titles only
set(PSX_SETUP_WIZARD ON CACHE BOOL
"Advertise first-run setup wizard + Generate & rebuild in recomp-ui" FORCE)
include("${PSXRECOMP_ROOT}/runtime/runtime.cmake")
psxrecomp_add_game_runtime(psx-runtime
# ENABLE_NETPLAY_IF_PRESENT
ENABLE_SETUP_WIZARD
WINDOW_TITLE "My Game Recompiled"
GEN_MARKER "generated/SLUS_01234_dispatch.c"
GEN_FULL_GLOB "generated/SLUS_01234_full_*.c"
CODEGEN_SETUP_SOURCES codegen_setup.c
DEFAULT_GAME_CONFIG_PATH "game.toml"
LAUNCHER_BOXART "${CMAKE_CURRENT_SOURCE_DIR}/launcher_assets/img/boxart.tga"
APP_ICON "${CMAKE_CURRENT_SOURCE_DIR}/assets/psxrecomp.ico"
)
Or use the scaffolding scripts under New Project Layout (preview) above.
Add the submodules
cd YourGameRecomp
git submodule add -b master https://github.com/mstan/psxrecomp.git psxrecomp
git submodule add -b master https://github.com/RetroPortingToolKit/recomp-ui.git recomp-ui
git submodule update --init --recursive
The New Project Layout scaffold pins via detached submodule gitlinks
(optional framework_pins.txt snapshot for humans/logs). Do not float on
main/master in release CI — bump submodules deliberately when you want a
newer framework or UI.
Clone for contributors:
git clone --recurse-submodules https://github.com/you/YourGameRecomp.git
Local development loop
After scaffold (or a manual clone):
- Legal disc — under
disc/or the path ingame.toml(never commit dumps or retail BIOS). - Seeds — scaffold/
probe_disc.pywrites a first-passseeds/ghidra_funcs.txtfrom the boot EXE. Grow it as overlays / runtime paths miss. - Build emitters (once per machine / after recompiler changes):
./psxrecomp/tools/ci/build_emitters.sh
- Generate game (+ OpenBIOS) C — setup wizard (if enabled) or:
python3 psxrecomp/psxrecomp_cli.py generate \
--config game.toml --project-root . --disc disc/game.cue
- Commit the generated C — it is the release's input:
git add generated && git commit -m "Regenerate game C"
- Build the playable runtime:
cmake -S . -B build-release -G Ninja -DCMAKE_BUILD_TYPE=Release
cmake --build build-release --target psx-runtime -j"$(nproc)"
- Soak — offline boot → LAN netplay → lobby. Tune
[runtime]quirks only after you have evidence (see MotKgame.tomlcomments for the pattern).
Details: [LOCAL_CODEGEN_SDK.md](LOCAL_CODEGEN_SDK.md), [BUILDING.md](BUILDING.md),
[NETPLAY.md](NETPLAY.md).
Bundled releases (compiled game zip)
CI builds the committed generated/ C and ships a zip that is the game:
- The game executable (signed on Windows when a certificate is configured)
assets/,mods/bundled/(catalog verified against the build's manifest),game.toml/game_options.tomlbios/openbios.bin+ its MIT notice — the release runs on OpenBIOSoverlay_toolchain/so overlays outside the compiled static shard can be turned native from the player's disc at runtimelicenses/(third-party notices),README.txt
It contains no sources, emitters at the root, CLI, psxrecomp/ or
recomp-ui/ tree, generated C, disc, or BIOS dump. Players (or
Retro) extract it,
pick their legal disc on first run, and play; an update is a raw zip extract
over the previous one. Details and the exact CI steps:
ci/BUNDLED_RELEASES.md. CI tip: leave the
psxrecomp gitlink pinned and use reuse_cached_emitters so release jobs skip
rebuilding emitters when only host sources moved.
Do not set PSX_PGO in CI. PGO stays user-local when [pgo] enabled = true.
Shared tools (use these; do not reimplement)
| Tool | Role |
|---|---|
tools/ci/normalize_version.sh |
vX.Y.Z → VERSION / TAG |
tools/ci/check_generated.sh |
Committed game C + framework BIOS backends present |
tools/ci/record_pins.sh |
Log submodule SHAs |
tools/ci/build_emitters.sh |
Build psxrecomp-game + psxrecomp-bios |
tools/fetch_toolchain.sh |
Windows emitter build on the CI machine (never shipped) |
tools/bundle_mingw_dlls.sh |
Windows runtime DLL copy |
tools/package_game_release.sh |
Bundled zip: compiled game + runtime data (title args) |
tools/release_stage.py |
Mod catalog / overlay toolchain / overlay cache staging |
Composite actions (from the game repo after checkout):
- uses: ./psxrecomp/.github/actions/build-emitters
# Optional — only when packaging with --embed-toolchain:
# - uses: ./psxrecomp/.github/actions/fetch-toolchain
# with:
# artifact: ${{ matrix.artifact }}
- uses: ./psxrecomp/.github/actions/stage-setup-sdk
with:
stage: dist/stage-setup-${{ matrix.artifact }}
recompiler-build: build-recompiler
allow-no-toolchain: 'true'
CI workflow template
For an existing project, tools/generate_ci writes the filled workflow and
nothing else (no commit, no push, no migration plan):
sh psxrecomp/tools/generate_ci.sh # in the project; --check, --force, --dry-run
powershell -File psxrecomp\tools\generate_ci.ps1
It is the single-purpose face of Project Studio's emit_ci_workflow: the
template comes from the project's own psxrecomp submodule, the zip prefix from
scripts/package_release.sh so CI uploads the zips the packager built,
and an installed workflow is compared by step name -- --check exits 1 when
it is missing or stale, and only --force overwrites one.
The New Project Layout scaffold copies and fills this for you after submodules land:
.github/workflows/release.yml←docs/ci/templates/game-release.yml(Linux x64, Windows x64, macOS arm64 + Intel)scripts/package_release.sh← zip prefix / exe / display name
--exe-name / codegen_setup.exe_basename should match CMake OUTPUT_NAME
(MAKE_C_IDENTIFIER(WINDOW_TITLE), e.g. TwistedMetal4_Recompiled), not the
bare project folder name (TwistedMetal4Recomp). Scaffolding derives
EXE_BASENAME from WINDOW_TITLE for that reason.
It is no longer fatal when they disagree. CMake is the single source of truth:
runtime.cmake writes the OUTPUT_NAME it chose to
<build-dir>/psxrecomp_exe_name-<target>.txt, and both the build CLI and the
in-runtime self-compiler read that in preference to their own copy. A build
tree predating the marker still falls back to exe_basename.
This matters because the two values are separate copies of the same window
title, and renaming a game updates one of them. Before the marker existed, that
drift produced build succeeded but binary missing on a build with no errors
at all — the executable was sitting in the build directory under the name CMake
had chosen. To pin the name explicitly instead of deriving it, pass EXE_NAME
to psxrecomp_add_game_runtime().
Zip prefix defaults to a short acronym from --name (e.g. MastersOfTerasKasiRecomp
→ motk); override with --zip-prefix / -ZipPrefix.
Manual install (existing repos):
mkdir -p .github/workflows scripts
cp psxrecomp/docs/ci/templates/game-release.yml .github/workflows/release.yml
# replace YOUR_ZIP_PREFIX / YOUR_GAME_TITLE / yourgame-release
# add scripts/package_release.sh (fill tools/new_project_layout/templates/package_release.sh.in)
Or re-run token fill against the submodule template:
python3 psxrecomp/tools/new_project_layout/fill_tokens.py \
psxrecomp/docs/ci/templates/game-release.yml \
.github/workflows/release.yml \
--ci-placeholders \
--set ZIP_PREFIX=mygame \
--set GAME_TITLE='My Game Recompiled'
Full action reference: [ci/README.md](ci/README.md).
Bundled release checklist
Use this before tagging a release.
Repository
psxrecomp/andrecomp-ui/are root-level submodules on pinned commits- CLI lives in the submodule (
psxrecomp/psxrecomp_cli.py) — no sibling sdk game.tomlhas disc identity ([prepare_disc]hashes/sizes) and boot EXE(scaffold `--disc` / `probe_disc.py` autofills these)- Multi-track titles that ship netplay:
[netplay] require_cue+`required_tracks` + `required_disc_fp` so Track-01-only dumps cannot go online against full Redump cues - Optional: commit
catalog_identity.json(+framework_pins.txtsnapshot) .recomp.jsonstatusmatches reality (exploring→in-progress→playable→released, orpaused) — community lists read it daily, and a stale status is worse than noneseeds/ghidra_funcs.txtcovers the boot path (probe JAL pass + discoveries);`VERSION` matches the release you will tag- Disc images, BIOS, logs, and runtime dumps are gitignored;
generated/is not (scaffold writes a rich.gitignore; see alsodocs/ci/templates/game.gitignore) generated/<boot>_dispatch.c+_full_*.c(+overlays_static*.cwhen the AOT profile declares it) are committed and current for the pinned framework (tools/ci/check_generated.shpasses locally)- The pinned
psxrecompcommits its BIOS backends (psxrecomp/generated/ {OpenBIOS,SCPH1001}_*.c) with stamps current for its emitter - Submodule gitlinks (
psxrecomp/recomp-ui/ nestedrecomp-net) arethe SHAs you intend to ship (CI builds those; `record_pins.sh` only logs) - Full build configures from the committed C: CI passes
-DPSXRECOMP_REQUIRE_GAME_C=ON -DPSX_SETUP_WIZARD=ONand asserts the configure log sayslinking generated game C (full runtime)andBIOS backends linked: OpenBIOS;SCPH1001 [runtime] overlay_cache = trueingame.toml(the packager refuses otherwise)- Thin
codegen_setup.c+psxrecomp_add_game_runtime(codegen host is inpsxrecomp/host/). Must exportpsx_game_codegen_forward_if_built(seecodegen_setup.c.in) —main.cppalways calls it; in a full build it is a no-op
Packaging (shared helpers)
- CI uses
./psxrecomp/.github/actions/build-emitters(OpenBIOS emission and the shipped overlay toolchain need them) scripts/package_release.shcallspsxrecomp/tools/package_game_release.sh- Staged tree has the game exe,
psx_game_version.txt,assets/,mods/bundled/,game.toml,bios/openbios.bin+OpenBIOS.LICENSE,overlay_toolchain/,licenses/— and nopsxrecomp/,recomp-ui/, CLI, root-level emitters, sources, generated C, retail.BIN - Windows zip has the runtime DLLs the exe imports beside it
(
bundle_mingw_dlls.sh), signed when a certificate is configured
Zip contents smoke test
- Linux / macOS / Windows artifacts named consistently
(
YOUR_PREFIX-<ver>-<linux-x64|windows-x64|macos-arm64|macos-x64>.zip) - Extract → run → pick a legal disc → the game boots; saves/settings land beside the exe
- Retro install/update (if you publish a catalog entry) uses this same zip as a prebuilt Play binary (raw zip extract; no build step)
Publish
- Tag
vX.Y.Z; GitHub Release attaches all four platform zips - Release notes say the zip is the compiled game: bring your own disc, no BIOS, no build
- Catalog / Retro entry points at the release assets when ready