libchdr (BSD-3-Clause) and toml11 (MIT) are statically linked into the
runtime and both require the notice in binary redistributions;
runtime/licenses/ existed but nothing copied it into a package.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Mods under active development had nowhere to live: a package was either in
the catalog and therefore in every release, or not built at all. Widescreen is
the immediate case -- it engages, but it is unvalidated as gameplay and FF7's
pre-rendered field backdrops still cull at the 4:3 bound, so it is not
something to put in front of players yet.
A package now declares its own maturity with channel = "developer". It still
ships with ordinary local builds and local exports, so it can be tested
exactly as a player would see it, and release packaging drops it.
The exclusion defaults from $CI -- on under any CI provider, off locally.
That is deliberate: a per-workflow opt-in is precisely the shape that left 21
of 23 ports shipping no mods at all, so this must not need 23 release
workflows edited to take effect. --exclude-dev-mods / --include-dev-mods and
EXCLUDE_DEV_MODS=0|1 override in either direction, and Studio's
`build export --exclude-dev-mods` reproduces what a release contains.
Applied in all three packagers (setup-host, Windows, macOS). Each prunes the
whole PACKAGE directory and then asserts no developer manifest survived --
including the SOURCE catalog under mods/preloaded, since the setup-host zip
rebuilds on the player's machine and would otherwise re-stage the package the
release just removed.
A manifest is never rewritten during packaging: editing a package while
packaging it would change content meant to be verifiable, which is why the
marker is package-level rather than per-feature.
Verified by packaging: local keeps 5 catalogs, CI=true drops to 4 with no
ff7.enhancements entry anywhere in the zip, and EXCLUDE_DEV_MODS=0 under
CI=true restores it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0194TYJAqgyG9a9PJe9uUcgQ
A built title stages its mod catalog next to the exe, but nothing put it in
the shipped package. Every release went out with an empty Mods page.
The packager already had --runtime-dir mods; it was opt-in and RUNTIME_DIRS
defaulted to empty, so each title had to pass it from its own generated
wrapper. An audit of the workspace found 21 of 23 ports never did, and the
two that did used the -optional variant that warns instead of failing. A
capability that exists, defaults off, and fails silently.
- package_setup_host.sh stages mods/ by DEFAULT and REQUIRES it. runtime.cmake
guarantees <exe>/mods for every non-oracle target, so a missing one means
broken build wiring and should fail the release rather than quietly ship an
empty catalog. --no-mods opts out a title with none; an explicit
--runtime-dir-optional mods still wins. It also ships the SOURCE catalog,
because the setup-host zip rebuilds on the player's machine and that rebuild
re-runs POST_BUILD staging from the source tree -- without it a rebuilt exe
silently drops every game-owned mod and keeps only framework builtins.
- package_release.ps1 (Windows/MinGW) had no mod handling at all. It now
stages the catalog before the stray-file scan, so the existing gate covers
it, strips the build machine's mods/state.toml, and asserts at least one
manifest survived -- matching the file's own assert-rather-than-trust idiom.
- package_release_macos.sh shipped ONE package where the player should have
seen five. It took the catalog from mods/preloaded "never the build tree",
but framework builtins (PGXP, fast loading, CD speed, bezel) live in
psxrecomp/mods/builtin and only ever appear next to the exe. The real hazard
was never the build tree, it was state.toml inside it -- so take the build
tree and delete that one file. --mods-src still overrides.
- The wrapper template gains --project-dir src so future titles ship their
game-owned trusted-plugin sources; CMakeLists.txt references them
unconditionally, so the setup-host rebuild cannot configure without them.
Studio could build a title but never bundle one: the only route to a
distributable was install-CI, commit, push, wait. That is how an empty Mods
page survived unnoticed -- nobody produced a package locally to look inside.
buildops.export_release() runs the SAME wrapper CI runs, so a local zip and a
released one are the same artifact by construction; exposed as
`build export` on the CLI and an "Export release zip" button in the GUI.
Artifact tags default to the host and match the CI matrix.
Verified by packaging, not by reading: default produces 5 catalogs with no
state.toml; --no-mods reproduces the old behaviour at 0.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0194TYJAqgyG9a9PJe9uUcgQ
tools/package_release.ps1 packages this framework's BIOS-only runtime, and
each game repo carries its own Windows packager. There was no macOS
equivalent at either level, so the first macOS release (Crash Bandicoot) was
assembled by hand and TombaRecomp already carries a private copy of most of
this logic. Add the generic one here so titles configure rather than
duplicate it.
Takes an already-built runtime plus the per-game identity and produces a
signed, relocatable .app in a staged release folder, archived as
<package>-<version>-macos-<arch>.zip with a .sha256 sidecar — the naming the
game repos' Windows releases already use. It does not build; the game's
build script builds, then calls this.
Release gates, porting the intent of the Windows packager to macOS:
- self-containment via otool (only OS libraries or bundled Frameworks)
- no baked absolute BIOS path, and no build-machine home path. The Windows
gate keys on a drive letter; the macOS tell is a path rooted under
/Users, /Volumes and friends. Matching bare "/...SCPH*.BIN" instead would
flag both the correct relative default "bios/SCPH1001.BIN" and the usage
string "/path/to/SCPH1001.BIN" in every binary.
- the 512 KiB OpenBIOS image and its MIT notice are present
- every --require-mod package is present, so a title cannot ship a launcher
whose Mods page silently vanished
- no disc / retail BIOS / memory-card / generated / local-state files
- saves/ ships empty
- the .app carries a valid signature
The mod catalog is taken from the repo's reviewed source, never the build
tree: a developer build dir accumulates mods/state.toml with enhancements
switched on, which would silently flip a title's default presentation for
every player. Packaging from a build tree without --mods-src is refused.
Bundle layout: staged data lives in Contents/Resources with symlinks from
Contents/MacOS. The runtime resolves bios/, assets/ and mods/ beside the
real executable, but codesign treats anything non-executable under
Contents/MacOS as nested code and rejects the whole bundle —
mods/packages/<pkg>/<version>/ parses as a versioned bundle. codesign seals
a symlink without descending, so this signs cleanly and path resolution is
unchanged. Signing is deliberately not --deep for the same reason.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>