feat: RecompLauncher support (recomplauncher.json, make_game --game/--prepare) #1102

Merged
Xarishark merged 8 commits from feat/recomplauncher-support into main 2026-11-10 16:30:52 +00:00
Xarishark commented 2026-11-09 11:18:44 +00:00 (Migrated from github.com)

Hi! This makes Hookshot work with RecompLauncher, which installs and updates recomps from their GitHub releases. Nothing changes for anyone who doesn't use it.

What players get (Linux AppImage and Windows)

  • Install Hookshot from RecompLauncher, pick the Switch 2 .nup (or a folder with romfs and exefs), and the launcher builds the game for you with a progress bar, then starts it with Play.
  • The in-game menu's options (quality preset, fullscreen, frame generation, resolution, upscaling, text speed and more) show on the game's page and in Big Picture mode. They edit hookshot_options.ini, the same file the in-game menu writes.
  • Saves and the game folder are one click away.

What's in it

  1. recomplauncher.json at the repo root, and scripts/linux/appimage.sh copies it into the AppImage at /recomplauncher.json. It validates against the schema. A small test (tests/test_recomplauncher_json.py) checks that every settings key is a row of the in-game menu.

  2. Code change (its own commit, nx2/make_game.py + tests/test_make_game_prepare.py): two new opt-in options.

    • --game <.nup or folder> is the same as --nup, or --romfs and --exefs.
    • --prepare prints progress 0.4 Text lines instead of the builder app's @@ lines, builds into ~/Games/Hookshot unless --out is given, and exits 0 on success.

    They only apply when those arguments are passed. Without them make_game.py, the builder windows and the game behave exactly as before. The existing read, check and build code is reused as is.

  3. Windows (own commits): HookshotBuilder.exe --cli <make_game options> and --play <folder> (builder/setup/setup.c, builder/HookshotBuilder/App.xaml.cs). --cli installs the builder the usual way but with no window, then runs make_game.py with the output on stdout/stderr and its result as the exit code. --play starts bin\hookshot.exe and waits for it. The windows workflow also makes Hookshot-<tag>-Windows-x64.zip (the setup beside recomplauncher.json) and uploads it with the release. The setup and the builder start exactly as before unless those two arguments are passed.

What doesn't change

The game, graphics, saves, config formats and the builder's own start are untouched. No game files are added.

How it was tested

  • recomplauncher.json validates against the published schema except for the new check.nup_build_id, which RecompLauncher's schema in #39 adds. Game-free tests cover the new options and that every settings key is a row of the in-game menu.
  • On Windows, with the real setup exe and a real copy of the game: HookshotBuilder.exe --cli --check reported the supported version, --cli --prepare built the game in about 15 minutes (exit 0, ordered progress lines), and --play started it. The Windows builder workflow compiles and publishes on GitHub's runner.
  • RecompLauncher's reader for nup_build_id returns e0f3ccfa… for the real Ocarina of Time .nup and a different build for five other Switch 2 packages.
  • Not tested: RecompLauncher itself end to end with this file, the Linux AppImage build, and the new Windows zip step (it only runs on tags).
  • The Game-free source regressions run has one failure, tests/test_program_answer_domains.py (a gpu/vk fixture, answers_in undeclared). It is not touched by this PR.

For the maintainer

  • The release job that uploads the Windows zip is still off until CI_RELEASE_WINDOWS is set.
  • RecompLauncher shows a game's setup as maintained by its developer if you tell them you ship and keep your own recomplauncher.json.
  • recomplauncher.json has "check": { "nup_build_id": "e0f3ccfa" }, so RecompLauncher refuses a .nup (or a folder with exefs/main) of another build before the build starts. It needs the launcher change in elripalda/RecompLauncher#39. A launcher without it ignores the field and checks nothing, as before. The published schema doesn't list the field until that is released, so schema validation may flag it until then.
  • No dumps list: Redump doesn't cover Switch 2 packages, and no checksums are made up.
