feat(builder): the launcher, one window to play, build and update (Windows and Linux) #2373

Open
madebyjordan wants to merge 17 commits from madebyjordan/builder/launcher into main
madebyjordan commented 2026-11-11 16:25:36 +00:00 (Migrated from github.com)

Players have to juggle three things today: the builder window, the game folder's launcher, and the update dialogs. Nothing tells them which version they have or what an update will do (it rebuilds the game, ~30 minutes). This replaces all of that with one launcher window, on Windows and Linux, drawn from the "Hookshot v2" design.

Status: planned for 3.0.0, per review. Up to date with main as of 55dd6414 (#2367, #2447, #2449, #2141, #2160 merged in). Waiting for hands-on testing. The open scope questions for @NotRexed are at the end.

What changed

hookshot.exe in the game folder (and the AppImage) opens the launcher. It shows the wordmark, then Play (Build while there is no game yet, which opens the build). Update stands beside Play while a newer release is out. Under Play are the version, then Credits / GitHub / Discord. Menu opens a side panel with three tabs. The window is the page colour behind the wordmark. No picture of the game ships, and original art will come in its own PR.

  • Build: the game file and the install folder, then Build Game.
    • While it builds, the button becomes the build's bar, with its step and the time left ("Compile 31 of 58 · about 18 minutes left").
    • If the build stops, a red Build stopped button says why and retries.
    • Language, Workers and Memory sit under it.
  • Releases: every published release, newest first.
    • The one in the folder is ticked. The five newest stay in sight, and older ones fold into one row.
    • Pressing another release asks first. It then installs that release's builder beside the one in use (setup.c --engine, no window) and rebuilds the game with that release's own make_game.py.
    • With pre-releases on, release candidates appear among the releases in semver order (rc.10 after rc.9).
  • Settings:
    • Prepare Shaders.
    • DLSS (asks for NVIDIA's license) and FSR 4 (asks for AMD's).
    • Lower memory use: #2206's RX_VK_PCACHE_LOAD=0, one line of the game folder's hookshot_env.txt, so it holds however the game is started. The cache file is kept, and a note gives its size.
    • Pipeline list → Save: #2447's "Save my pipeline list", moved here from the old launch page.
    • Enable pre-releases: replaces the Release / Release candidate track menu. A saved Nightly stays Nightly.
  • Foot of the panel:
    • Check for updates, or Update Available when a newer release is out.
    • During an update, its bar and Cancel. If it fails, Update stopped with the reason, Show details, Copy error log and Dismiss.
    • Before anything is pressed, it says that installing a version rebuilds the game from the player's files, and about how long that takes on their PC (from build_times.json).

Linux (builder/linux/hookshot_launcher.py): the same screen on one Tk canvas, painted with numpy from the XAML's own path data. The art test checks the paths are byte-for-byte the same. The old Tk pages remain, for a Python without numpy and for the builder's own update.

Smaller changes:

  • Shaders step: the last step, preparing the shaders, shows its own percentage and time left. It has the bar's last 8% but can take as long as the compile.
  • Updater.ReleaseList: every release with a builder to download. Tags that are not versions (nightly, ultrawide, low-vram) are skipped.
  • setup.c --engine --tag <tag>: installs a release beside the one in use and reports progress on stdout. It hard-links the parts the two versions share (copying where links can't be made), in a portable home too.
  • Clean-up after a build: it also keeps the builder of the release the game folder holds, so switching back and forth downloads nothing.
  • docs/versioning.md: the update-track section describes the switch.

For reviewers

Where to start:

  • builder/HookshotBuilder/Launcher.cs: the launcher's state and its update flow (Bring → StartWork → PullEngine → Runner).
  • LaunchList.cs and LaunchWork.cs: the pure rules (which versions show, what the bar and the words say). The Linux side mirrors them in launch_list.py / launch_work.py, and both have tests.
  • MainWindow.xaml: the screen itself.
  • setup.c: the --engine mode (g_engine).

Already settled in review (no need to raise again):

  • Game art: the background picture is gone (launcher.jpg, the Linux picture.png, make_picture.py). The only new asset is the Marcellus font, with its OFL licence.
  • Engine mode security: https parts with SHA-256 checked before unpacking; list names through list_name_ok; --tag limited to [0-9A-Za-z._-]; never over the version in current.txt; staged in <tag>.part. The 127.0.0.1 exception applies only to a list that itself came from localhost (the rig).
  • Portable homes (#2367): the engine setup and make_game.py get the home through BuilderHome.PassTo. Storage.Base is BuilderHome.Base, so the clean-up after a build stays in the portable home.
  • #2449's runtime check: the engine mode goes through the same work() path, so builder_ready runs before @@installed.
  • Hard-linked parts: nothing writes a part's file in place. Compilers, Python, headers and libraries are only read. Python's bytecode caches are written to a new file and renamed over. emsdk is used for its clang only. This rule is written beside link_tree.
  • CI: the launcher's checks run on the first Windows shard only, like builder_workers.
  • Back after a stopped or cancelled update (#2129/#2141): one function, BackFromBuild, returns to the launcher, which offers the update again.

Checks to run:

  • dotnet run -c Release in tests/builder_launcher: list, work and settings rules.
  • python -I -B tests/run_all.py builder launcher portable setup update.
  • On Linux: tests/test_linux_launcher_{lists,work,art}.py and tests/test_linux_update*.py.
  • The full flow on Windows: tests/builder_launcher/rig/run.ps1. It uses fake releases from a local server, a scratch install (HOOKSHOT_SETUP_BASE) and a stand-in make_game.py. The real install is untouched.
  • Screens without a display: HOOKSHOT_UI_SHOT=<file.png> draws the window to a PNG and exits. HOOKSHOT_UI_PREVIEW picks the state: tab=build|releases|settings, older, pick=…,go, or found,build / found,stopped / update,menu for the build pages. HOOKSHOT_LATEST_TAG=v2.2.0,v2.1.0 fakes GitHub's answer. The Linux launcher takes HOOKSHOT_UI_PREVIEW=menu,tab=…,building|stopped|updating|update-stopped.

How it was tested

  • tests/builder_launcher (new, in CI on Windows): the version lists, release-candidate order, and the progress and rebuild wording.
  • tests/builder_launcher/rig/run.ps1, run locally against the real Windows build. It covers:
    • install beside the builder in use, and switch back;
    • cancel, stop and retry;
    • a builder that cannot be downloaded;
    • a build from the Build tab, and --update;
    • that the real install is untouched.
  • tests/test_linux_launcher_{lists,work,art}.py (new): the Linux list rules, the progress and rebuild wording, the launcher's state handling (cancel during a download, a second exit, a stuck pipe), and the drawing.
  • tests/test_builder_details_hitbox.py hit-tests the Show details / Cancel row: the words take clicks, and the blank beside them does not.
  • Updated tests from main:
    • test_portable_setup_transaction.py also runs the engine mode's part reuse: linked, in a standard and a portable home, with the version in use left whole.
    • test_builder_update_back.py checks the launcher's way back.
    • test_linux_update_cancel.py compiles the Linux builder's message_in beside pump.
  • After the merge with main:
    • Windows tests/run_all.py: 387 passed. Five failed (test_lift_cache, test_nup_paths, test_vram_budget_row, test_fpx_sticky, test_bake_portability), and the same five fail on a clean checkout of main on the same machine.
    • On Ubuntu 22.04: the Linux checks above, test_linux_update*, test_release_scripts and the launcher's screens, in each state.
  • On Windows, a real update (v1.0.0 to v2.1.0 from GitHub, real game files, about 30 minutes) went through this launcher before the hardening commits.
  • Not tried: a real game build, or a real downloaded release, through the new screens on Linux.

Open for @NotRexed

  • Older releases: the Releases tab can install any older release that has a builder to download, as well as newer ones. Keep that, or newer only?
  • Nightly: it is no longer a choice in the launcher. The nightly's own setup or AppImage installs it, and it stays Nightly until Enable pre-releases is turned off. If you want it selectable, I'll add it as a third choice.

Screenshots

960 × 640. The Windows ones are drawn off-screen from this branch, the Linux ones taken in Ubuntu 22.04. The paths shown are placeholders.

Windows

Launcher
Launcher
Update Available
Update Available
Building
Building
Build stopped
Build stopped
Updating
Updating
Settings
Settings
Linux
Update Available
Update Available
Updating
Updating
Building
Building
Update stopped
Update stopped
Settings
Settings
Build
Build

Linux draws the same screens, with Inter in place of Segoe UI.

🤖 Generated with Claude Code

Players have to juggle three things today: the builder window, the game folder's launcher, and the update dialogs. Nothing tells them which version they have or what an update will do (it rebuilds the game, ~30 minutes). This replaces all of that with one launcher window, on Windows and Linux, drawn from the "Hookshot v2" design. > **Status:** planned for 3.0.0, per review. Up to date with `main` as of 55dd6414 (#2367, #2447, #2449, #2141, #2160 merged in). Waiting for hands-on testing. The open scope questions for @NotRexed are at the end. ## What changed `hookshot.exe` in the game folder (and the AppImage) opens the launcher. It shows the wordmark, then **Play** (**Build** while there is no game yet, which opens the build). **Update** stands beside Play while a newer release is out. Under Play are the version, then Credits / GitHub / Discord. **Menu** opens a side panel with three tabs. The window is the page colour behind the wordmark. No picture of the game ships, and original art will come in its own PR. - **Build**: the game file and the install folder, then **Build Game**. - While it builds, the button becomes the build's bar, with its step and the time left ("Compile 31 of 58 · about 18 minutes left"). - If the build stops, a red **Build stopped** button says why and retries. - Language, Workers and Memory sit under it. - **Releases**: every published release, newest first. - The one in the folder is ticked. The five newest stay in sight, and older ones fold into one row. - Pressing another release asks first. It then installs that release's builder beside the one in use (`setup.c --engine`, no window) and rebuilds the game with that release's own `make_game.py`. - With pre-releases on, release candidates appear among the releases in semver order (`rc.10` after `rc.9`). - **Settings**: - Prepare Shaders. - DLSS (asks for NVIDIA's license) and FSR 4 (asks for AMD's). - **Lower memory use**: #2206's `RX_VK_PCACHE_LOAD=0`, one line of the game folder's `hookshot_env.txt`, so it holds however the game is started. The cache file is kept, and a note gives its size. - **Pipeline list → Save**: #2447's "Save my pipeline list", moved here from the old launch page. - **Enable pre-releases**: replaces the Release / Release candidate track menu. A saved Nightly stays Nightly. - **Foot of the panel**: - Check for updates, or **Update Available** when a newer release is out. - During an update, its bar and **Cancel**. If it fails, **Update stopped** with the reason, Show details, Copy error log and Dismiss. - Before anything is pressed, it says that installing a version rebuilds the game from the player's files, and about how long that takes on their PC (from `build_times.json`). **Linux** (`builder/linux/hookshot_launcher.py`): the same screen on one Tk canvas, painted with numpy from the XAML's own path data. The art test checks the paths are byte-for-byte the same. The old Tk pages remain, for a Python without numpy and for the builder's own update. **Smaller changes:** - **Shaders step:** the last step, preparing the shaders, shows its own percentage and time left. It has the bar's last 8% but can take as long as the compile. - **`Updater.ReleaseList`:** every release with a builder to download. Tags that are not versions (`nightly`, `ultrawide`, `low-vram`) are skipped. - **`setup.c --engine --tag <tag>`:** installs a release beside the one in use and reports progress on stdout. It hard-links the parts the two versions share (copying where links can't be made), in a portable home too. - **Clean-up after a build:** it also keeps the builder of the release the game folder holds, so switching back and forth downloads nothing. - **`docs/versioning.md`:** the update-track section describes the switch. ## For reviewers **Where to start:** - `builder/HookshotBuilder/Launcher.cs`: the launcher's state and its update flow (`Bring` → `StartWork` → `PullEngine` → `Runner`). - `LaunchList.cs` and `LaunchWork.cs`: the pure rules (which versions show, what the bar and the words say). The Linux side mirrors them in `launch_list.py` / `launch_work.py`, and both have tests. - `MainWindow.xaml`: the screen itself. - `setup.c`: the `--engine` mode (`g_engine`). **Already settled in review** (no need to raise again): - **Game art:** the background picture is gone (`launcher.jpg`, the Linux `picture.png`, `make_picture.py`). The only new asset is the Marcellus font, with its OFL licence. - **Engine mode security:** https parts with SHA-256 checked before unpacking; list names through `list_name_ok`; `--tag` limited to `[0-9A-Za-z._-]`; never over the version in `current.txt`; staged in `<tag>.part`. The `127.0.0.1` exception applies only to a list that itself came from localhost (the rig). - **Portable homes (#2367):** the engine setup and make_game.py get the home through `BuilderHome.PassTo`. `Storage.Base` is `BuilderHome.Base`, so the clean-up after a build stays in the portable home. - **#2449's runtime check:** the engine mode goes through the same `work()` path, so `builder_ready` runs before `@@installed`. - **Hard-linked parts:** nothing writes a part's file in place. Compilers, Python, headers and libraries are only read. Python's bytecode caches are written to a new file and renamed over. emsdk is used for its clang only. This rule is written beside `link_tree`. - **CI:** the launcher's checks run on the first Windows shard only, like `builder_workers`. - **Back after a stopped or cancelled update (#2129/#2141):** one function, `BackFromBuild`, returns to the launcher, which offers the update again. **Checks to run:** - `dotnet run -c Release` in `tests/builder_launcher`: list, work and settings rules. - `python -I -B tests/run_all.py builder launcher portable setup update`. - On Linux: `tests/test_linux_launcher_{lists,work,art}.py` and `tests/test_linux_update*.py`. - The full flow on Windows: `tests/builder_launcher/rig/run.ps1`. It uses fake releases from a local server, a scratch install (`HOOKSHOT_SETUP_BASE`) and a stand-in `make_game.py`. The real install is untouched. - **Screens without a display:** `HOOKSHOT_UI_SHOT=<file.png>` draws the window to a PNG and exits. `HOOKSHOT_UI_PREVIEW` picks the state: `tab=build|releases|settings`, `older`, `pick=…,go`, or `found,build` / `found,stopped` / `update,menu` for the build pages. `HOOKSHOT_LATEST_TAG=v2.2.0,v2.1.0` fakes GitHub's answer. The Linux launcher takes `HOOKSHOT_UI_PREVIEW=menu,tab=…,building|stopped|updating|update-stopped`. ## How it was tested - `tests/builder_launcher` (new, in CI on Windows): the version lists, release-candidate order, and the progress and rebuild wording. - `tests/builder_launcher/rig/run.ps1`, run locally against the real Windows build. It covers: - install beside the builder in use, and switch back; - cancel, stop and retry; - a builder that cannot be downloaded; - a build from the Build tab, and `--update`; - that the real install is untouched. - `tests/test_linux_launcher_{lists,work,art}.py` (new): the Linux list rules, the progress and rebuild wording, the launcher's state handling (cancel during a download, a second exit, a stuck pipe), and the drawing. - `tests/test_builder_details_hitbox.py` hit-tests the Show details / Cancel row: the words take clicks, and the blank beside them does not. - **Updated tests from `main`:** - `test_portable_setup_transaction.py` also runs the engine mode's part reuse: linked, in a standard and a portable home, with the version in use left whole. - `test_builder_update_back.py` checks the launcher's way back. - `test_linux_update_cancel.py` compiles the Linux builder's `message_in` beside `pump`. - **After the merge with `main`:** - Windows `tests/run_all.py`: 387 passed. Five failed (`test_lift_cache`, `test_nup_paths`, `test_vram_budget_row`, `test_fpx_sticky`, `test_bake_portability`), and the same five fail on a clean checkout of `main` on the same machine. - On Ubuntu 22.04: the Linux checks above, `test_linux_update*`, `test_release_scripts` and the launcher's screens, in each state. - On Windows, a real update (v1.0.0 to v2.1.0 from GitHub, real game files, about 30 minutes) went through this launcher before the hardening commits. - **Not tried:** a real game build, or a real downloaded release, through the new screens on Linux. ## Open for @NotRexed - **Older releases:** the Releases tab can install any older release that has a builder to download, as well as newer ones. Keep that, or newer only? - **Nightly:** it is no longer a choice in the launcher. The nightly's own setup or AppImage installs it, and it stays Nightly until Enable pre-releases is turned off. If you want it selectable, I'll add it as a third choice. ## Screenshots 960 × 640. The Windows ones are drawn off-screen from this branch, the Linux ones taken in Ubuntu 22.04. The paths shown are placeholders. ### Windows | | | |---|---| | **Launcher**<br><img src="https://gist.githubusercontent.com/madebyjordan/a39d9d51e5fa6dbc08d430dfd4425cc6/raw/shot3-windows-1-launcher.png" alt="Launcher" width="440"> | **Update Available**<br><img src="https://gist.githubusercontent.com/madebyjordan/a39d9d51e5fa6dbc08d430dfd4425cc6/raw/shot3-windows-2-update-available.png" alt="Update Available" width="440"> | | **Building**<br><img src="https://gist.githubusercontent.com/madebyjordan/a39d9d51e5fa6dbc08d430dfd4425cc6/raw/shot3-windows-3-building.png" alt="Building" width="440"> | **Build stopped**<br><img src="https://gist.githubusercontent.com/madebyjordan/a39d9d51e5fa6dbc08d430dfd4425cc6/raw/shot3-windows-4-build-stopped.png" alt="Build stopped" width="440"> | | **Updating**<br><img src="https://gist.githubusercontent.com/madebyjordan/a39d9d51e5fa6dbc08d430dfd4425cc6/raw/shot3-windows-5-updating.png" alt="Updating" width="440"> | **Settings**<br><img src="https://gist.githubusercontent.com/madebyjordan/a39d9d51e5fa6dbc08d430dfd4425cc6/raw/shot3-windows-6-settings.png" alt="Settings" width="440"> | <details><summary>Linux</summary> | | | |---|---| | **Update Available**<br><img src="https://gist.githubusercontent.com/madebyjordan/a39d9d51e5fa6dbc08d430dfd4425cc6/raw/shot3-linux-1-update-available.png" alt="Update Available" width="440"> | **Updating**<br><img src="https://gist.githubusercontent.com/madebyjordan/a39d9d51e5fa6dbc08d430dfd4425cc6/raw/shot3-linux-2-updating.png" alt="Updating" width="440"> | | **Building**<br><img src="https://gist.githubusercontent.com/madebyjordan/a39d9d51e5fa6dbc08d430dfd4425cc6/raw/shot3-linux-3-building.png" alt="Building" width="440"> | **Update stopped**<br><img src="https://gist.githubusercontent.com/madebyjordan/a39d9d51e5fa6dbc08d430dfd4425cc6/raw/shot3-linux-4-update-stopped.png" alt="Update stopped" width="440"> | | **Settings**<br><img src="https://gist.githubusercontent.com/madebyjordan/a39d9d51e5fa6dbc08d430dfd4425cc6/raw/shot3-linux-5-settings.png" alt="Settings" width="440"> | **Build**<br><img src="https://gist.githubusercontent.com/madebyjordan/a39d9d51e5fa6dbc08d430dfd4425cc6/raw/shot3-linux-6-build.png" alt="Build" width="440"> | Linux draws the same screens, with Inter in place of Segoe UI. </details> 🤖 Generated with [Claude Code](https://claude.com/claude-code)
nullifyshot commented 2026-11-11 16:48:30 +00:00 (Migrated from github.com)

A suggestion for the new Settings tab: an option for #2206's RX_VK_PCACHE_LOAD=0.

Why. #2206 (merged) lets the game skip loading and saving mkw_vk_pipelines.bin. That file can reach 2.5 GB, and the driver holds it in memory for the whole session. Skipping it saves about 3 GB of memory and commit, which matters for players near the commit limit, like the RTX 3060 player in #2206 who crashed at 30.7 GB. Right now it's only reachable by setting an environment variable or editing hookshot_env.txt, which most players won't do.

It fits what's already here. StartGame in MainWindow.xaml.cs already starts the game with a variable of its own (si.Environment["HOOKSHOT_NO_UPDATE_CHECK"] = "1"). The Linux launcher's play() passes its own environment too. So it would be:

  • Settings: a toggle saved in BuilderSettings (for example bool SkipPipelineCacheFile), off by default as in #2206.
  • Play: si.Environment["RX_VK_PCACHE_LOAD"] = "0" when the toggle is on, and the same line in the Linux play()'s environment.
  • Wording: something like "Lower memory use: skip the pipeline cache file (about 3 GB less memory; can mean more stutter the first time, on a fresh driver cache)". That covers #2206's reason for keeping it opt-in, since on a cold or small driver cache (AMD, older NVIDIA defaults) pipelines may compile during play.

Two things to decide:

  • Starting the game without the launcher. Starting bin\hookshot.exe directly, or from a shortcut, wouldn't get a variable set at Play. If it should apply to every start, the launcher could instead add or remove an RX_VK_PCACHE_LOAD=0 line in the game folder's hookshot_env.txt, which the game reads at every start (it takes RX_ names). The Play variable is simpler and matches what StartGame already does.
  • Don't delete the file when it's turned on. Turning it off again should pick up the existing cache. It might be worth showing the file's size next to the toggle, so players can see what it saves.
A suggestion for the new Settings tab: an option for #2206's `RX_VK_PCACHE_LOAD=0`. **Why.** #2206 (merged) lets the game skip loading and saving `mkw_vk_pipelines.bin`. That file can reach 2.5 GB, and the driver holds it in memory for the whole session. Skipping it saves about 3 GB of memory and commit, which matters for players near the commit limit, like the RTX 3060 player in #2206 who crashed at 30.7 GB. Right now it's only reachable by setting an environment variable or editing `hookshot_env.txt`, which most players won't do. **It fits what's already here.** `StartGame` in `MainWindow.xaml.cs` already starts the game with a variable of its own (`si.Environment["HOOKSHOT_NO_UPDATE_CHECK"] = "1"`). The Linux launcher's `play()` passes its own environment too. So it would be: - **Settings:** a toggle saved in `BuilderSettings` (for example `bool SkipPipelineCacheFile`), off by default as in #2206. - **Play:** `si.Environment["RX_VK_PCACHE_LOAD"] = "0"` when the toggle is on, and the same line in the Linux `play()`'s environment. - **Wording:** something like "**Lower memory use:** skip the pipeline cache file (about 3 GB less memory; can mean more stutter the first time, on a fresh driver cache)". That covers #2206's reason for keeping it opt-in, since on a cold or small driver cache (AMD, older NVIDIA defaults) pipelines may compile during play. **Two things to decide:** - **Starting the game without the launcher.** Starting `bin\hookshot.exe` directly, or from a shortcut, wouldn't get a variable set at Play. If it should apply to every start, the launcher could instead add or remove an `RX_VK_PCACHE_LOAD=0` line in the game folder's `hookshot_env.txt`, which the game reads at every start (it takes `RX_` names). The Play variable is simpler and matches what `StartGame` already does. - **Don't delete the file when it's turned on.** Turning it off again should pick up the existing cache. It might be worth showing the file's size next to the toggle, so players can see what it saves.
Arkex-ai commented 2026-11-11 16:56:15 +00:00 (Migrated from github.com)

I'm investigating #2352, claimed here: the game-folder wrapper can select an older installed Builder over the recorded AppImage, and the legacy default update mode can offer older tools even offline. Both are reproduced with authored fixtures on main.

This overlaps linux_update.py here, although hookshot-folder is outside this PR's file list. My scope would protect the wrapper/default action while preserving your explicit release selection and downgrade confirmation. Are you already addressing those two paths, or is there a preferred integration point? I haven't edited them yet.

I'm investigating #2352, [claimed here](https://git.magicbean.dev/MagicBeanDev/Hookshot/issues/2352): the game-folder wrapper can select an older installed Builder over the recorded AppImage, and the legacy default update mode can offer older tools even offline. Both are reproduced with authored fixtures on main. This overlaps `linux_update.py` here, although `hookshot-folder` is outside this PR's file list. My scope would protect the wrapper/default action while preserving your explicit release selection and downgrade confirmation. Are you already addressing those two paths, or is there a preferred integration point? I haven't edited them yet.
madebyjordan commented 2026-11-11 17:03:45 +00:00 (Migrated from github.com)

@nullifyshot Thanks, added in 96eaeefd: Lower memory use in Settings, off by default. It writes RX_VK_PCACHE_LOAD=0 as one line of the game folder's hookshot_env.txt (so it holds however the game is started) and removes only that line when switched off; the cache file is never deleted, and the note under the switch shows its size. Windows (GameEnv.cs) and Linux (game_env.py), with tests of both.

@nullifyshot Thanks, added in 96eaeefd: **Lower memory use** in Settings, off by default. It writes `RX_VK_PCACHE_LOAD=0` as one line of the game folder's `hookshot_env.txt` (so it holds however the game is started) and removes only that line when switched off; the cache file is never deleted, and the note under the switch shows its size. Windows (`GameEnv.cs`) and Linux (`game_env.py`), with tests of both.
madebyjordan commented 2026-11-11 17:03:46 +00:00 (Migrated from github.com)

@Arkex-ai Thanks for checking. This PR doesn't touch hookshot-folder, and its only change to linux_update.py is a ValueError guard in game_version, so both of your paths are yours. The PR's new launcher (hookshot_launcher.py) shows Update Available only for a newer release and always asks before an older one, in semver order (launch_list.newer, which also orders rc.N). Its --update <folder> start with no --tag (the game's own update offer) now asks before an older target too: a14058e1. For choose_mode, fixing it in linux_update.py as you suggest, compared by release_key, sounds right, and I'll rebase on whichever lands first.

@Arkex-ai Thanks for checking. This PR doesn't touch `hookshot-folder`, and its only change to `linux_update.py` is a `ValueError` guard in `game_version`, so both of your paths are yours. The PR's new launcher (`hookshot_launcher.py`) shows Update Available only for a newer release and always asks before an older one, in semver order (`launch_list.newer`, which also orders `rc.N`). Its `--update <folder>` start with no `--tag` (the game's own update offer) now asks before an older target too: a14058e1. For `choose_mode`, fixing it in `linux_update.py` as you suggest, compared by `release_key`, sounds right, and I'll rebase on whichever lands first.
hckrman101 commented 2026-11-11 17:12:51 +00:00 (Migrated from github.com)

Going to do very deep testing on this, plus will have a few design feedback. Few to start:

  • Currently it only shows 'Play' (at least in screenshots). When nothing is built, it should maybe have 'Build' as the CTA?
    • When clicking Build, instead of a sidebar menu, let's make this a full-screen modal
    • The Build modal should be broken down into 3 steps: NUP/ROM, options (DLSS/FSR/memory/CPU/etc), and a build progress
    • Also this is minor but maybe the Play/Build CTA could be redesign a bit, personally I'm not a super big fan of this slanted styled
  • When there's an update available, it should have 2 buttons seen (make sure to include screenshots for this case). 'Play' launches last version, 'Update' basically is like build
  • Also, when the user clicks 'Build' or 'Update', we should start downloading the builder package while they select their ROM. In the future, I see a world where the options list actually comes from the builder (so it's dynamic), so it's best to download the builder early
Going to do very deep testing on this, plus will have a few design feedback. Few to start: - Currently it only shows 'Play' (at least in screenshots). When nothing is built, it should maybe have 'Build' as the CTA? - When clicking Build, instead of a sidebar menu, let's make this a full-screen modal - The Build modal should be broken down into 3 steps: NUP/ROM, options (DLSS/FSR/memory/CPU/etc), and a build progress - Also this is minor but maybe the Play/Build CTA could be redesign a bit, personally I'm not a super big fan of this slanted styled - When there's an update available, it should have 2 buttons seen (make sure to include screenshots for this case). 'Play' launches last version, 'Update' basically is like build - Also, when the user clicks 'Build' or 'Update', we should start downloading the builder package while they select their ROM. In the future, I see a world where the options list actually comes from the builder (so it's dynamic), so it's best to download the builder early
madebyjordan commented 2026-11-11 17:17:58 +00:00 (Migrated from github.com)

Thanks @hckrman101, deep testing is very welcome.

On the design side (a full-screen build window, the three steps, the style of the Play/Build button): I'll take those as suggestions. The design here isn't final. I have bigger plans for it that I kept out of this release so the launcher could be ready sooner, and I'll weigh your ideas in that.

The other points are fair, and I'm addressing them in this PR:

  • Build when there's no game: the main button will say Build and open the build, instead of a dimmed Play.
  • Play + Update when an update is out: two buttons side by side. Play starts the version in the folder, Update brings the new one (it asks first, as it rebuilds the game). I'll post screenshots of that case here.
  • The builder downloaded early: an update already starts downloading that release's builder the moment it's confirmed (the setup's --engine mode, hidden), before the rebuild, and a first build uses the builder already installed, so nothing waits on a download after the game file is picked. When the options come from the builder later, they can be read from that download, so the order already fits.
Thanks @hckrman101, deep testing is very welcome. On the design side (a full-screen build window, the three steps, the style of the Play/Build button): I'll take those as suggestions. The design here isn't final. I have bigger plans for it that I kept out of this release so the launcher could be ready sooner, and I'll weigh your ideas in that. The other points are fair, and I'm addressing them in this PR: - **Build when there's no game:** the main button will say **Build** and open the build, instead of a dimmed Play. - **Play + Update when an update is out:** two buttons side by side. **Play** starts the version in the folder, **Update** brings the new one (it asks first, as it rebuilds the game). I'll post screenshots of that case here. - **The builder downloaded early:** an update already starts downloading that release's builder the moment it's confirmed (the setup's `--engine` mode, hidden), before the rebuild, and a first build uses the builder already installed, so nothing waits on a download after the game file is picked. When the options come from the builder later, they can be read from that download, so the order already fits.
madebyjordan commented 2026-11-11 17:24:08 +00:00 (Migrated from github.com)

@hckrman101 The two cases are in (4ee39e21), on Windows and Linux:

  • No game yet: the main button says Build and opens the build (the menu's Build tab), instead of a faint Play.
  • A newer release is out: Update stands beside Play. Play plays the version in the folder; Update brings the new one, asked first (it rebuilds the game, and says about how long that takes on the PC). It sits at Play's right so the menu's panel never covers it.
Windows Linux
Update out Windows: Play and Update Linux: Play and Update
No game yet Windows: Build Linux: Build
@hckrman101 The two cases are in (4ee39e21), on Windows and Linux: - **No game yet:** the main button says **Build** and opens the build (the menu's Build tab), instead of a faint Play. - **A newer release is out:** **Update** stands beside Play. Play plays the version in the folder; Update brings the new one, asked first (it rebuilds the game, and says about how long that takes on the PC). It sits at Play's right so the menu's panel never covers it. | | Windows | Linux | |---|---|---| | **Update out** | <img src="https://gist.githubusercontent.com/madebyjordan/a39d9d51e5fa6dbc08d430dfd4425cc6/raw/cta-windows-update.png" alt="Windows: Play and Update" width="440"> | <img src="https://gist.githubusercontent.com/madebyjordan/a39d9d51e5fa6dbc08d430dfd4425cc6/raw/cta-linux-update.png" alt="Linux: Play and Update" width="440"> | | **No game yet** | <img src="https://gist.githubusercontent.com/madebyjordan/a39d9d51e5fa6dbc08d430dfd4425cc6/raw/cta-windows-build.png" alt="Windows: Build" width="440"> | <img src="https://gist.githubusercontent.com/madebyjordan/a39d9d51e5fa6dbc08d430dfd4425cc6/raw/cta-linux-build.png" alt="Linux: Build" width="440"> |
hckrman101 (Migrated from github.com) reviewed 2026-11-11 17:41:37 +00:00
hckrman101 (Migrated from github.com) left a comment

Thanks for this, it's a big step up from three windows, and the two cases I asked for (Build with no game, Play + Update side by side) are in with screenshots on both platforms. I'm still doing the deeper hands-on testing I mentioned, so this is a first pass on the code, not a sign-off.

The one thing that has to be sorted before anything else: the background picture. builder/HookshotBuilder/Images/launcher.jpg is a picture of the game (the XAML calls it that: "the picture of the game, dim behind everything"), and builder/linux/launcher/picture.png is the same image dimmed by make_picture.py. CONTRIBUTING.md is fine with screenshots of the game in issues and PRs, but this goes further: it is committed to the repo and embedded in every HookshotBuilder.exe (<Resource>) and copied into every AppImage (cp -a builder/linux). That's redistributing the game's art with every release, and the PR doesn't say where the image comes from. That's a project and legal call for @NotRexed, not something to slip in with a launcher; until he's OK'd it, I'd drop both files and use something we own (a flat colour or gradient, or original art), keeping make_picture.py only if there's an image that can ship.

Updater / setup security looks right to me:

  • setup.c --engine keeps the existing checks: parts must be https with a 64-hex sha256 that's verified before unpacking, list names go through list_name_ok, the --tag is restricted to [0-9A-Za-z._-], and a list whose tag differs from the one asked for stops the install. Unpacking still goes into <tag>.part and is renamed when complete, and it refuses to install over the version in current.txt.
  • The http://127.0.0.1: exception in part_url_ok only applies when HOOKSHOT_SETUP_LIST itself points at localhost, so a list downloaded from GitHub can't use it. Fine for the rig.
  • link_tree hard-links the shared parts (or copies them), doesn't follow reparse points, and removes the half-made folder and downloads it again if it fails. Good.
  • On Linux the launcher uses the existing linux_update.install path (https only, redirects too, sha256 per part) and checks manifest.tag == tag before execve.
  • ReleaseList drops tags that aren't versions, and the tag goes into the API URL with Uri.EscapeDataString and to the setup through ArgumentList. No quoting problems that I could see.

Process launching: Windows StartGame is unchanged apart from the error box (working directory is the folder, HOOKSHOT_NO_UPDATE_CHECK=1). Linux Play still goes through hookshot_builder.play(), so the socketpair stderr from #2279 is kept and the game still writes hookshot_log.txt. The Lower memory switch only edits RX_VK_PCACHE_LOAD lines in hookshot_env.txt, through a .new file and a replace, and never touches the cache file. Thanks for doing it that way (@nullifyshot's suggestion).

Saves / install folders: I found nothing that moves or deletes saves or the game folder. Storage.Freeable now skips <tag>.part, which is needed now that an engine install can run next to a build. One thing to be aware of: after switching to an older release or a pre-release, FreeAfterBuild deletes that version's builder again (it isn't current.txt), so switching back and forth downloads it again each time. That's probably intended, just confirming.

Things for @NotRexed (product, not code):

  • The Track menu goes away. A saved Nightly stays Nightly, but nobody can choose Nightly any more from either launcher, while docs/versioning.md still lists it as a track. Either keep a way to opt in or say in the docs that Nightly can't be chosen in the UI any more.
  • The scope (a new main window on both platforms, the Releases tab that can install any older release) is his decision.

Overlaps: this conflicts textually with #2367 (portable installs: Storage.Base, setup.c, Updater.cs, Runner.cs, App.xaml.cs, the asset-digest test), #2201 (MainWindow.xaml(.cs), Updater.cs), #2141 (MainWindow.xaml.cs) and #2150/#2159/#2160 (hookshot_builder.py, linux_update.py). The one that's more than a rebase is #2367: whichever lands second has to make sure the launcher's hidden --engine setup and FreeAfterBuild use the portable home rather than %LOCALAPPDATA%. #2352's two paths are Arkex-ai's, as agreed above.

CI: the new tests run (Windows builder_launcher: 37 + 33 + 13 checks passed; test_linux_launcher_* are picked up by run_all, and _art declares numpy). The two red jobs aren't from this PR: Windows part 2/2 fails test_native_astc_texture_count.py (a scanf deprecation in its fixture under clang -Werror), and appimage timed out installing system packages. Re-run the AppImage job once the runner's network is back, since appimage.sh changed here.

Small things inline.

Thanks for this, it's a big step up from three windows, and the two cases I asked for (Build with no game, Play + Update side by side) are in with screenshots on both platforms. I'm still doing the deeper hands-on testing I mentioned, so this is a first pass on the code, not a sign-off. **The one thing that has to be sorted before anything else: the background picture.** `builder/HookshotBuilder/Images/launcher.jpg` is a picture of the game (the XAML calls it that: "the picture of the game, dim behind everything"), and `builder/linux/launcher/picture.png` is the same image dimmed by `make_picture.py`. CONTRIBUTING.md is fine with screenshots of the game in issues and PRs, but this goes further: it is committed to the repo and embedded in every `HookshotBuilder.exe` (`<Resource>`) and copied into every AppImage (`cp -a builder/linux`). That's redistributing the game's art with every release, and the PR doesn't say where the image comes from. That's a project and legal call for @NotRexed, not something to slip in with a launcher; until he's OK'd it, I'd drop both files and use something we own (a flat colour or gradient, or original art), keeping `make_picture.py` only if there's an image that can ship. **Updater / setup security** looks right to me: - `setup.c --engine` keeps the existing checks: parts must be https with a 64-hex sha256 that's verified before unpacking, list names go through `list_name_ok`, the `--tag` is restricted to `[0-9A-Za-z._-]`, and a list whose tag differs from the one asked for stops the install. Unpacking still goes into `<tag>.part` and is renamed when complete, and it refuses to install over the version in `current.txt`. - The `http://127.0.0.1:` exception in `part_url_ok` only applies when `HOOKSHOT_SETUP_LIST` itself points at localhost, so a list downloaded from GitHub can't use it. Fine for the rig. - `link_tree` hard-links the shared parts (or copies them), doesn't follow reparse points, and removes the half-made folder and downloads it again if it fails. Good. - On Linux the launcher uses the existing `linux_update.install` path (https only, redirects too, sha256 per part) and checks `manifest.tag == tag` before `execve`. - `ReleaseList` drops tags that aren't versions, and the tag goes into the API URL with `Uri.EscapeDataString` and to the setup through `ArgumentList`. No quoting problems that I could see. **Process launching**: Windows `StartGame` is unchanged apart from the error box (working directory is the folder, `HOOKSHOT_NO_UPDATE_CHECK=1`). Linux Play still goes through `hookshot_builder.play()`, so the socketpair stderr from #2279 is kept and the game still writes `hookshot_log.txt`. The Lower memory switch only edits `RX_VK_PCACHE_LOAD` lines in `hookshot_env.txt`, through a `.new` file and a replace, and never touches the cache file. Thanks for doing it that way (@nullifyshot's suggestion). **Saves / install folders**: I found nothing that moves or deletes saves or the game folder. `Storage.Freeable` now skips `<tag>.part`, which is needed now that an engine install can run next to a build. One thing to be aware of: after switching to an older release or a pre-release, `FreeAfterBuild` deletes that version's builder again (it isn't `current.txt`), so switching back and forth downloads it again each time. That's probably intended, just confirming. **Things for @NotRexed (product, not code):** - The Track menu goes away. A saved Nightly stays Nightly, but nobody can choose Nightly any more from either launcher, while `docs/versioning.md` still lists it as a track. Either keep a way to opt in or say in the docs that Nightly can't be chosen in the UI any more. - The scope (a new main window on both platforms, the Releases tab that can install any older release) is his decision. **Overlaps:** this conflicts textually with #2367 (portable installs: `Storage.Base`, `setup.c`, `Updater.cs`, `Runner.cs`, `App.xaml.cs`, the asset-digest test), #2201 (`MainWindow.xaml(.cs)`, `Updater.cs`), #2141 (`MainWindow.xaml.cs`) and #2150/#2159/#2160 (`hookshot_builder.py`, `linux_update.py`). The one that's more than a rebase is #2367: whichever lands second has to make sure the launcher's hidden `--engine` setup and `FreeAfterBuild` use the portable home rather than `%LOCALAPPDATA%`. #2352's two paths are Arkex-ai's, as agreed above. **CI:** the new tests run (Windows `builder_launcher`: 37 + 33 + 13 checks passed; `test_linux_launcher_*` are picked up by run_all, and `_art` declares numpy). The two red jobs aren't from this PR: Windows part 2/2 fails `test_native_astc_texture_count.py` (a `scanf` deprecation in its fixture under clang `-Werror`), and `appimage` timed out installing system packages. Re-run the AppImage job once the runner's network is back, since `appimage.sh` changed here. Small things inline.
@ -153,6 +154,12 @@ jobs:
run: |
hckrman101 (Migrated from github.com) commented 2026-11-11 17:41:37 +00:00

Nit: unlike builder_workers just above, this has no matrix.shard == '1/2' condition, so it restores and builds on both Windows shards. Same tests, twice the cost.

Nit: unlike `builder_workers` just above, this has no `matrix.shard == '1/2'` condition, so it restores and builds on both Windows shards. Same tests, twice the cost.
@ -24,11 +24,11 @@
<DebugType>none</DebugType>
hckrman101 (Migrated from github.com) commented 2026-11-11 17:41:37 +00:00

This embeds the game picture in every HookshotBuilder.exe we publish. See the review body: it needs @NotRexed's OK, or an image we own, before it ships.

This embeds the game picture in every `HookshotBuilder.exe` we publish. See the review body: it needs @NotRexed's OK, or an image we own, before it ships.
hckrman101 (Migrated from github.com) commented 2026-11-11 17:41:37 +00:00

With the Track menu gone, this is the only switch, and it can't select Nightly (only keep a saved one). Turning it off and on again moves a Nightly player to Release candidate for good. Fine if that's the intent, but docs/versioning.md's table still presents Nightly as a track you choose.

With the Track menu gone, this is the only switch, and it can't select Nightly (only keep a saved one). Turning it off and on again moves a Nightly player to Release candidate for good. Fine if that's the intent, but `docs/versioning.md`'s table still presents Nightly as a track you choose.
hckrman101 (Migrated from github.com) commented 2026-11-11 17:41:37 +00:00

Heads-up: #2367 replaces this line with BuilderHome.Base, and its setup sets HOOKSHOT_SETUP_BASE for portable homes. Whichever lands second should check that the launcher's --engine setup (which inherits the environment) and FreeAfterBuild end up in the portable home.

Heads-up: #2367 replaces this line with `BuilderHome.Base`, and its setup sets `HOOKSHOT_SETUP_BASE` for portable homes. Whichever lands second should check that the launcher's `--engine` setup (which inherits the environment) and `FreeAfterBuild` end up in the portable home.
hckrman101 (Migrated from github.com) commented 2026-11-11 17:41:37 +00:00

picture.png is made from launcher.jpg, so it has the same problem, and appimage.sh copies builder/linux into every AppImage. If the picture is replaced, regenerate this (or drop it and draw the page colour) in the same change.

`picture.png` is made from `launcher.jpg`, so it has the same problem, and `appimage.sh` copies `builder/linux` into every AppImage. If the picture is replaced, regenerate this (or drop it and draw the page colour) in the same change.
madebyjordan commented 2026-11-11 19:16:49 +00:00 (Migrated from github.com)

Thanks @hckrman101, that's a thorough first pass. Fixed in d1a41557:

  • The background picture: you're right, it's the game's art, and it shouldn't ship with every release. launcher.jpg, the Linux picture.png and make_picture.py are gone; the window is the page's colour behind the wordmark for now. I'll replace it with original art I'm making from scratch (nothing traced or taken from the game), and I'll say where it comes from and its licence in the PR that adds it.
  • Switching builders: not intended. The clean-up after a build now also keeps the builder of the release the game folder holds, so switching back and forth doesn't download it again. Other old builders still go.
  • CI: the launcher check now runs on the first Windows shard only, like builder_workers.
  • Nightly: docs/versioning.md now says it isn't chosen in the launcher: the nightly's own setup or AppImage installs it, and it stays Nightly until Enable pre-releases is turned off. @NotRexed, if you'd rather keep Nightly selectable, I'll add it as a third choice.

#2367: agreed. If this lands second, I'll make sure the hidden --engine setup and the clean-up use the portable home.

CI reds: agreed, neither is from this PR. Could someone re-run the AppImage job once the runner's network is back, since appimage.sh changed here?

Looking forward to the hands-on testing.

Thanks @hckrman101, that's a thorough first pass. Fixed in d1a41557: - **The background picture:** you're right, it's the game's art, and it shouldn't ship with every release. `launcher.jpg`, the Linux `picture.png` and `make_picture.py` are gone; the window is the page's colour behind the wordmark for now. I'll replace it with original art I'm making from scratch (nothing traced or taken from the game), and I'll say where it comes from and its licence in the PR that adds it. - **Switching builders:** not intended. The clean-up after a build now also keeps the builder of the release the game folder holds, so switching back and forth doesn't download it again. Other old builders still go. - **CI:** the launcher check now runs on the first Windows shard only, like `builder_workers`. - **Nightly:** `docs/versioning.md` now says it isn't chosen in the launcher: the nightly's own setup or AppImage installs it, and it stays Nightly until Enable pre-releases is turned off. @NotRexed, if you'd rather keep Nightly selectable, I'll add it as a third choice. **#2367:** agreed. If this lands second, I'll make sure the hidden `--engine` setup and the clean-up use the portable home. **CI reds:** agreed, neither is from this PR. Could someone re-run the AppImage job once the runner's network is back, since `appimage.sh` changed here? Looking forward to the hands-on testing.
Arkex-ai commented 2026-11-11 20:02:38 +00:00 (Migrated from github.com)

#2449 is ready for review: one runtime-readiness check before installed reuse, staging publication and fast launch. Hosted CI passes, and an actual disposable installation recovered a missing coreclr.dll from the pinned official release and ran the managed CLI successfully.

For your --engine route, keep that check before reporting @@installed, while preserving your current-version/pruning policy. I checked the history: the parts-based installer already used directory-based self-contained publishing when it was introduced. The required-file contract will need an intentional update if the launcher moves to Avalonia or single-file publishing. Whichever lands second should retain both contracts.

#2449 is ready for review: one runtime-readiness check before installed reuse, staging publication and fast launch. Hosted CI passes, and an actual disposable installation recovered a missing `coreclr.dll` from the pinned official release and ran the managed CLI successfully. For your `--engine` route, keep that check before reporting `@@installed`, while preserving your current-version/pruning policy. I checked the history: the parts-based installer already used directory-based self-contained publishing when it was introduced. The required-file contract will need an intentional update if the launcher moves to Avalonia or single-file publishing. Whichever lands second should retain both contracts.
madebyjordan commented 2026-11-11 20:16:51 +00:00 (Migrated from github.com)

Thanks for the heads-up @Arkex-ai. The --engine route goes through the same work() path, so it gets builder_ready before @@installed with no extra change here, and I'll keep both contracts whichever of the two lands second.

One thing to know: I have a small follow-up planned that lets a later release rename the builder (the setup records the list's run path, and the game folder's launcher opens that). I'll base it on #2449 once it's in, and derive the .dll / .deps.json / .runtimeconfig.json names from the exe's own name rather than HookshotBuilder, so the check holds for a renamed builder too. Noted on Avalonia / single-file: that contract will change with the standalone launcher anyway.

Thanks for the heads-up @Arkex-ai. The `--engine` route goes through the same `work()` path, so it gets `builder_ready` before `@@installed` with no extra change here, and I'll keep both contracts whichever of the two lands second. One thing to know: I have a small follow-up planned that lets a later release rename the builder (the setup records the list's `run` path, and the game folder's launcher opens that). I'll base it on #2449 once it's in, and derive the `.dll` / `.deps.json` / `.runtimeconfig.json` names from the exe's own name rather than `HookshotBuilder`, so the check holds for a renamed builder too. Noted on Avalonia / single-file: that contract will change with the standalone launcher anyway.
Joey-Magnus (Migrated from github.com) reviewed 2026-11-11 21:25:04 +00:00
Joey-Magnus (Migrated from github.com) left a comment

It now conflicts with main in MainWindow.xaml.cs, builder/linux/hookshot_builder.py, builder/linux/linux_update.py and tests/test_updater_asset_digest.py (#2141, #2150, #2159).

The art blocker is fixed: only the Marcellus font is new, with its OFL licence. I spot-checked the risky paths and they hold: --engine still requires https parts with sha256, the localhost exception only applies when the list itself came from localhost, and RX_VK_PCACHE_LOAD is rewritten through .new plus rename.

Minor: link_tree hard-links shared parts between versions, so nothing may ever write those files in place.

Merge order: I'd take #2367 first (clean), then this rebased with BuilderHome.PassTo on the hidden --engine setup and the portable home in FreeAfterBuild. The scope (Releases tab installing any older release, Nightly no longer selectable) is NotRexed's call.

It now conflicts with main in `MainWindow.xaml.cs`, `builder/linux/hookshot_builder.py`, `builder/linux/linux_update.py` and `tests/test_updater_asset_digest.py` (#2141, #2150, #2159). The art blocker is fixed: only the Marcellus font is new, with its OFL licence. I spot-checked the risky paths and they hold: `--engine` still requires https parts with sha256, the localhost exception only applies when the list itself came from localhost, and `RX_VK_PCACHE_LOAD` is rewritten through `.new` plus rename. Minor: `link_tree` hard-links shared parts between versions, so nothing may ever write those files in place. Merge order: I'd take #2367 first (clean), then this rebased with `BuilderHome.PassTo` on the hidden `--engine` setup and the portable home in `FreeAfterBuild`. The scope (Releases tab installing any older release, Nightly no longer selectable) is NotRexed's call.
madebyjordan commented 2026-11-11 22:46:02 +00:00 (Migrated from github.com)

Thanks @Joey-Magnus. #2367 was already in, so I merged main (55dd6414) and followed your order:

  • Portable home: the hidden --engine setup now gets BuilderHome.PassTo, and Storage.Base is BuilderHome.Base, so FreeAfterBuild cleans up in the portable home. The merge also caught a real problem: engine mode ran before init_home, so every engine install would have stopped on "home unavailable". It now opens the home first.
  • Shared parts in a portable home: engine mode still links them, in a portable home too, so the version in use stays whole. Otherwise #2367's move/copy is unchanged. test_portable_setup_transaction.py now also runs that path and checks the files are links, in both homes.
  • link_tree: I checked what writes into the shared parts, and nothing writes in place. llvm-mingw, Python, emsdk (its clang only, never emcc and its cache), cpm, third_party and wgpu-native are only read. Python's bytecode caches are written to a new file and renamed over, which replaces that version's link alone. That contract is now written beside link_tree.
  • Conflicts:
    • #2447's "Save my pipeline list" was on the launch page this PR replaces, so it's now a row in the menu's Settings on Windows and Linux.
    • #2129/#2141: Back and Cancel share one way back. It returns to the launcher, which offers the update again, and test_builder_update_back.py now checks the launcher's flow.
    • #2160's cancel checks are in the Linux builder's message_in.

Agreed that the scope (installing older releases, Nightly not selectable) is @NotRexed's call.

Thanks @Joey-Magnus. #2367 was already in, so I merged `main` (55dd6414) and followed your order: - **Portable home:** the hidden `--engine` setup now gets `BuilderHome.PassTo`, and `Storage.Base` is `BuilderHome.Base`, so `FreeAfterBuild` cleans up in the portable home. The merge also caught a real problem: engine mode ran before `init_home`, so every engine install would have stopped on "home unavailable". It now opens the home first. - **Shared parts in a portable home:** engine mode still links them, in a portable home too, so the version in use stays whole. Otherwise #2367's move/copy is unchanged. `test_portable_setup_transaction.py` now also runs that path and checks the files are links, in both homes. - **`link_tree`:** I checked what writes into the shared parts, and nothing writes in place. llvm-mingw, Python, emsdk (its clang only, never `emcc` and its cache), cpm, third_party and wgpu-native are only read. Python's bytecode caches are written to a new file and renamed over, which replaces that version's link alone. That contract is now written beside `link_tree`. - **Conflicts:** - #2447's "Save my pipeline list" was on the launch page this PR replaces, so it's now a row in the menu's Settings on Windows and Linux. - #2129/#2141: Back and Cancel share one way back. It returns to the launcher, which offers the update again, and `test_builder_update_back.py` now checks the launcher's flow. - #2160's cancel checks are in the Linux builder's `message_in`. Agreed that the scope (installing older releases, Nightly not selectable) is @NotRexed's call.
Owner

@hckrman101 commented on GitHub · 2026-11-12 06:28 UTC · original

This is starting to look good. Few notes:

  • We should put the update button above the play button
  • Hookshot logo and buttons could be centered a bit more
> [@hckrman101](https://github.com/hckrman101) commented on GitHub · 2026-11-12 06:28 UTC · [original](https://github.com/NotRexed/Hookshot/pull/2373#issuecomment-6816489455) This is starting to look good. Few notes: - We should put the update button above the play button - Hookshot logo and buttons could be centered a bit more <!-- github-sync:c:6816489455 -->
Owner

@Joey-Magnus reviewed on GitHub · 2026-11-12 14:32 UTC · original

My last points are handled: BuilderHome.PassTo on the hidden --engine setup, the portable home in FreeAfterBuild (and the init_home ordering bug you caught), and the link_tree no-write rule written down. It conflicts with main again in builder/HookshotBuilder/MainWindow.xaml.cs (#2528, #2598), and only the title check has run on 5ccc2f16.

The new FfxRuntime.cs is sound: a pinned commit, .part first, each SHA-256 checked, and tests/test_ffx_runtime_pins.py keeps it in step with scripts/ffx_sdk.py. One small thing: Launcher.cs calls FfxRuntime.Ensure(Storage.FfxSdk) without a cancellation token, so Cancel waits out the 120 s timeout.

On the PR head, test_linux_launcher_lists, test_linux_launcher_work, test_linux_update_cancel, test_builder_update_back and test_ffx_runtime_pins pass (the C# and mingw checks need Windows).

At +7.4k lines it keeps gaining features (rebuild, FSR runtime fetch). The scope (installing any older release, Nightly not selectable) still needs NotRexed's decision; moving the rebuild and FSR work to a follow-up would help it land. hckrman101's layout notes are open too.

> [@Joey-Magnus](https://github.com/Joey-Magnus) reviewed on GitHub · 2026-11-12 14:32 UTC · [original](https://github.com/NotRexed/Hookshot/pull/2373#pullrequestreview-5876210937) My last points are handled: `BuilderHome.PassTo` on the hidden `--engine` setup, the portable home in `FreeAfterBuild` (and the `init_home` ordering bug you caught), and the `link_tree` no-write rule written down. It conflicts with main again in `builder/HookshotBuilder/MainWindow.xaml.cs` (#2528, #2598), and only the title check has run on 5ccc2f16. The new `FfxRuntime.cs` is sound: a pinned commit, `.part` first, each SHA-256 checked, and `tests/test_ffx_runtime_pins.py` keeps it in step with `scripts/ffx_sdk.py`. One small thing: `Launcher.cs` calls `FfxRuntime.Ensure(Storage.FfxSdk)` without a cancellation token, so Cancel waits out the 120 s timeout. On the PR head, `test_linux_launcher_lists`, `test_linux_launcher_work`, `test_linux_update_cancel`, `test_builder_update_back` and `test_ffx_runtime_pins` pass (the C# and mingw checks need Windows). At +7.4k lines it keeps gaining features (rebuild, FSR runtime fetch). The scope (installing any older release, Nightly not selectable) still needs NotRexed's decision; moving the rebuild and FSR work to a follow-up would help it land. hckrman101's layout notes are open too. <!-- github-sync:r:5876210937 -->
Commenting is not possible because the repository is archived.
No description provided.