mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-27 10:38:46 -04:00
ff570512ed
G1.11: the hook-flavor runtime, the mod armed at session start, dataflow only, exact projection, and the rectangle shortcut. It covers what tri_mixed can and cannot see, and the R4 census after every change: - 1P: 99.91% dataflow, 0 of 389k drawn triangles mixed; - menus, attract and 2P VS: every truncation reject is a vertex at the saturation limit. The wobble table drops the preserve-projection row, which is 0 by construction. G1.12: precise culling (the culled-row crack, the rule, why it is mod-only, packet headroom, identity, cost under R4's default stack) and [video] pgxp_mod_only. TCP_COMMANDS: the new geom_correction fields and the pgxp verb's culling knob. MOD_PACKAGES: the session arming takes the request; how a title ships PGXP on. GAME_PROJECT_SETUP: pointer to pgxp_mod_only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
639 lines
30 KiB
Markdown
639 lines
30 KiB
Markdown
# 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.
|
||
|
||
```bash
|
||
# 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
|
||
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:
|
||
|
||
1. Create repo stubs (`CMakeLists.txt`, `game.toml`, codegen, rich `.gitignore`,
|
||
`symbols.toml`, `README`, empty `mods/preloaded/packages/`, …) + copy
|
||
`tools/sync_symbols.py`
|
||
2. Submodule `psxrecomp` (+ `recomp-ui` only if accepted); detach at fetched
|
||
SHAs; optional snapshot → `framework_pins.txt`
|
||
3. Packager stub; optional CI workflow (submodule gitlinks are the pin;
|
||
`record_pins.sh` logs SHAs)
|
||
4. Stage disc → `probe_disc.py` (marketing + seeds) → `psx_symbols.h` →
|
||
optional boxart → initial commit → optional `gh repo create` (no push) →
|
||
optional Generate → optional cmake/ninja build → single `git push`. If
|
||
Actions has not indexed `release.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](https://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`](SYMBOLS.md)) |
|
||
|
||
|
||
Re-run without scaffolding:
|
||
|
||
```bash
|
||
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_pc` and `text_size` taken 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 each `jr $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:
|
||
|
||
```bash
|
||
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 |
|
||
|
||
```bash
|
||
# 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:
|
||
|
||
1. **Boot / soak** — fix missing seeds, overlays, FMV/runtime quirks in `game.toml`
|
||
2. **Netplay QA** — LAN then lobby; confirm digests + TOC fp; pin `VERSION`
|
||
3. **Polish** — more symbols in `symbols.toml`, boxart name mismatches
|
||
4. **Ship** — commit `generated/` (the scaffold does this after a successful
|
||
Generate), push, enable Actions, tag `vX.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 set `PSX_NETPLAY ON`
|
||
**before** `include(runtime.cmake)` when `lib/recomp-net` exists (otherwise
|
||
`option(PSX_NETPLAY)` stays OFF and `psx_netplay_sched.c` cannot find
|
||
`recomp_net/session.h`)
|
||
- `NETPLAY_LOBBY_URL "ws://host:port"` — compile-time default lobby URL
|
||
(`PSX_NET_LOBBY_DEFAULT_URL`; env `PSX_NET_LOBBY_URL` still wins at runtime)
|
||
- `ENABLE_SETUP_WIZARD` — force `PSX_SETUP_WIZARD=ON` for that target
|
||
- `PGXP` — build the title's one runtime as the PGXP hook flavor
|
||
(`-DPSX_PGXP=1`, overlay cache flavor `_f2`, no `_pgxp` suffix). The generated
|
||
C already carries the hook sites, so `generated/` does not change. PGXP stays
|
||
off at runtime until `psx.enhancement.pgxp` or the `[video]` keys arm it; with
|
||
it off the guest runs exactly as in the base flavor. Savestates of the two
|
||
flavors are not interchangeable. A title that ships PGXP on by default does so
|
||
through a default-on override of `psx.enhancement.pgxp` plus
|
||
`[video] pgxp_mod_only = true`. See `ENHANCEMENTS.md` G1.11/G1.12.
|
||
|
||
Minimal opt-in example:
|
||
|
||
```cmake
|
||
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.
|
||
|
||
```text
|
||
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`).
|
||
|
||
```cmake
|
||
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
|
||
|
||
```bash
|
||
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:
|
||
|
||
```bash
|
||
git clone --recurse-submodules https://github.com/you/YourGameRecomp.git
|
||
```
|
||
|
||
---
|
||
|
||
## Local development loop
|
||
|
||
After scaffold (or a manual clone):
|
||
|
||
1. **Legal disc** — under `disc/` or the path in `game.toml` (never commit dumps
|
||
or retail BIOS).
|
||
2. **Seeds** — scaffold/`probe_disc.py` writes a first-pass `seeds/ghidra_funcs.txt`
|
||
from the boot EXE. Grow it as overlays / runtime paths miss.
|
||
3. **Build emitters** (once per machine / after recompiler changes):
|
||
```bash
|
||
./psxrecomp/tools/ci/build_emitters.sh
|
||
```
|
||
4. **Generate game (+ OpenBIOS) C** — setup wizard (if enabled) or:
|
||
```bash
|
||
python3 psxrecomp/psxrecomp_cli.py generate \
|
||
--config game.toml --project-root . --disc disc/game.cue
|
||
```
|
||
5. **Commit the generated C** — it is the release's input:
|
||
```bash
|
||
git add generated && git commit -m "Regenerate game C"
|
||
```
|
||
6. **Build the playable runtime**:
|
||
```bash
|
||
cmake -S . -B build-release -G Ninja -DCMAKE_BUILD_TYPE=Release
|
||
cmake --build build-release --target psx-runtime -j"$(nproc)"
|
||
```
|
||
7. **Soak** — offline boot → LAN netplay → lobby. Tune `[runtime]` quirks only
|
||
after you have evidence (see MotK `game.toml` comments 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.toml`
|
||
- `bios/openbios.bin` + its MIT notice — the release runs on OpenBIOS
|
||
- `overlay_toolchain/` so overlays outside the compiled static shard can be
|
||
turned native from the player's disc at runtime
|
||
- `licenses/` (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](https://github.com/RetroPortingToolKit/Retro-Launcher)) 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/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):
|
||
|
||
```yaml
|
||
- 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
|
||
sh psxrecomp/tools/generate_ci.sh # in the project; --check, --force, --dry-run
|
||
```
|
||
|
||
```powershell
|
||
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):
|
||
|
||
```bash
|
||
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:
|
||
|
||
```bash
|
||
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/` and `recomp-ui/` are root-level submodules on pinned commits
|
||
- [ ] CLI lives in the submodule (`psxrecomp/psxrecomp_cli.py`) — no sibling sdk
|
||
- [ ] `game.toml` has 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.txt` snapshot)
|
||
- [ ] `.recomp.json` `status` matches reality (`exploring` → `in-progress` →
|
||
`playable` → `released`, or `paused`) — community lists read it daily, and a
|
||
stale status is worse than none
|
||
- [ ] `seeds/ghidra_funcs.txt` covers 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 also
|
||
`docs/ci/templates/game.gitignore`)
|
||
- [ ] `generated/<boot>_dispatch.c` + `_full_*.c` (+ `overlays_static*.c` when
|
||
the AOT profile declares it) are committed and current for the pinned
|
||
framework (`tools/ci/check_generated.sh` passes locally)
|
||
- [ ] The pinned `psxrecomp` commits its BIOS backends (`psxrecomp/generated/
|
||
{OpenBIOS,SCPH1001}_*.c`) with stamps current for its emitter
|
||
- [ ] Submodule gitlinks (`psxrecomp` / `recomp-ui` / nested `recomp-net`) are
|
||
```
|
||
the 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=ON` and asserts the
|
||
configure log says `linking generated game C (full runtime)` and
|
||
`BIOS backends linked: OpenBIOS;SCPH1001`
|
||
- [ ] `[runtime] overlay_cache = true` in `game.toml` (the packager refuses
|
||
otherwise)
|
||
- [ ] 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`) — `main.cpp` always 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.sh` calls `psxrecomp/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 **no** `psxrecomp/`, `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 |