The runtime mounts a .chd natively and the launcher lists it in the disc picker, so a player could select one and see a valid disc identity, then have first-run setup fail: verify-disc, prepare_disc.py and probe_disc.py hash and parse raw 2352-byte track data, which a CHD stores compressed. Nothing on the Python side could read it, and telling players to install MAME for chdman is not an answer. libchdr is already vendored and compiled for the runtime and the native media CLI. Build it once more as a shared library in recompiler/ (CMake target chdr, output next to the emitters, static CRT on Windows so a plain python.exe can load it) and read the CHD from Python through ctypes. tools/psx_chd.py mirrors the layout the runtime's iso_reader.cpp uses -- 2448-byte frames, 4-frame track padding, stored versus virtual pregaps, big-endian audio -- and reproduces the .bin files a Redump dump of the same disc contains, in either of Redump's two layouts (one bin per track, or one bin for the disc). verify-disc hashes those reconstructed track bytes against [prepare_disc] instead of the container. prepare_disc.py writes the tracks into out_dir in whichever layout the recorded digests match and then continues down its normal cue path, so the data track is written once, SBI companions are looked up next to the CHD, and the receipt names the CHD the player chose. probe_disc.py extracts to a scratch directory and probes the cue. The CLI builds target chdr on demand for a kit whose emitters predate it, and falls back to the extract-it-first message when it cannot. Verified against two discs from a Redump library converted with chdman: a single-track MODE2 disc and a four-track disc with stored audio pregaps both reproduce their catalogued track-1 size, MD5 and SHA-1 exactly; verify-disc and prepare_disc.py accept the four-track one end to end. Tests build a synthetic uncompressed v5 CHD around a cooked ISO image, so no retail data is needed; the decode tests skip with a reason when no libchdr is built.
New Project Layout + Project Studio
Scaffold new titles and migrate older ones onto the setup-host layout.
Policy: public releases are setup-host only (no prebuilt generated game C).
New project
sh tools/new_project_layout/setup_project.sh --disc /path/to/game.cue --dir ~/src
See docs/GAME_PROJECT_SETUP.md.
Project Studio (migrate / update)
Shared Python library under project_studio/ with CLI + CustomTkinter GUI.
CLI stays stdlib-only. First GUI launch auto-creates
tools/new_project_layout/.venv and installs requirements-gui.txt
(network once); later launches are silent.
python3 tools/new_project_layout/project_studio_gui.py
# Audit layout gaps
python3 tools/new_project_layout/migrate_project.py audit \
--root /path/to/ApeEscapeRecomp
# Show ordered plan
python3 tools/new_project_layout/migrate_project.py plan \
--root /path/to/ApeEscapeRecomp
# Dry-run apply (recommended first)
python3 tools/new_project_layout/migrate_project.py apply \
--root /path/to/ApeEscapeRecomp --dry-run
# Apply for real (rewrites CMake with .pre_migrate.bak)
python3 tools/new_project_layout/migrate_project.py apply \
--root /path/to/ApeEscapeRecomp \
--disc /path/to/game.cue
# GUI (auto-bootstraps .venv + customtkinter on first run)
python3 tools/new_project_layout/migrate_project.py gui
# or
python3 tools/new_project_layout/project_studio_gui.py
# Git / GitHub (also available as the GUI "Git / GitHub" tab)
python3 tools/new_project_layout/migrate_project.py git status \
--root /path/to/ApeEscapeRecomp
python3 tools/new_project_layout/migrate_project.py git ensure-submodules \
--root /path/to/ApeEscapeRecomp \
--psxrecomp-branch master --recomp-ui-branch master
python3 tools/new_project_layout/migrate_project.py git set-branch \
--root /path/to/ApeEscapeRecomp --submodule psxrecomp --branch master
python3 tools/new_project_layout/migrate_project.py git ensure-nested \
--root /path/to/ApeEscapeRecomp \
--recomp-net-branch main --rbengine-branch main
python3 tools/new_project_layout/migrate_project.py git update-nested \
--root /path/to/ApeEscapeRecomp --remote
python3 tools/new_project_layout/migrate_project.py git commit-nested \
--root /path/to/ApeEscapeRecomp -m "chore: bump nested modules"
python3 tools/new_project_layout/migrate_project.py git update-submodules \
--root /path/to/ApeEscapeRecomp --remote
python3 tools/new_project_layout/migrate_project.py git commit \
--root /path/to/ApeEscapeRecomp -m "chore: bump submodules"
python3 tools/new_project_layout/migrate_project.py git push \
--root /path/to/ApeEscapeRecomp
python3 tools/new_project_layout/migrate_project.py git release \
--root /path/to/ApeEscapeRecomp --bump patch
Git ops shell out to git / gh (no force-push, amend, or hook skips). Submodule branch in .gitmodules is for update --remote; CI builds the committed gitlink SHAs.
For bulk ops across many titles / platforms, use the sibling tool
retcomm-studio (~/…/GitHub/retcomm-studio — catalog-backed CLI + plugin API).
Ops (subset)
| Op | Purpose |
|---|---|
rename_psxrecomp_submodule |
Promote/keep psxrecomp/; delete leftover psxrecomp-v4 |
repair_psxrecomp_submodule |
Re-clone when psxrecomp/.git gitdir is broken / absorbed |
ensure_recomp_ui_submodule |
Add recomp-ui |
emit_codegen_setup |
Thin codegen_setup.c/.h |
rewrite_cmake_setup_host |
psxrecomp_add_game_runtime + wizard |
emit_packager |
scripts/package_setup_release.sh |
emit_ci_workflow |
Setup-host release.yml |
probe_disc_refresh |
TOC / catalog / seeds (needs --disc) |
annotate_legacy_packaging |
Mark old prebuilt packagers obsolete |
Wizard + recomp-ui are forced on for apply (setup-host requirement).
Helpers reused: probe_disc.py, fill_tokens.py, sync_symbols.py, templates/*.