migrate bundling logic into psxrecomp, thinner layer at game level

This commit is contained in:
Alex Vanderveen
2026-07-31 23:18:24 -04:00
parent defaf257d9
commit 4cb93f8c3f
12 changed files with 1325 additions and 24 deletions
@@ -0,0 +1,51 @@
name: Fetch psxrecomp portable toolchain
description: >
Download cmake-clang-v1 from retcomm-toolchains and export TOOLCHAIN_DIR /
PSXRECOMP_TOOLCHAIN_DIR / BPE_TOOLCHAIN_DIR for later packaging steps.
inputs:
artifact:
description: Platform pack id (linux-x64, windows-x64, macos-arm64, macos-x64)
required: true
framework:
description: Path to the psxrecomp checkout (contains tools/fetch_toolchain.sh)
required: false
default: psxrecomp
dl-dir:
description: Download cache directory
required: false
default: .cache/toolchain-dl
out-dir:
description: Unpack directory
required: false
default: .cache/toolchain-pack
repo:
description: GitHub owner/name for toolchain releases
required: false
default: TechnicallyComputers/retcomm-toolchains
outputs:
toolchain-dir:
description: Absolute path to the unpacked toolchain root (bin/ present)
value: ${{ steps.fetch.outputs.toolchain-dir }}
runs:
using: composite
steps:
- id: fetch
shell: bash
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
SCRIPT="${{ inputs.framework }}/tools/fetch_toolchain.sh"
if [[ ! -f "${SCRIPT}" ]]; then
echo "::error::missing ${SCRIPT}"
exit 1
fi
chmod +x "${SCRIPT}"
bash "${SCRIPT}" \
--artifact "${{ inputs.artifact }}" \
--dl-dir "${{ inputs.dl-dir }}" \
--out-dir "${{ inputs.out-dir }}" \
--repo "${{ inputs.repo }}"
@@ -0,0 +1,71 @@
name: Stage psxrecomp setup SDK
description: >
Copy emitters, OpenBIOS profiles, retcomm-sdk.json, portable toolchain, and
Windows MinGW runtime DLLs into an existing setup-host stage directory.
inputs:
stage:
description: Absolute or workspace-relative stage directory
required: true
framework:
description: Path to the psxrecomp checkout
required: false
default: psxrecomp
sdk-overlay:
description: Optional overlay directory (e.g. psxrecomp-sdk)
required: false
default: ''
recompiler-build:
description: Build dir containing psxrecomp-game / psxrecomp-bios
required: false
default: build-recompiler
toolchain-dir:
description: Unpacked toolchain root (defaults to env PSXRECOMP_TOOLCHAIN_DIR / TOOLCHAIN_DIR / BPE_TOOLCHAIN_DIR)
required: false
default: ''
host-exe:
description: Optional host .exe path for Windows DLL bundling
required: false
default: ''
runtime-bin:
description: MinGW bin dir for runtime DLLs (Windows)
required: false
default: ''
allow-no-toolchain:
description: If true, warn instead of failing when toolchain is unset
required: false
default: 'false'
runs:
using: composite
steps:
- shell: bash
run: |
set -euo pipefail
SCRIPT="${{ inputs.framework }}/tools/stage_setup_sdk.sh"
if [[ ! -f "${SCRIPT}" ]]; then
echo "::error::missing ${SCRIPT}"
exit 1
fi
chmod +x "${SCRIPT}"
args=(
--stage "${{ inputs.stage }}"
--framework "${{ inputs.framework }}"
--recompiler-build "${{ inputs.recompiler-build }}"
)
if [[ -n "${{ inputs.sdk-overlay }}" ]]; then
args+=(--sdk-overlay "${{ inputs.sdk-overlay }}")
fi
if [[ -n "${{ inputs.toolchain-dir }}" ]]; then
args+=(--toolchain-dir "${{ inputs.toolchain-dir }}")
fi
if [[ "${{ inputs.allow-no-toolchain }}" == "true" ]]; then
args+=(--allow-no-toolchain)
fi
if [[ -n "${{ inputs.host-exe }}" ]]; then
args+=(--host-exe "${{ inputs.host-exe }}")
fi
if [[ -n "${{ inputs.runtime-bin }}" ]]; then
args+=(--runtime-bin "${{ inputs.runtime-bin }}")
fi
bash "${SCRIPT}" "${args[@]}"
+11 -9
View File
@@ -16,20 +16,22 @@ New to the codebase? Read [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) and
(`recompiler/`), the runtime engine (`runtime/`), shared tools, and docs.
Framework changes go here.
- **Per-game repos** (`mstan/TombaRecomp`, `mstan/MegaManX6Recomp`,
`mstan/ApeEscapeRecomp`, …) — one per title. Each contains only that game's
config, seeds, and build glue and links this framework in as a submodule at
`psxrecomp/`. Game-specific work goes in the game repo; nothing game-specific
belongs in the framework.
`mstan/ApeEscapeRecomp`, …) — one per title. Each keeps **game code at the
repo root** and pins this framework (and usually `recomp-ui`) as **root-level
submodules** (`psxrecomp/`, `recomp-ui/`). Game-specific work goes in the game
repo; nothing game-specific belongs in the framework.
Each game repo pins an **exact framework commit** — the git submodule pointer at
`psxrecomp/` (some older game repos also carry a human-readable `psxrecomp-v4.pin`
record; the submodule pointer is the source of truth). The framework evolves on
its own cadence; a game only moves to a newer framework when someone deliberately
bumps that submodule pointer. See
[Linking the framework](docs/BUILDING.md#linking-the-framework) for the submodule
+ local-junction dev setup, and
[Framework changes and the pin](#framework-changes-and-the-pin) below for how a
framework change actually reaches a game.
bumps that submodule pointer.
**Starting a new title or shipping a setup-host zip?** Follow
[`docs/GAME_PROJECT_SETUP.md`](docs/GAME_PROJECT_SETUP.md) (layout, CI template
under `docs/ci/templates/setup-release.yml`, release checklist). Also see
[Linking the framework](docs/BUILDING.md#linking-the-framework) and
[Framework changes and the pin](#framework-changes-and-the-pin) below.
## The core rules
+27 -15
View File
@@ -55,8 +55,10 @@ standalone BIOS runtime supporting OpenBIOS and a compatible retail BIOS** —
see [Release Package](#release-package) below.
Bringing up a title of your own? Start with
[`CONTRIBUTING.md`](CONTRIBUTING.md) and open an issue — community projects are
listed here alongside the rest.
[`docs/GAME_PROJECT_SETUP.md`](docs/GAME_PROJECT_SETUP.md) (submodules,
setup-host CI template, release checklist), then
[`CONTRIBUTING.md`](CONTRIBUTING.md). Community projects are listed here
alongside the rest.
## What It Is
@@ -95,15 +97,22 @@ Three things sit on that foundation:
execution.
PSXRecomp is a **framework**. Game-specific projects live in their own
repositories and link this one in as a **git submodule** to build a game binary.
repositories with **`psxrecomp/` and `recomp-ui/` as root-level submodules**
and game code (`game.toml`, seeds, CMake) at the repo root. See
[`docs/GAME_PROJECT_SETUP.md`](docs/GAME_PROJECT_SETUP.md).
**New here?** The fastest way in:
[`docs/EXECUTION_MODEL.md`](docs/EXECUTION_MODEL.md) (how a game actually
runs — static / native-overlay / interpreter), then
[`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md),
[`docs/BUILDING.md`](docs/BUILDING.md),
[`docs/MOD_PACKAGES.md`](docs/MOD_PACKAGES.md) (versioned runtime mods),
[`CONTRIBUTING.md`](CONTRIBUTING.md).
| Path | Doc |
|------|-----|
| How a game runs | [`docs/EXECUTION_MODEL.md`](docs/EXECUTION_MODEL.md) |
| Architecture | [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) |
| Build the framework | [`docs/BUILDING.md`](docs/BUILDING.md) |
| **Ship a game repo** (submodules + CI + release checklist) | [`docs/GAME_PROJECT_SETUP.md`](docs/GAME_PROJECT_SETUP.md) |
| Setup-host CI template | [`docs/ci/templates/setup-release.yml`](docs/ci/templates/setup-release.yml) |
| Local Generate & rebuild CLI | [`docs/LOCAL_CODEGEN_SDK.md`](docs/LOCAL_CODEGEN_SDK.md) |
| Mods | [`docs/MOD_PACKAGES.md`](docs/MOD_PACKAGES.md) |
| Contributing | [`CONTRIBUTING.md`](CONTRIBUTING.md) |
## Which PlayStation BIOS does it use?
@@ -709,13 +718,16 @@ the selected faithfully recompiled BIOS — OpenBIOS or retail BIOS — is the
baseline and oracle, generated code is never hand-edited (fix the recompiler and
regenerate), and a change proves itself against the Beetle oracle / on screen
rather than by assertion. Game-specific work lives in the game repos, which pin
an exact framework commit as a submodule.
exact framework and UI commits as root-level submodules.
Read [`CONTRIBUTING.md`](CONTRIBUTING.md) before opening a PR — it covers the core
rules, how to verify a change, the regression checklist across the known games,
and how a framework fix reaches a game through its pin. Bugs and build problems go
to GitHub issues (include `gcc -v` / OS / generator for build failures); design
discussion happens in the **R.A.I.D.** Discord (invite below).
- New title / setup-host release:
[`docs/GAME_PROJECT_SETUP.md`](docs/GAME_PROJECT_SETUP.md)
- Framework PRs: [`CONTRIBUTING.md`](CONTRIBUTING.md) (rules, verification,
regression checklist, how a fix reaches a game through its pin)
Bugs and build problems go to GitHub issues (include `gcc -v` / OS / generator
for build failures); design discussion happens in the **R.A.I.D.** Discord
(invite below).
## License
+193
View File
@@ -0,0 +1,193 @@
# 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 |
| [`ci/README.md`](ci/README.md) | Shared CI scripts and composite actions |
| [`CONTRIBUTING.md`](../CONTRIBUTING.md) | Framework contribution rules |
---
## 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.
```text
YourGameRecomp/ # your git repo
├── .gitmodules
├── CMakeLists.txt # game target + setup-host options
├── game.toml # disc / recompiler / runtime config
├── VERSION # release pin (e.g. 0.1.0)
├── seeds/ # function-start seeds for psxrecomp-game
├── host/ # optional: codegen host glue for the wizard
├── codegen_setup.c / .h # optional: PSX_HAS_GAME_CODEGEN wiring
├── launcher_assets/ # boxart / fonts / brand (recomp-ui)
├── scripts/
│ └── package_setup_release.sh # title packager (thin; calls psxrecomp tools)
├── .github/workflows/
│ └── release.yml # copied from docs/ci/templates/setup-release.yml
├── psxrecomp/ # submodule → mstan/psxrecomp (pinned commit)
├── recomp-ui/ # submodule → TechnicallyComputers/recomp-ui
├── psxrecomp-sdk/ # optional: CLI / prepare_disc overlay for zips
├── generated/ # local only — gitignore (not in CI setup zip)
└── prepared_disc/ / disc/ # local disc working tree — gitignore
```
### Add the submodules
```bash
cd YourGameRecomp
git submodule add https://github.com/mstan/psxrecomp.git psxrecomp
git submodule add https://github.com/TechnicallyComputers/recomp-ui.git recomp-ui
# optional SDK surface used by RetComM / setup zips:
# git submodule add <your-sdk-url> psxrecomp-sdk
git submodule update --init --recursive
```
Pin **exact commits** (do not track `main` floating). Bump 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
1. **Legal disc** — place your dump where `game.toml` expects it (or pass
`--disc` to the CLI / wizard). Never commit disc images or retail BIOS dumps.
2. **Build emitters** (once per machine / after recompiler changes):
```bash
cmake -S psxrecomp/recompiler -B build-recompiler -G Ninja \
-DCMAKE_BUILD_TYPE=Release
cmake --build build-recompiler --target psxrecomp-game psxrecomp-bios -j"$(nproc)"
```
3. **Generate game (+ OpenBIOS) C** — either the setup wizard, or:
```bash
python3 psxrecomp/psxrecomp_cli.py generate \
--config game.toml --project-root . --disc /path/to/dump.cue
```
4. **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)"
```
Details: [`LOCAL_CODEGEN_SDK.md`](LOCAL_CODEGEN_SDK.md), [`BUILDING.md`](BUILDING.md).
---
## Setup-host releases (self-build zip)
CI ships a zip **without** `generated/` game C and **without** retail BIOS
dumps. 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)
- `toolchain/` — portable `cmake-clang-v1` from
[retcomm-toolchains](https://github.com/TechnicallyComputers/retcomm-toolchains)
- On Windows: MinGW runtime DLLs beside the host and emitters
Players (or [RetComM](https://github.com/TechnicallyComputers/RetComM-Launcher))
run Generate & rebuild locally. After a successful rebuild the CLI can prune
`toolchain/` and build intermediates.
Do **not** set `PSX_PGO` in CI. PGO stays user-local when `[pgo] enabled = true`.
---
## CI workflow template
1. Copy the template into your title repo:
```bash
mkdir -p .github/workflows
cp psxrecomp/docs/ci/templates/setup-release.yml .github/workflows/release.yml
```
2. Replace every `YOUR_*` placeholder (exe name, zip prefix, cmake setup flag,
release title, package script path).
3. Keep using the shared actions — do not reimplement toolchain fetch or DLL
bundling:
```yaml
- uses: ./psxrecomp/.github/actions/fetch-toolchain
with:
artifact: ${{ matrix.artifact }}
```
4. Your `scripts/package_setup_release.sh` should:
- Stage the host exe, assets, and game sources
- Copy filtered `psxrecomp/` + `recomp-ui/`
- Call `psxrecomp/tools/stage_setup_sdk.sh` (emitters, OpenBIOS checks,
`toolchain/`, MinGW DLLs)
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/` and `recomp-ui/` are root-level submodules on pinned commits
- [ ] `game.toml` has disc identity (`[prepare_disc]` hashes/sizes) and boot EXE
- [ ] Seeds cover the boot path; `VERSION` matches the release you will tag
- [ ] Disc images, `generated/`, and `SCPH1001.BIN` are gitignored
- [ ] Setup-host CMake path builds with **no** game C and **no** BIOS backends
(e.g. `-DPSXRECOMP_ALLOW_NO_BIOS=ON` + your title’s force-setup option)
- [ ] Codegen host / `PSX_HAS_GAME_CODEGEN` wired so the wizard can regenerate
### Packaging (shared helpers)
- [ ] CI builds `psxrecomp-game` and `psxrecomp-bios` into `build-recompiler/`
- [ ] CI uses `./psxrecomp/.github/actions/fetch-toolchain` for the matrix OS
- [ ] Packager calls `psxrecomp/tools/stage_setup_sdk.sh` with
`--recompiler-build`, `--toolchain-dir` (or env from fetch), and on
Windows `--runtime-bin /mingw64/bin` + `--host-exe`
- [ ] Staged tree includes `psxrecomp/psxrecomp_cli.py` (sdk overlay if needed)
- [ ] Staged `psxrecomp/bios/` has `OpenBIOS.toml`, `openbios.bin`,
`OpenBIOS.LICENSE`, `SCPH1001.toml` — and **no** retail `.BIN`
- [ ] Windows zip has `libstdc++-6.dll` + `libgcc_s_seh-1.dll` next to both
emitters (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
### 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
+5
View File
@@ -20,7 +20,12 @@ then game C, then rebuild links everything.
|-------|------|
| Setup exe | `recomp-ui` + codegen host; opens Generate & rebuild |
| Game zip `psxrecomp/` | `psxrecomp_cli.py`, `tools/`, `psxrecomp-game` + `psxrecomp-bios` |
| `tools/fetch_toolchain.sh` | CI: download/unpack portable `cmake-clang-v1` |
| `tools/stage_setup_sdk.sh` | CI/pack: emitters, OpenBIOS checks, embed `toolchain/`, MinGW DLLs |
| `tools/bundle_mingw_dlls.sh` | Windows: copy MinGW runtime DLLs next to host + emitters |
| Game zip `toolchain/` | Portable `cmake-clang-v1` (cmake/ninja/clang); pruned after rebuild |
| `docs/ci/` | Composite actions + [`templates/setup-release.yml`](ci/templates/setup-release.yml) |
| `docs/GAME_PROJECT_SETUP.md` | Submodules, CI template usage, bundled-release checklist |
| Game sources | `game.toml`, seeds, `CMakeLists.txt`, `psxrecomp/`, `recomp-ui/` |
RetComM harvests emitters + toolchain into shared caches (no separate tools
+65
View File
@@ -0,0 +1,65 @@
# CI helpers for setup-host releases
Shared scripts and GitHub Actions so new PSXRecomp titles do not reimplement
toolchain fetch, emitter staging, or Windows MinGW DLL bundling.
**Start here for a new game repo:**
[`../GAME_PROJECT_SETUP.md`](../GAME_PROJECT_SETUP.md)
## Workflow template
Copy into the **game** repository (not into this framework alone):
```bash
mkdir -p .github/workflows
cp psxrecomp/docs/ci/templates/setup-release.yml .github/workflows/release.yml
# edit YOUR_* placeholders
```
Template path: [`templates/setup-release.yml`](templates/setup-release.yml)
A shorter stub also lives at [`setup-release.yml.example`](setup-release.yml.example)
(points at the full template).
## Tools
| Script | Role |
|--------|------|
| `tools/fetch_toolchain.sh` | Download/unpack `cmake-clang-v1` from retcomm-toolchains |
| `tools/stage_setup_sdk.sh` | Stage emitters, OpenBIOS checks, `toolchain/`, MinGW DLLs |
| `tools/bundle_mingw_dlls.sh` | Copy imported non-system DLLs next to Windows PEs |
## Composite actions
Use from a game repo after `actions/checkout` with `submodules: recursive`:
```yaml
- uses: ./psxrecomp/.github/actions/fetch-toolchain
with:
artifact: ${{ matrix.artifact }} # linux-x64 | windows-x64 | macos-arm64 | macos-x64
- uses: ./psxrecomp/.github/actions/stage-setup-sdk
with:
stage: dist/stage-setup-${{ matrix.artifact }}
sdk-overlay: psxrecomp-sdk
recompiler-build: build-recompiler
runtime-bin: /mingw64/bin # Windows / MSYS2 only
```
`fetch-toolchain` exports `TOOLCHAIN_DIR`, `PSXRECOMP_TOOLCHAIN_DIR`, and
`BPE_TOOLCHAIN_DIR` via `GITHUB_ENV` for later steps.
## Title responsibilities
Keep in the game repo:
- Setup-host CMake flags and exe basename
- Copying game sources / assets into the stage
- Zip naming and release notes
- Thin `scripts/package_setup_release.sh` that stages title files then calls
`psxrecomp/tools/stage_setup_sdk.sh`
## Release checklist
See the **Bundled release checklist** in
[`../GAME_PROJECT_SETUP.md`](../GAME_PROJECT_SETUP.md#bundled-release-checklist).
+23
View File
@@ -0,0 +1,23 @@
# Deprecated stub — use the full template:
#
# docs/ci/templates/setup-release.yml
#
# Install:
# cp psxrecomp/docs/ci/templates/setup-release.yml .github/workflows/release.yml
#
# Guide: docs/GAME_PROJECT_SETUP.md
# Actions: docs/ci/README.md
name: Release builds (see docs/ci/templates/setup-release.yml)
on:
workflow_dispatch:
jobs:
redirect:
runs-on: ubuntu-24.04
steps:
- run: |
echo "Copy psxrecomp/docs/ci/templates/setup-release.yml to .github/workflows/release.yml"
echo "See psxrecomp/docs/GAME_PROJECT_SETUP.md"
exit 1
+270
View File
@@ -0,0 +1,270 @@
# Setup-host multi-platform release template for a PSXRecomp *game* repository.
#
# Install:
# mkdir -p .github/workflows
# cp psxrecomp/docs/ci/templates/setup-release.yml .github/workflows/release.yml
# Then replace every YOUR_* token. See docs/GAME_PROJECT_SETUP.md.
#
# Shared (do not fork these into the title):
# ./psxrecomp/.github/actions/fetch-toolchain
# ./psxrecomp/.github/actions/stage-setup-sdk
# psxrecomp/tools/{fetch_toolchain,stage_setup_sdk,bundle_mingw_dlls}.sh
#
# Title-owned:
# CMake setup-host flags, exe basename, zip prefix, package_setup_release.sh
name: Release builds
on:
workflow_dispatch:
inputs:
version:
description: 'Release version (e.g. 0.1.0 or v0.1.0)'
required: true
type: string
publish:
description: 'Create git tag vX.Y.Z and GitHub Release'
required: true
type: boolean
default: true
push:
tags:
- 'v*'
concurrency:
group: yourgame-release-${{ github.event_name }}-${{ github.ref }}-${{ github.run_id }}
cancel-in-progress: true
permissions:
contents: write
env:
CMAKE_BUILD_TYPE: Release
jobs:
prepare:
name: Resolve version
runs-on: ubuntu-24.04
outputs:
version: ${{ steps.meta.outputs.version }}
tag: ${{ steps.meta.outputs.tag }}
publish: ${{ steps.meta.outputs.publish }}
steps:
- name: Normalize version / tag
id: meta
shell: bash
run: |
set -euo pipefail
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
RAW="${{ github.event.inputs.version }}"
PUBLISH="${{ github.event.inputs.publish }}"
else
RAW="${{ github.ref_name }}"
PUBLISH="true"
fi
RAW="$(printf '%s' "${RAW}" | tr -d '[:space:]')"
VER="${RAW#v}"
if [[ ! "${VER}" =~ ^[0-9]+\.[0-9]+\.[0-9]+([.+-][A-Za-z0-9.+-]*)?$ ]]; then
echo "::error::invalid version '${RAW}' (want X.Y.Z or vX.Y.Z)"
exit 1
fi
echo "version=${VER}" >>"$GITHUB_OUTPUT"
echo "tag=v${VER}" >>"$GITHUB_OUTPUT"
echo "publish=${PUBLISH}" >>"$GITHUB_OUTPUT"
build:
name: ${{ matrix.artifact }}
needs: prepare
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-24.04
artifact: linux-x64
shell: bash
- os: windows-2022
artifact: windows-x64
shell: 'msys2 {0}'
- os: macos-15
artifact: macos-arm64
shell: bash
deployment_target: '11.0'
- os: macos-15-intel
artifact: macos-x64
shell: bash
deployment_target: '11.0'
defaults:
run:
shell: ${{ matrix.shell }}
env:
# Rename to match your packager (e.g. BPE_RELEASE_VERSION).
RELEASE_VERSION: ${{ needs.prepare.outputs.version }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
submodules: recursive
fetch-depth: 1
- name: Pin VERSION
shell: bash
run: |
set -euo pipefail
printf '%s\n' "${RELEASE_VERSION}" >VERSION
echo "VERSION=$(cat VERSION)"
- name: Setup host mode (no game C / BIOS backends in CI)
shell: bash
run: |
set -euo pipefail
rm -rf generated psxrecomp/generated
echo "Cleared generated/ — CI builds a BIOS-free setup host."
- name: Setup MSYS2 (Windows)
if: runner.os == 'Windows'
uses: msys2/setup-msys2@v2
with:
msystem: MINGW64
update: true
install: >-
mingw-w64-x86_64-toolchain
mingw-w64-x86_64-cmake
mingw-w64-x86_64-ninja
mingw-w64-x86_64-SDL2
mingw-w64-x86_64-pkgconf
curl
zip
- name: Install deps (Linux)
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
build-essential cmake ninja-build pkg-config \
libsdl2-dev libgl1-mesa-dev zip
- name: Install deps (macOS)
if: runner.os == 'macOS'
run: |
brew install cmake ninja pkg-config sdl3 || brew install cmake ninja pkg-config sdl2
- name: Build recompiler tools (emitters)
run: |
set -euo pipefail
JOBS="$(nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 4)"
if [[ -d /mingw64/bin ]]; then
export PATH="/mingw64/bin:${PATH}"
fi
cmake -S psxrecomp/recompiler -B build-recompiler -G Ninja \
-DCMAKE_BUILD_TYPE=Release
cmake --build build-recompiler \
--target psxrecomp-game psxrecomp-bios -j"${JOBS}"
for bin in psxrecomp-bios psxrecomp-game; do
if [[ -f "build-recompiler/${bin}.exe" ]]; then
chmod +x "build-recompiler/${bin}.exe"
elif [[ -f "build-recompiler/${bin}" ]]; then
chmod +x "build-recompiler/${bin}"
else
echo "::error::${bin} missing after recompiler build"
exit 1
fi
done
- name: Configure & build setup host
env:
CMAKE_OSX_DEPLOYMENT_TARGET: ${{ matrix.deployment_target || '' }}
run: |
set -euo pipefail
EXTRA=()
if [[ -n "${CMAKE_OSX_DEPLOYMENT_TARGET:-}" ]]; then
EXTRA+=("-DCMAKE_OSX_DEPLOYMENT_TARGET=${CMAKE_OSX_DEPLOYMENT_TARGET}")
fi
JOBS="$(nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 4)"
# YOUR_SETUP_CMAKE_FLAGS: title-specific. Example from BPE:
# -DYOURGAME_FORCE_SETUP_HOST=ON -DPSXRECOMP_ALLOW_NO_BIOS=ON
cmake -S . -B build-ci -G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DPSXRECOMP_ALLOW_NO_BIOS=ON \
-DYOURGAME_FORCE_SETUP_HOST=ON \
-DPSX_GAME_VERSION="${RELEASE_VERSION}" \
"${EXTRA[@]}"
cmake --build build-ci --target psx-runtime -j"${JOBS}"
- name: Fetch portable toolchain pack
uses: ./psxrecomp/.github/actions/fetch-toolchain
with:
artifact: ${{ matrix.artifact }}
- name: Package setup zip
run: |
set -euo pipefail
chmod +x scripts/package_setup_release.sh
if [[ "${{ runner.os }}" == "Windows" ]]; then
export PSXRECOMP_RUNTIME_BIN_DIR=/mingw64/bin
export BPE_RUNTIME_BIN_DIR=/mingw64/bin
fi
export RELEASE_VERSION
# Packager stages title files, then calls stage_setup_sdk.sh.
if [[ -z "${PSXRECOMP_TOOLCHAIN_DIR:-${TOOLCHAIN_DIR:-${BPE_TOOLCHAIN_DIR:-}}}" ]]; then
echo "::error::toolchain dir not set — fetch-toolchain failed?"
exit 1
fi
scripts/package_setup_release.sh build-ci "${{ matrix.artifact }}" build-recompiler
ls -la dist/
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
# YOUR_ZIP_PREFIX e.g. bpe, motk, yourgame
name: YOUR_ZIP_PREFIX-${{ matrix.artifact }}
path: dist/YOUR_ZIP_PREFIX-*-${{ matrix.artifact }}.zip
if-no-files-found: error
retention-days: 30
release:
name: GitHub Release
needs: [prepare, build]
if: needs.prepare.outputs.publish == 'true'
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/download-artifact@v4
with:
path: artifacts
- name: Flatten zips
run: |
set -euo pipefail
mkdir -p dist
find artifacts -type f -name 'YOUR_ZIP_PREFIX-*.zip' -exec cp -v {} dist/ \;
ls -la dist/
- name: Ensure release tag exists
if: github.event_name == 'workflow_dispatch'
env:
TAG: ${{ needs.prepare.outputs.tag }}
run: |
set -euo pipefail
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
if ! git rev-parse -q --verify "refs/tags/${TAG}" >/dev/null; then
git tag -a "${TAG}" -m "YOUR_GAME_TITLE ${TAG}"
fi
if ! git ls-remote --exit-code --tags origin "refs/tags/${TAG}" >/dev/null 2>&1; then
git push origin "refs/tags/${TAG}"
fi
- name: Publish release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ needs.prepare.outputs.tag }}
name: YOUR_GAME_TITLE ${{ needs.prepare.outputs.tag }}
files: |
dist/YOUR_ZIP_PREFIX-*.zip
generate_release_notes: true
fail_on_unmatched_files: true
target_commitish: ${{ github.sha }}
+225
View File
@@ -0,0 +1,225 @@
#!/usr/bin/env bash
# Bundle imported non-system DLLs next to Windows PE executables.
#
# Title packagers (BPE/MotK setup zips) call this so MSYS2 MinGW emitters
# (psxrecomp-game / psxrecomp-bios) ship with libstdc++/libgcc — the portable
# llvm-mingw toolchain does not provide those GCC runtimes.
#
# Usage:
# bundle_mingw_dlls.sh [options] --exe <path> [--dest <dir>] [--label <name>] ...
#
# Options:
# --runtime-bin DIR Preferred DLL search directory (repeatable)
# --search-dir DIR Extra DLL search directory (repeatable)
# --exe PATH PE to inspect (repeatable; --dest/--label apply to it)
# --dest DIR Copy DLLs here (default: dirname of the preceding --exe)
# --label NAME Log label for the preceding --exe
# --require DLL After bundling, DLL must exist in every --dest that
# imported it (repeatable)
# --soft-missing Warn instead of exit 1 when objdump is unavailable
#
# Exit 1 if an imported DLL cannot be found, or a --require check fails.
set -euo pipefail
SOFT_MISSING=0
RUNTIME_BINS=()
SEARCH_DIRS=()
REQUIRE=()
# Parallel arrays for targets.
EXE_PATHS=()
DEST_DIRS=()
LABELS=()
pending_dest=""
pending_label=""
usage() {
sed -n '2,22p' "$0" | sed 's/^# \{0,1\}//'
exit 2
}
flush_pending_meta() {
# Apply trailing --dest/--label to the last --exe.
local n="${#EXE_PATHS[@]}"
if [[ "$n" -eq 0 ]]; then
return 0
fi
local i=$((n - 1))
if [[ -n "${pending_dest}" ]]; then
DEST_DIRS[$i]="${pending_dest}"
pending_dest=""
fi
if [[ -n "${pending_label}" ]]; then
LABELS[$i]="${pending_label}"
pending_label=""
fi
}
while [[ $# -gt 0 ]]; do
case "$1" in
-h|--help) usage ;;
--soft-missing) SOFT_MISSING=1; shift ;;
--runtime-bin)
RUNTIME_BINS+=("${2:?}"); shift 2 ;;
--search-dir)
SEARCH_DIRS+=("${2:?}"); shift 2 ;;
--require)
REQUIRE+=("${2:?}"); shift 2 ;;
--exe)
flush_pending_meta
EXE_PATHS+=("${2:?}")
DEST_DIRS+=("")
LABELS+=("")
shift 2
;;
--dest)
pending_dest="${2:?}"; shift 2 ;;
--label)
pending_label="${2:?}"; shift 2 ;;
*)
echo "error: unknown arg: $1" >&2
usage
;;
esac
done
flush_pending_meta
if [[ ${#EXE_PATHS[@]} -eq 0 ]]; then
echo "error: at least one --exe is required" >&2
usage
fi
OBJDUMP=""
if command -v x86_64-w64-mingw32-objdump >/dev/null 2>&1; then
OBJDUMP="x86_64-w64-mingw32-objdump"
elif command -v objdump >/dev/null 2>&1; then
OBJDUMP="objdump"
else
if [[ "${SOFT_MISSING}" -eq 1 ]]; then
echo "warning: no objdump; skipping MinGW DLL bundling" >&2
exit 0
fi
echo "error: no objdump; cannot bundle MinGW DLLs" >&2
exit 1
fi
SYSTEM_DLL_RE='^(KERNEL32|USER32|GDI32|ADVAPI32|SHELL32|OLE32|OLEAUT32|WS2_32|WINMM|IMM32|SETUPAPI|VERSION|OPENGL32|COMCTL32|COMDLG32|RPCRT4|SHLWAPI|CRYPT32|BCRYPT|IPHLPAPI|NSI|DNSAPI|MSVCRT|UCRTBASE|VCRUNTIME|API-MS-).*\.DLL$'
PROBE_DLLS=(
SDL2.dll
zlib1.dll
libgcc_s_seh-1.dll
libstdc++-6.dll
libwinpthread-1.dll
libssp-0.dll
)
exe_imports_dll() {
local exe="$1"
local dll="$2"
"${OBJDUMP}" -p "${exe}" 2>/dev/null | grep -qi "DLL Name:[[:space:]]*${dll}"
}
bundle_one() {
local exe="$1"
local dest_dir="$2"
local label="$3"
local dll src key cand d
local -a needed=()
local -a unique=()
local -a candidates=()
local -A seen=()
if [[ ! -f "${exe}" ]]; then
echo "error: cannot bundle DLLs; missing ${exe}" >&2
exit 1
fi
if [[ -z "${dest_dir}" ]]; then
dest_dir="$(dirname "${exe}")"
fi
if [[ -z "${label}" ]]; then
label="$(basename "${exe}")"
fi
mkdir -p "${dest_dir}"
mapfile -t needed < <(
"${OBJDUMP}" -p "${exe}" 2>/dev/null \
| awk '/DLL Name:/{print $3}' \
| grep -viE "${SYSTEM_DLL_RE}" \
| sort -u || true
)
needed+=("${PROBE_DLLS[@]}")
for dll in "${needed[@]}"; do
[[ -n "${dll}" ]] || continue
key="$(printf '%s' "${dll}" | tr '[:upper:]' '[:lower:]')"
if [[ -n "${seen[$key]:-}" ]]; then
continue
fi
seen[$key]=1
unique+=("${dll}")
done
for dll in "${unique[@]}"; do
if ! exe_imports_dll "${exe}" "${dll}"; then
continue
fi
src=""
candidates=(
"$(dirname "${exe}")/${dll}"
"${dest_dir}/${dll}"
"/mingw64/bin/${dll}"
"/usr/x86_64-w64-mingw32/bin/${dll}"
)
for d in "${SEARCH_DIRS[@]+"${SEARCH_DIRS[@]}"}"; do
candidates+=("${d}/${dll}")
done
for d in "${RUNTIME_BINS[@]+"${RUNTIME_BINS[@]}"}"; do
candidates+=("${d}/${dll}")
done
for cand in "${candidates[@]}"; do
[[ -n "${cand}" ]] || continue
if [[ -f "${cand}" ]]; then
src="${cand}"
break
fi
done
if [[ -z "${src}" ]]; then
echo "error: required DLL missing for ${label}: ${dll}" >&2
echo " exe: ${exe}" >&2
echo " dest: ${dest_dir}" >&2
if [[ ${#SEARCH_DIRS[@]} -gt 0 ]]; then
echo " search-dirs: ${SEARCH_DIRS[*]}" >&2
fi
if [[ ${#RUNTIME_BINS[@]} -gt 0 ]]; then
echo " runtime-bins: ${RUNTIME_BINS[*]}" >&2
fi
exit 1
fi
cp -f "${src}" "${dest_dir}/"
echo "bundled ${dll} → ${dest_dir}/ (${label})"
done
}
for i in "${!EXE_PATHS[@]}"; do
bundle_one "${EXE_PATHS[$i]}" "${DEST_DIRS[$i]}" "${LABELS[$i]}"
done
for dll in "${REQUIRE[@]}"; do
[[ -n "${dll}" ]] || continue
for i in "${!EXE_PATHS[@]}"; do
exe="${EXE_PATHS[$i]}"
dest="${DEST_DIRS[$i]:-}"
if [[ -z "${dest}" ]]; then
dest="$(dirname "${exe}")"
fi
if ! exe_imports_dll "${exe}" "${dll}"; then
continue
fi
if [[ ! -f "${dest}/${dll}" ]]; then
echo "error: $(basename "${exe}") imported ${dll} but it is missing under ${dest}" >&2
exit 1
fi
done
done
+133
View File
@@ -0,0 +1,133 @@
#!/usr/bin/env bash
# Download and unpack a retcomm-toolchains cmake-clang-v1 pack.
#
# Usage:
# fetch_toolchain.sh --artifact <linux-x64|windows-x64|macos-arm64|macos-x64> \
# [--dl-dir DIR] [--out-dir DIR] [--repo OWNER/NAME]
#
# Prints: TOOLCHAIN_DIR=<absolute path with bin/>
# When GITHUB_ENV is set (Actions), also appends:
# TOOLCHAIN_DIR=...
# PSXRECOMP_TOOLCHAIN_DIR=...
# BPE_TOOLCHAIN_DIR=... (compat alias for existing title workflows)
#
# Env:
# GH_TOKEN / GITHUB_TOKEN — optional; used for gh or Authorization on curl
set -euo pipefail
ARTIFACT=""
DL_DIR=".cache/toolchain-dl"
OUT_DIR=".cache/toolchain-pack"
REPO="TechnicallyComputers/retcomm-toolchains"
usage() {
sed -n '2,16p' "$0" | sed 's/^# \{0,1\}//'
exit 2
}
while [[ $# -gt 0 ]]; do
case "$1" in
-h|--help) usage ;;
--artifact) ARTIFACT="${2:?}"; shift 2 ;;
--dl-dir) DL_DIR="${2:?}"; shift 2 ;;
--out-dir) OUT_DIR="${2:?}"; shift 2 ;;
--repo) REPO="${2:?}"; shift 2 ;;
*)
echo "error: unknown arg: $1" >&2
usage
;;
esac
done
if [[ -z "${ARTIFACT}" ]]; then
echo "error: --artifact is required" >&2
usage
fi
case "${ARTIFACT}" in
linux-x64)
PATTERN='cmake-clang-v1*linux*.zip'
ASSET_NAME='cmake-clang-v1-linux-x64.zip'
;;
windows-x64)
PATTERN='cmake-clang-v1*windows*.zip'
ASSET_NAME='cmake-clang-v1-windows-x64.zip'
;;
macos-arm64|macos-x64)
PATTERN='cmake-clang-v1*macos*.zip'
ASSET_NAME='cmake-clang-v1-macos-universal.zip'
;;
*)
echo "error: unknown artifact '${ARTIFACT}' (want linux-x64|windows-x64|macos-arm64|macos-x64)" >&2
exit 1
;;
esac
TOKEN="${GH_TOKEN:-${GITHUB_TOKEN:-}}"
rm -rf "${DL_DIR}" "${OUT_DIR}"
mkdir -p "${DL_DIR}" "${OUT_DIR}"
if command -v gh >/dev/null 2>&1; then
if [[ -n "${TOKEN}" ]]; then
export GH_TOKEN="${TOKEN}"
fi
gh release download -R "${REPO}" -p "${PATTERN}" -D "${DL_DIR}" --clobber
elif command -v curl >/dev/null 2>&1; then
ASSET_URL="https://github.com/${REPO}/releases/latest/download/${ASSET_NAME}"
echo "gh not on PATH; curling ${ASSET_URL}"
curl_args=(-fsSL -o "${DL_DIR}/${ASSET_NAME}")
if [[ -n "${TOKEN}" ]]; then
curl_args+=(-H "Authorization: Bearer ${TOKEN}")
fi
curl "${curl_args[@]}" "${ASSET_URL}"
else
echo "error: neither gh nor curl available to fetch toolchain pack" >&2
exit 1
fi
ZIP="$(find "${DL_DIR}" -maxdepth 1 -type f -name '*.zip' | head -n1)"
if [[ -z "${ZIP}" ]]; then
echo "error: no toolchain zip matching ${PATTERN}" >&2
ls -la "${DL_DIR}" >&2 || true
exit 1
fi
echo "Unpacking toolchain from ${ZIP}"
if command -v unzip >/dev/null 2>&1; then
unzip -q "${ZIP}" -d "${OUT_DIR}"
else
python3 -c '
import pathlib, zipfile, sys
z = next(pathlib.Path(sys.argv[1]).glob("*.zip"))
zipfile.ZipFile(z).extractall(sys.argv[2])
' "${DL_DIR}" "${OUT_DIR}"
fi
# Unwrap single top-level directory if present.
shopt -s nullglob
entries=("${OUT_DIR}"/*)
shopt -u nullglob
if [[ ${#entries[@]} -eq 1 && -d "${entries[0]}" ]]; then
TOOLCHAIN_DIR="$(cd "${entries[0]}" && pwd)"
else
TOOLCHAIN_DIR="$(cd "${OUT_DIR}" && pwd)"
fi
if [[ ! -d "${TOOLCHAIN_DIR}/bin" ]]; then
echo "error: toolchain pack missing bin/ under ${TOOLCHAIN_DIR}" >&2
find "${OUT_DIR}" -maxdepth 3 >&2 || true
exit 1
fi
echo "TOOLCHAIN_DIR=${TOOLCHAIN_DIR}"
if [[ -n "${GITHUB_ENV:-}" ]]; then
{
echo "TOOLCHAIN_DIR=${TOOLCHAIN_DIR}"
echo "PSXRECOMP_TOOLCHAIN_DIR=${TOOLCHAIN_DIR}"
echo "BPE_TOOLCHAIN_DIR=${TOOLCHAIN_DIR}"
} >>"${GITHUB_ENV}"
fi
if [[ -n "${GITHUB_OUTPUT:-}" ]]; then
echo "toolchain-dir=${TOOLCHAIN_DIR}" >>"${GITHUB_OUTPUT}"
fi
+251
View File
@@ -0,0 +1,251 @@
#!/usr/bin/env bash
# Stage psxrecomp emitters + OpenBIOS surface + optional portable toolchain
# into an existing setup-host release stage directory.
#
# Title packagers copy the host exe, game sources, and framework tree first,
# then call this to finish the RetComM/wizard-complete zip layout.
#
# Usage:
# stage_setup_sdk.sh --stage <stage-dir> [options]
#
# Options:
# --framework DIR psxrecomp source tree (default: <cwd>/psxrecomp)
# --sdk-overlay DIR Overlay (e.g. psxrecomp-sdk) onto stage/psxrecomp
# --recompiler-build DIR Where psxrecomp-game/bios were built (repeatable)
# --toolchain-dir DIR Pack root with bin/; embedded as stage/toolchain/
# --allow-no-toolchain Warn instead of failing when toolchain unset
# --host-exe PATH Host PE for Windows DLL bundling (optional)
# --runtime-bin DIR MinGW runtime DLL search dir (repeatable)
# --search-dir DIR Extra DLL search dir (repeatable)
# --require-cli Require psxrecomp_cli.py after overlays (default on)
#
# Env aliases for toolchain dir (first wins):
# PSXRECOMP_TOOLCHAIN_DIR, TOOLCHAIN_DIR, BPE_TOOLCHAIN_DIR
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
FW_TOOLS="${SCRIPT_DIR}"
STAGE=""
FRAMEWORK=""
SDK_OVERLAY=""
RECOMPILER_BUILDS=()
TOOLCHAIN_DIR="${PSXRECOMP_TOOLCHAIN_DIR:-${TOOLCHAIN_DIR:-${BPE_TOOLCHAIN_DIR:-}}}"
ALLOW_NO_TOOLCHAIN=0
HOST_EXE=""
RUNTIME_BINS=()
SEARCH_DIRS=()
REQUIRE_CLI=1
usage() {
sed -n '2,28p' "$0" | sed 's/^# \{0,1\}//'
exit 2
}
while [[ $# -gt 0 ]]; do
case "$1" in
-h|--help) usage ;;
--stage) STAGE="${2:?}"; shift 2 ;;
--framework) FRAMEWORK="${2:?}"; shift 2 ;;
--sdk-overlay) SDK_OVERLAY="${2:?}"; shift 2 ;;
--recompiler-build) RECOMPILER_BUILDS+=("${2:?}"); shift 2 ;;
--toolchain-dir) TOOLCHAIN_DIR="${2:?}"; shift 2 ;;
--allow-no-toolchain) ALLOW_NO_TOOLCHAIN=1; shift ;;
--host-exe) HOST_EXE="${2:?}"; shift 2 ;;
--runtime-bin) RUNTIME_BINS+=("${2:?}"); shift 2 ;;
--search-dir) SEARCH_DIRS+=("${2:?}"); shift 2 ;;
--require-cli) REQUIRE_CLI=1; shift ;;
--no-require-cli) REQUIRE_CLI=0; shift ;;
*)
echo "error: unknown arg: $1" >&2
usage
;;
esac
done
if [[ -z "${STAGE}" ]]; then
echo "error: --stage is required" >&2
usage
fi
STAGE="$(cd "${STAGE}" && pwd)"
if [[ -z "${FRAMEWORK}" ]]; then
if [[ -d "${PWD}/psxrecomp" ]]; then
FRAMEWORK="$(cd "${PWD}/psxrecomp" && pwd)"
else
FRAMEWORK="$(cd "${SCRIPT_DIR}/.." && pwd)"
fi
else
FRAMEWORK="$(cd "${FRAMEWORK}" && pwd)"
fi
mkdir -p "${STAGE}/psxrecomp"
# Optional SDK overlay (CLI / prepare_disc / progress helpers).
if [[ -n "${SDK_OVERLAY}" ]]; then
if [[ ! -d "${SDK_OVERLAY}" ]]; then
echo "error: --sdk-overlay not a directory: ${SDK_OVERLAY}" >&2
exit 1
fi
if command -v rsync >/dev/null 2>&1; then
rsync -a "${SDK_OVERLAY}/" "${STAGE}/psxrecomp/"
else
cp -a "${SDK_OVERLAY}/." "${STAGE}/psxrecomp/"
fi
echo "overlaid SDK from ${SDK_OVERLAY}"
fi
find_tool_bin() {
local name="$1"
local dir cand
local -a roots=()
local r
for r in "${RECOMPILER_BUILDS[@]+"${RECOMPILER_BUILDS[@]}"}"; do
roots+=("$(cd "${r}" && pwd)")
done
roots+=(
"${STAGE}/psxrecomp/recompiler/build"
"${FRAMEWORK}/recompiler/build"
"${PWD}/build-recompiler"
)
for dir in "${roots[@]}"; do
[[ -d "${dir}" ]] || continue
for cand in \
"${dir}/${name}" \
"${dir}/${name}.exe" \
"${dir}/Release/${name}.exe"
do
if [[ -f "${cand}" ]]; then
echo "${cand}"
return 0
fi
done
done
return 1
}
GAME_BIN="$(find_tool_bin psxrecomp-game || true)"
BIOS_BIN="$(find_tool_bin psxrecomp-bios || true)"
if [[ -z "${GAME_BIN}" ]]; then
echo "error: psxrecomp-game not found (pass --recompiler-build)" >&2
exit 1
fi
if [[ -z "${BIOS_BIN}" ]]; then
echo "error: psxrecomp-bios not found (required for OpenBIOS regen)" >&2
exit 1
fi
mkdir -p "${STAGE}/psxrecomp/recompiler/build"
cp -a "${GAME_BIN}" \
"${STAGE}/psxrecomp/recompiler/build/$(basename "${GAME_BIN}")"
cp -a "${BIOS_BIN}" \
"${STAGE}/psxrecomp/recompiler/build/$(basename "${BIOS_BIN}")"
chmod +x "${STAGE}/psxrecomp/recompiler/build/$(basename "${GAME_BIN}")" 2>/dev/null || true
chmod +x "${STAGE}/psxrecomp/recompiler/build/$(basename "${BIOS_BIN}")" 2>/dev/null || true
echo "staged emitters from ${GAME_BIN%/*}"
cat >"${STAGE}/psxrecomp/retcomm-sdk.json" <<'EOF'
{
"cli": "psxrecomp_cli.py",
"id": "psxrecomp-tools",
"game_bin": "recompiler/build/psxrecomp-game",
"bios_bin": "recompiler/build/psxrecomp-bios"
}
EOF
for f in OpenBIOS.toml openbios.bin OpenBIOS.LICENSE SCPH1001.toml; do
if [[ ! -f "${STAGE}/psxrecomp/bios/${f}" ]]; then
echo "error: missing psxrecomp/bios/${f} in staged tree" >&2
exit 1
fi
done
if [[ "${REQUIRE_CLI}" -eq 1 && ! -f "${STAGE}/psxrecomp/psxrecomp_cli.py" ]]; then
echo "error: missing psxrecomp/psxrecomp_cli.py (pass --sdk-overlay or copy CLI into stage)" >&2
exit 1
fi
# Never ship retail BIOS dumps from the stage.
rm -f "${STAGE}/psxrecomp/bios/SCPH1001.BIN" \
"${STAGE}/psxrecomp/bios/SCPH1001.bin" 2>/dev/null || true
if [[ -n "${TOOLCHAIN_DIR}" && -d "${TOOLCHAIN_DIR}" ]]; then
if [[ ! -d "${TOOLCHAIN_DIR}/bin" ]]; then
echo "error: toolchain dir missing bin/: ${TOOLCHAIN_DIR}" >&2
exit 1
fi
mkdir -p "${STAGE}/toolchain"
if command -v rsync >/dev/null 2>&1; then
rsync -a --delete "${TOOLCHAIN_DIR}/" "${STAGE}/toolchain/"
else
rm -rf "${STAGE}/toolchain"
mkdir -p "${STAGE}/toolchain"
cp -a "${TOOLCHAIN_DIR}/." "${STAGE}/toolchain/"
fi
echo "bundled toolchain from ${TOOLCHAIN_DIR}"
elif [[ "${ALLOW_NO_TOOLCHAIN}" -eq 1 ]]; then
echo "warning: toolchain unset — zip will need system cmake/ninja" >&2
else
echo "error: toolchain dir required (pass --toolchain-dir or set PSXRECOMP_TOOLCHAIN_DIR)" >&2
exit 1
fi
# Windows MinGW DLL bundling for host + emitters.
BUNDLE="${FW_TOOLS}/bundle_mingw_dlls.sh"
if [[ ! -f "${BUNDLE}" ]]; then
# Staged copy (after sdk overlay) may own the helper.
if [[ -f "${STAGE}/psxrecomp/tools/bundle_mingw_dlls.sh" ]]; then
BUNDLE="${STAGE}/psxrecomp/tools/bundle_mingw_dlls.sh"
fi
fi
need_dlls=0
if [[ -n "${HOST_EXE}" && "${HOST_EXE}" == *.exe ]]; then
need_dlls=1
fi
if [[ -f "${STAGE}/psxrecomp/recompiler/build/psxrecomp-game.exe" ]]; then
need_dlls=1
fi
if [[ "${need_dlls}" -eq 1 ]]; then
if [[ ! -f "${BUNDLE}" ]]; then
echo "error: bundle_mingw_dlls.sh not found next to stage_setup_sdk.sh" >&2
exit 1
fi
chmod +x "${BUNDLE}" 2>/dev/null || true
for emitter in psxrecomp-game.exe psxrecomp-bios.exe; do
if [[ ! -f "${STAGE}/psxrecomp/recompiler/build/${emitter}" ]]; then
echo "error: missing ${emitter} in staged tree" >&2
exit 1
fi
done
args=()
for d in "${RUNTIME_BINS[@]+"${RUNTIME_BINS[@]}"}"; do
args+=(--runtime-bin "${d}")
done
for d in "${SEARCH_DIRS[@]+"${SEARCH_DIRS[@]}"}"; do
args+=(--search-dir "${d}")
done
if [[ -n "${HOST_EXE}" && -f "${HOST_EXE}" ]]; then
args+=(
--exe "${HOST_EXE}"
--dest "${STAGE}"
--label "$(basename "${HOST_EXE}")"
)
fi
args+=(
--exe "${STAGE}/psxrecomp/recompiler/build/psxrecomp-game.exe"
--dest "${STAGE}/psxrecomp/recompiler/build"
--label "psxrecomp-game.exe"
--exe "${STAGE}/psxrecomp/recompiler/build/psxrecomp-bios.exe"
--dest "${STAGE}/psxrecomp/recompiler/build"
--label "psxrecomp-bios.exe"
--require libgcc_s_seh-1.dll
--require libstdc++-6.dll
)
bash "${BUNDLE}" "${args[@]}"
fi
echo "stage_setup_sdk: ready under ${STAGE}/psxrecomp"