26 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,
and (optionally) ship a setup-host zip that users generate from a legal disc
locally — the same model as Bomberman Party Edition / Masters of Teräs Käsi.
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/ gitignored) |
| 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 |
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)).
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 remain setup-host only — the tool does not
create prebuilt generated-C packages.
| 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) → setup-host 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 (required for
PSXRECOMP_FORCE_SETUP_HOST). 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 — scaffold already creates the repo and pushes once at the end when
you opt in; otherwise push manually. Enable Actions, tag
vX.Y.Z(CI ships setup-host withoutgenerated/— end users run Generate locally / via wizard)
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 RetComM / 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 # RetComM / catalog digests + track_counts + disc_fp
├── 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 (RetComM-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
│ └── packages/ # packages/<id>/<version>/manifest.toml …
├── scripts/
│ └── package_setup_release.sh # scaffold fills from package_setup_release.sh.in
├── .github/workflows/
│ └── release.yml # scaffold fills from docs/ci/templates/setup-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/ # local only — gitignore (not in CI setup zip)
└── disc/ # local disc working tree — gitignore
Minimal CMakeLists.txt shape for a setup-host title (CI zip / first-run
Generate & rebuild). Wizard is required whenever you ship
PSXRECOMP_FORCE_SETUP_HOST=ON — without it the launcher never opens first-run.
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/mstan/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
- 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).
Setup-host releases (self-build zip)
CI ships a zip without generated/ game C, retail BIOS dumps, or an
embedded toolchain/. The zip includes:
- Setup host exe (
recomp-ui+ generate/rebuild wizard) - Your game sources (
game.toml, seeds, CMake, host glue, …) psxrecomp/(runtime + CLI + OpenBIOS profiles + emitters)recomp-ui/sources (needed to rebuild)- On Windows: MinGW runtime DLLs beside the host and emitters
Players (or RetComM)
run Generate once (wizard or RetComM Build & Install) with a legal disc.
RetComM / the wizard download cmake-clang-v1 from
retcomm-toolchains
(or accept an offline zip / RETCOMM_TOOLCHAIN_DIR). Pass
--embed-toolchain to package_setup_host.sh only for special offline-first
packs.
Player updates (after first Generate)
| Action | Meaning |
|---|---|
| Update (RetComM) | New setup-host zip → refresh source → cmake rebuild. Skips disc→C when codegen-cache fingerprints (ROM/BIOS/emitters) still match. |
| Generate & Rebuild | Force regenerate game C from the disc, then rebuild. Use when emit inputs change or cache is wrong. |
Ordinary host/UI releases do not require Generate & Rebuild. Details:
ci/HOST_ONLY_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/clear_generated.sh |
Wipe generated/ for setup-host CI |
tools/ci/record_pins.sh |
Log submodule SHAs |
tools/ci/build_emitters.sh |
Build psxrecomp-game + psxrecomp-bios |
tools/fetch_toolchain.sh |
Optional download/unpack (embed packs only) |
tools/stage_setup_sdk.sh |
Emitters + OpenBIOS + optional toolchain/ + MinGW DLLs |
tools/bundle_mingw_dlls.sh |
Windows runtime DLL copy |
tools/package_setup_host.sh |
Full setup-host zip (title args) |
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
The New Project Layout scaffold copies and fills this for you after submodules land:
.github/workflows/release.yml←docs/ci/templates/setup-release.yml(Linux x64, Windows x64, macOS arm64 + Intel)scripts/package_setup_release.sh← zip prefix / exe / display name
--exe-name / codegen_setup.exe_basename must 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.
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/setup-release.yml .github/workflows/release.yml
# replace YOUR_ZIP_PREFIX / YOUR_GAME_TITLE / yourgame-release
# add scripts/package_setup_release.sh (see MotK / BPE)
Or re-run token fill against the submodule template:
python3 psxrecomp/tools/new_project_layout/fill_tokens.py \
psxrecomp/docs/ci/templates/setup-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 setup-host release that matches other titles (BPE / MotK / RetComM).
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) seeds/ghidra_funcs.txtcovers the boot path (probe JAL pass + discoveries);`VERSION` matches the release you will tag- Disc images,
generated/, BIOS, logs, and runtime dumps are gitignored(scaffold writes a rich `.gitignore`; see also `docs/ci/templates/game.gitignore`) - Submodule gitlinks (
psxrecomp/recomp-ui/ nestedrecomp-net) arethe SHAs you intend to ship (CI builds those; `record_pins.sh` only logs) - Setup-host CMake path builds with no game C and no BIOS backends
CI: `-DPSXRECOMP_FORCE_SETUP_HOST=ON -DPSXRECOMP_ALLOW_NO_BIOS=ON -DPSX_SETUP_WIZARD=ON` (CMakeLists must also set `PSX_SETUP_WIZARD` / `ENABLE_SETUP_WIZARD` — FORCE_SETUP_HOST alone does not open the wizard; `psxrecomp_add_game_runtime` FATAL_ERRORs if FORCE_SETUP_HOST is on without the wizard). clear_generated.sh wipes OpenBIOS C; users Generate via wizard. - Thin
codegen_setup.c+psxrecomp_add_game_runtime(codegen host is in`psxrecomp/host/`). Must export `psx_game_codegen_forward_if_built` (see `codegen_setup.c.in`) — setup-host CI links `main.cpp`, which always calls it.
Packaging (shared helpers)
- CI uses
./psxrecomp/.github/actions/build-emitters - Packager calls
psxrecomp/tools/package_setup_host.sh(lean zip bydefault; optional `--embed-toolchain`) or `stage_setup_sdk.sh` after a custom stage - Staged tree includes
psxrecomp/psxrecomp_cli.py - Staged
psxrecomp/bios/hasOpenBIOS.toml,openbios.bin,`OpenBIOS.LICENSE`, `SCPH1001.toml` — and **no** retail `.BIN` - Windows zip has
libstdc++-6.dll+libgcc_s_seh-1.dllnext to bothemitters (and host deps such as `zlib1.dll` next to the exe) - Zip mtimes are normalized (packager
touch) so Ninja does not see“future” files after extract
Zip contents smoke test
- Linux / macOS / Windows artifacts named consistently
(`YOUR_PREFIX-<ver>-<linux-x64|windows-x64|macos-arm64|macos-x64>.zip`) - Extract → run host → Generate with a legal disc succeeds end-to-end
- After rebuild, game launches; saves/settings land beside the exe
- RetComM install/update (if you publish a catalog entry) uses this same
zip — no separate tools pack required; Update rebuilds via codegen-cache (not raw zip extract over a Play binary) - Release notes: first Generate once; later Updates are host/UI rebuilds
unless emitters/ROM/BIOS fingerprints change (see ci/HOST_ONLY_RELEASES.md)
Publish
- Tag
vX.Y.Z; GitHub Release attaches all four platform zips - Release notes say the zip is a setup host (no disc / no retail BIOS /
no pre-generated game C) - Catalog / RetComM entry points at the release assets when ready