Hi! This makes Hookshot work with [RecompLauncher](https://recomplauncher.com), which installs and updates recomps from their GitHub releases. Nothing changes for anyone who doesn't use it. ## What players get (Linux AppImage and Windows) - Install Hookshot from RecompLauncher, pick the Switch 2 `.nup` (or a folder with `romfs` and `exefs`), and the launcher builds the game for you with a progress bar, then starts it with Play. - The in-game menu's options (quality preset, fullscreen, frame generation, resolution, upscaling, text speed and more) show on the game's page and in Big Picture mode. They edit `hookshot_options.ini`, the same file the in-game menu writes. - Saves and the game folder are one click away. ## What's in it 1. **`recomplauncher.json`** at the repo root, and `scripts/linux/appimage.sh` copies it into the AppImage at `/recomplauncher.json`. It validates against the [schema](https://recomplauncher.com/schema/recomplauncher.json). A small test (`tests/test_recomplauncher_json.py`) checks that every settings key is a row of the in-game menu. 2. **Code change** (its own commit, `nx2/make_game.py` + `tests/test_make_game_prepare.py`): two new opt-in options. - `--game <.nup or folder>` is the same as `--nup`, or `--romfs` and `--exefs`. - `--prepare` prints `progress 0.4 Text` lines instead of the builder app's `@@` lines, builds into `~/Games/Hookshot` unless `--out` is given, and exits 0 on success. They only apply when those arguments are passed. Without them `make_game.py`, the builder windows and the game behave exactly as before. The existing read, check and build code is reused as is. 3. **Windows** (own commits): `HookshotBuilder.exe --cli <make_game options>` and `--play <folder>` (`builder/setup/setup.c`, `builder/HookshotBuilder/App.xaml.cs`). `--cli` installs the builder the usual way but with no window, then runs `make_game.py` with the output on stdout/stderr and its result as the exit code. `--play` starts `bin\hookshot.exe` and waits for it. The windows workflow also makes `Hookshot-<tag>-Windows-x64.zip` (the setup beside `recomplauncher.json`) and uploads it with the release. The setup and the builder start exactly as before unless those two arguments are passed. ## What doesn't change The game, graphics, saves, config formats and the builder's own start are untouched. No game files are added. ## How it was tested - `recomplauncher.json` validates against the [published schema](https://recomplauncher.com/schema/recomplauncher.json) except for the new `check.nup_build_id`, which RecompLauncher's schema in #39 adds. Game-free tests cover the new options and that every settings key is a row of the in-game menu. - On Windows, with the real setup exe and a real copy of the game: `HookshotBuilder.exe --cli --check` reported the supported version, `--cli --prepare` built the game in about 15 minutes (exit 0, ordered `progress` lines), and `--play` started it. The Windows builder workflow compiles and publishes on GitHub's runner. - RecompLauncher's reader for `nup_build_id` returns `e0f3ccfa…` for the real Ocarina of Time `.nup` and a different build for five other Switch 2 packages. - Not tested: RecompLauncher itself end to end with this file, the Linux AppImage build, and the new Windows zip step (it only runs on tags). - The `Game-free source regressions` run has one failure, `tests/test_program_answer_domains.py` (a `gpu/vk` fixture, `answers_in` undeclared). It is not touched by this PR. ## For the maintainer - The release job that uploads the Windows zip is still off until `CI_RELEASE_WINDOWS` is set. - RecompLauncher shows a game's setup as maintained by its developer if you tell them you ship and keep your own `recomplauncher.json`. - `recomplauncher.json` has `"check": { "nup_build_id": "e0f3ccfa" }`, so RecompLauncher refuses a `.nup` (or a folder with `exefs/main`) of another build before the build starts. It needs the launcher change in [elripalda/RecompLauncher#39](https://github.com/elripalda/RecompLauncher/pull/39). A launcher without it ignores the field and checks nothing, as before. The published schema doesn't list the field until that is released, so schema validation may flag it until then. - No `dumps` list: Redump doesn't cover Switch 2 packages, and no checksums are made up.
hckrman101 (Migrated from github.com) reviewed 2026-11-09 23:24:42 +00:00
hckrman101 (Migrated from github.com) left a comment

Thanks, this is a tidy change and the opt-in story holds up: without --game/--prepare, PREPARE stays False and say() still prints the @@ lines (the test covers that), the new --game mapping only runs when passed, and the --cli/--play checks in setup.c/App.xaml.cs only trigger on those exact first arguments. The new tests are picked up by the tests/test_*.py glob in run_all.py, so no registration is needed. The AppImage side needs no change either, since AppRun already has --cli and --play <folder>. A few things I'd like fixed or decided before merging:

1. Windows --cli --prepare failures lose their message. In prepare mode say_prepare writes error ... to stderr. Runner.Start forwards stderr through Line (so it ends up on stdout in --cli), but only @@error lines set error. So on a failed build Cli() prints the generic "The build tools stopped without saying why" on stderr and exits 1, while the real reason went to stdout. A launcher that shows stderr will show the useless message. Either have say_prepare also print something Runner recognises, or have Cli parse error lines too.

2. --check is silent with --prepare. say("check", ...) falls through say_prepare, which only handles stage/progress/error/done, so --prepare --check prints nothing. Probably fine since the manifest uses nup_build_id instead, but then --prepare shouldn't claim to work with --check, or check should be printed.

3. --game is not validated. A missing .nup hits os.stat(a.nup) and ends in a Python traceback (after ~/Games/Hookshot has been created), and a folder without romfs/exefs fails later inside the copy. Since the path comes from an external launcher, a one-line fail("... not found") up front (and for the folder: both romfs and exefs must exist) would give RecompLauncher a clean error line.

4. Naming/overlap with #1026. #1026 (shaders prepared at the end of the build) adds --no-prepare-shaders, the game's --prepare-shaders, and prepare_shaders() in the same make_game.py tail, so "prepare" will mean two different things (build everything for the launcher vs. the shader step). The argparse flags don't collide (--prepare is an exact match), but I'd consider something like --launcher / --progress-lines. More concretely, show_prepared in #1026 passes @@stage/@@progress lines and the "shaders were not prepared" text straight through print, bypassing say(), so in --prepare mode those would reach the launcher as raw @@ lines instead of progress <n> <text>. Whichever lands second needs to route that through say(). The two also edit the same hunk near say("done", out) and parse_args, so expect a rebase.

5. #1179 (profiles/save import). It touches the builder/launcher (MainWindow*, App.xaml, hookshot-launch, run_all.py). I didn't dig into its save layout, but recomplauncher.json hardcodes the saves at {local}/hookshot and {home}/Games/Hookshot/user; if profiles move where saves live, the manifest needs updating. Worth cross-checking whichever merges second.

Nits: --play's parser in setup.c only handles one optionally-quoted path (fine for how a launcher passes it), and the {home}/Games/Hookshot default is hardcoded in the manifest, so a player who built somewhere else gets "No built game to start", which is at least a clear message. The Windows zip step and glob look right to me (dist/ is the artifact root, and the tag-only release job is as described).

CI: GitHub reports no checks on this branch, so I couldn't confirm a green run. I didn't build or run anything.

Thanks, this is a tidy change and the opt-in story holds up: without `--game`/`--prepare`, `PREPARE` stays False and `say()` still prints the `@@` lines (the test covers that), the new `--game` mapping only runs when passed, and the `--cli`/`--play` checks in `setup.c`/`App.xaml.cs` only trigger on those exact first arguments. The new tests are picked up by the `tests/test_*.py` glob in `run_all.py`, so no registration is needed. The AppImage side needs no change either, since `AppRun` already has `--cli` and `--play <folder>`. A few things I'd like fixed or decided before merging: **1. Windows `--cli --prepare` failures lose their message.** In prepare mode `say_prepare` writes `error ...` to *stderr*. `Runner.Start` forwards stderr through `Line` (so it ends up on stdout in `--cli`), but only `@@error ` lines set `error`. So on a failed build `Cli()` prints the generic "The build tools stopped without saying why" on stderr and exits 1, while the real reason went to stdout. A launcher that shows stderr will show the useless message. Either have `say_prepare` also print something `Runner` recognises, or have `Cli` parse `error ` lines too. **2. `--check` is silent with `--prepare`.** `say("check", ...)` falls through `say_prepare`, which only handles stage/progress/error/done, so `--prepare --check` prints nothing. Probably fine since the manifest uses `nup_build_id` instead, but then `--prepare` shouldn't claim to work with `--check`, or `check` should be printed. **3. `--game` is not validated.** A missing `.nup` hits `os.stat(a.nup)` and ends in a Python traceback (after `~/Games/Hookshot` has been created), and a folder without `romfs`/`exefs` fails later inside the copy. Since the path comes from an external launcher, a one-line `fail("... not found")` up front (and for the folder: both `romfs` and `exefs` must exist) would give RecompLauncher a clean `error` line. **4. Naming/overlap with #1026.** #1026 (shaders prepared at the end of the build) adds `--no-prepare-shaders`, the game's `--prepare-shaders`, and `prepare_shaders()` in the same `make_game.py` tail, so "prepare" will mean two different things (build everything for the launcher vs. the shader step). The argparse flags don't collide (`--prepare` is an exact match), but I'd consider something like `--launcher` / `--progress-lines`. More concretely, `show_prepared` in #1026 passes `@@stage`/`@@progress` lines and the "shaders were not prepared" text straight through `print`, bypassing `say()`, so in `--prepare` mode those would reach the launcher as raw `@@` lines instead of `progress <n> <text>`. Whichever lands second needs to route that through `say()`. The two also edit the same hunk near `say("done", out)` and `parse_args`, so expect a rebase. **5. #1179 (profiles/save import).** It touches the builder/launcher (`MainWindow*`, `App.xaml`, `hookshot-launch`, `run_all.py`). I didn't dig into its save layout, but `recomplauncher.json` hardcodes the saves at `{local}/hookshot` and `{home}/Games/Hookshot/user`; if profiles move where saves live, the manifest needs updating. Worth cross-checking whichever merges second. Nits: `--play`'s parser in `setup.c` only handles one optionally-quoted path (fine for how a launcher passes it), and the `{home}/Games/Hookshot` default is hardcoded in the manifest, so a player who built somewhere else gets "No built game to start", which is at least a clear message. The Windows zip step and glob look right to me (`dist/` is the artifact root, and the tag-only release job is as described). CI: GitHub reports no checks on this branch, so I couldn't confirm a green run. I didn't build or run anything.
Xarishark commented 2026-11-10 05:32:59 +00:00 (Migrated from github.com)

Thanks, good catches. Fixed in 2d27d08 and a3859f1.

  1. Fixed, and it was a bit worse than that: RecompLauncher reads only stdout, so the reason was lost on Linux too. In --prepare, make_game now prints error <text> on stdout, and --cli takes that as the reason, saying it once on stdout and again on stderr (also for "build tools not found").
  2. --prepare --check now prints its check ok|bad … line.
  3. --game is checked in parse_args before any folder is made: a path that doesn't exist, or a folder without both romfs and exefs, is one error line. Tests added.
  4. Agreed that whichever of this and #1026 lands second routes show_prepared through say(); happy to rebase. I'd keep --prepare as the name: it matches the prepare step in recomplauncher.json, and the flags don't collide.
  5. Noted; I'll check the manifest's save paths against #1179 if it lands first.

The Python tests pass on Linux; the App.xaml.cs change still needs a Windows build.

Thanks, good catches. Fixed in 2d27d08 and a3859f1. 1. Fixed, and it was a bit worse than that: RecompLauncher reads only stdout, so the reason was lost on Linux too. In `--prepare`, `make_game` now prints `error <text>` on stdout, and `--cli` takes that as the reason, saying it once on stdout and again on stderr (also for "build tools not found"). 2. `--prepare --check` now prints its `check ok|bad …` line. 3. `--game` is checked in `parse_args` before any folder is made: a path that doesn't exist, or a folder without both `romfs` and `exefs`, is one `error` line. Tests added. 4. Agreed that whichever of this and #1026 lands second routes `show_prepared` through `say()`; happy to rebase. I'd keep `--prepare` as the name: it matches the `prepare` step in `recomplauncher.json`, and the flags don't collide. 5. Noted; I'll check the manifest's save paths against #1179 if it lands first. The Python tests pass on Linux; the `App.xaml.cs` change still needs a Windows build.
Xarishark commented 2026-11-10 05:41:48 +00:00 (Migrated from github.com)

Follow-up on the Windows side: HookshotBuilder at a3859f1 builds with .NET 8 (0 warnings, 0 errors), and --cli --prepare from that build exits 1 with a single error … line on stdout and again on stderr for:

  • a .nup that doesn't exist (error Your copy of the game was not found: …, and no output folder is made)
  • a folder without romfs (error The folder … needs both a romfs and an exefs folder in it.)
  • the builder run away from its tools (error The build tools were not found next to this program.)

Not run: a full successful build, since there was no game copy on that machine.

Follow-up on the Windows side: HookshotBuilder at a3859f1 builds with .NET 8 (0 warnings, 0 errors), and `--cli --prepare` from that build exits 1 with a single `error …` line on stdout and again on stderr for: - a `.nup` that doesn't exist (`error Your copy of the game was not found: …`, and no output folder is made) - a folder without `romfs` (`error The folder … needs both a romfs and an exefs folder in it.`) - the builder run away from its tools (`error The build tools were not found next to this program.`) Not run: a full successful build, since there was no game copy on that machine.
hckrman101 (Migrated from github.com) approved these changes 2026-11-10 16:19:23 +00:00
hckrman101 (Migrated from github.com) left a comment

Thanks, all the points are addressed at a3859f1.

  1. Fixed: in --prepare, error <text> now goes to stdout, and Cli() reuses that line as the reason (once on stdout, once on stderr), including the "tools not found" case. The failure == "cancelled" handling reads fine.
  2. Fixed: check lines are printed in prepare mode, and covered by test_error_and_check_lines_are_on_stdout.
  3. Fixed: --game is validated in parse_args before any folder is made (missing path, or a folder without both romfs and exefs), and --prepare is applied first so the failure comes out as a plain error line. The tests cover both and the non-prepare @@error case.
  4. Fine with keeping --prepare as the name. Whichever of this and #1026 lands second needs to route show_prepared through say(), as agreed.
  5. Fine, just cross-check the save paths in recomplauncher.json against #1179 if it lands first.

No new issues in the delta. The App.xaml.cs change I couldn't build, but your note about the .NET 8 build and the --cli --prepare failure runs covers it. GitHub shows no CI checks on this branch, and the PR is currently mergeable. I didn't build or run anything.

Thanks, all the points are addressed at a3859f1. 1. Fixed: in `--prepare`, `error <text>` now goes to stdout, and `Cli()` reuses that line as the reason (once on stdout, once on stderr), including the "tools not found" case. The `failure == "cancelled"` handling reads fine. 2. Fixed: `check` lines are printed in prepare mode, and covered by `test_error_and_check_lines_are_on_stdout`. 3. Fixed: `--game` is validated in `parse_args` before any folder is made (missing path, or a folder without both `romfs` and `exefs`), and `--prepare` is applied first so the failure comes out as a plain `error` line. The tests cover both and the non-prepare `@@error` case. 4. Fine with keeping `--prepare` as the name. Whichever of this and #1026 lands second needs to route `show_prepared` through `say()`, as agreed. 5. Fine, just cross-check the save paths in `recomplauncher.json` against #1179 if it lands first. No new issues in the delta. The `App.xaml.cs` change I couldn't build, but your note about the .NET 8 build and the `--cli --prepare` failure runs covers it. GitHub shows no CI checks on this branch, and the PR is currently mergeable. I didn't build or run anything.
Commenting is not possible because the repository is archived.
No description provided.