feat(builder): one desktop Flutter builder for Linux and Windows #2403

Closed
dazemc wants to merge 5 commits from feat/flutter-builder into main
dazemc commented 2026-11-11 17:26:23 +00:00 (Migrated from github.com)

Why

Hookshot has two builder GUIs — Tk on Linux, WPF on Windows — with duplicated build, update, launcher and save logic. They drift: a fix or a profile lands in one and the other keeps its old behaviour. This PR adds one desktop Flutter builder for both systems, built on flutter_bloc and the existing Slate look, and ports the open builder fixes into the shared engine so the same behaviour is tested without a game.

The game never leaves the PC: every build, run and test below used the local dump only; no game file or anything made from it is in this branch.

AI disclosure: this PR was created by the AI coding agent DeepSeek V4.1 Flash 1017 running in OpenCode, using the reporter's Sage tool. The reporter directed the work and tested it locally.

What the Flutter builder does

  • One window, one state machine. Game files, install location, build options, progress, result/problem and the game launcher are BLoC states; no shell/Tk/WPF callbacks or duplicated @@ parsers.
  • A real game launcher. Every Play opens the game's launcher first: profile, pinned version, rebuild, and (Linux) gamescope options. It no longer jumps straight into the game and hides those choices.
  • Profiles (#1958). Profile dropdown and Manage (new, rename, remove, import a save, export a save, open save folder). The engine side is nx2/profiles.py; the game validates HOOKSHOT_PROFILE against profiles.txt; scripts/oot_saves.py fills a new profile's save folder. hookshot-launch passes the saved profile.
  • Version selector and pin (#2201). Stable releases with this platform's assets, "Stay on the selected version", save/settings backup before a switch, and the game-side pin check.
  • Gamescope on Linux only. Off by default. Options: size, refresh, fullscreen, HDR, cursor grab, real-time scheduling, gamemoderun, and extra arguments (the Steam-style -W 3840 -H 2160 -r 164 -f --hdr-enabled --force-grab-cursor --rt -- %command% is covered). Already inside gamescope: started directly.
  • "Close the builder after the game starts" can be unticked, so the window stays open beside the game (the old behaviour is the default).
  • Build feedback. Explicit stage lines, files read (first ones and every N), files compiled/cached, numeric percentage through read, lift and compile, an animated current stage, a pinned auto-scrolling log, and a real "Build cancelled" result.
  • Damaged dumps (#2237/#2254). The ready/problem screens show the count and the list; hookshot_damaged_files.txt is written beside the game and both old/new note names are cleared on a clean check.
  • Cancel (#2160), fresh stop reasons (#2159), clock/countdown (#2150), update offered again (#2141/#2129), rebuild (#2287, launcher action), disk preflight (#1388).
  • Animations. Page fade/slide, animated step/stage markers, pop-in status icons and launcher status.

Ported into the shared engine

  • #2254 / #2237 — damaged-file notes and the warning path; #2096 — stop when damage has no replacement.
  • #2253 / #2246 — UTF-8 cache stamps with a legacy code-page fallback, exact-or-empty decode.
  • #2250 / #2142 — stale-process cleanup only for proven work/output paths.
  • #2137 / #2257 — linker options one per -Xlinker (comma-safe build paths).
  • #2242 — shader-preparation watchdog treats growing child CPU time as work, not silence.
  • #1414 — repository-relative update URLs for the game, setup, Tk updater and Flutter updater.
  • #2275 — AppImage workflow retries PPA enrollment before the signed-index refresh.
  • #2045 item 3 — setup-list validation (tags, destinations, URLs, checksums, symlinks).

Benefits over the split codebases

Split Tk + WPF This branch
Two GUI implementations and two command/@@ parsers One Flutter UI and one BLoC state machine
A fix or option often lands on one OS only Shared Dart behaviour with platform-only gates (gamescope is Linux-only)
Launcher/profile/version work is separate per GUI One launcher screen for both; one profile engine
Update/cancel/download code differs per GUI One Dart updater, tested once, used on both
Inconsistent layouts and icon sets Fluent + Lucide on the same page chrome
Play bypasses the launcher Play always lands on the launcher options

Screenshots

Linux, from this branch:

Linux form
Linux launcher
Linux build log and percentage

Windows 11 26H2 in a VM, flutter build windows --debug from this branch:

Windows launcher

Testing

Linux (CachyOS, RX 7900-class, real 25.6 GB dump):

  • Full build via the Flutter UI: 12:34, shaders prepared; game launched to the title screen from the launcher.
  • Profile 5a3c9e1f0b7d24680000000000000002 selected; hookshot_log.txt says account: profile 5a3c9e1f0b7d24680000000000000002.
  • Gamescope: gamescope -- /home/.../hookshot observed; game precompiled 10,956 programs / 15,330 pipelines.
  • Cancel path exercised and shows "Build cancelled"; cache cleared after layout/log findings.

Windows 11 26H2 VM:

  • flutter build windows --debug succeeds (Visual Studio Build Tools 17.14, Windows SDK 10.0.26100).
  • The app starts, the profile helper finds python.exe (%LOCALAPPDATA%\Programs\Python\Python312), profiles load, and free space reads correctly; gamescope is absent as intended.
  • A full Windows game build was not run in the VM (the game dump and llvm-mingw toolchain were not copied into it); this remains the one unverified platform path.

Checks:

  • python -I -B tests/run_all.py: 368 passed, 10 skipped, 1 failed — tests/test_updater_asset_digest.py cannot resolve .NET 8 reference packs in this environment (pre-existing).
  • dart analyze lib test: clean.
  • flutter test: 40 passed.
  • flutter build linux --debug and flutter build windows --debug: succeed.

Follow-ups (not this PR)

  • Flutter packaging in the Linux AppImage and Windows setup (the old Tk/WPF apps are still what the current release pipeline packages).
  • Separate versions for the builder GUI, the nx2 engine/toolchain and the built game; the header now labels the builder (Builder 1.0.0) and tools separately, but the release/update pipeline still uses one tag.
  • #1896 (Windows setup tar.exe fallback) and #2218 (shader preparation time on slow AMD drivers).
  • Full Windows game build with the Windows toolchain.

Closes #2237, #2246, #2142, #2257, #2242, #2129, #1388, #2287 (launcher rebuild), #1958, #1414, #2275, #1759, #2160, #2159, #2150, #2141, #2201, #2254, #2253, #2250, #2137, #2096, and #2045 item 3. #1002, #1700 and #2005 are the damage evidence this warning path is for.

## Why Hookshot has two builder GUIs — Tk on Linux, WPF on Windows — with duplicated build, update, launcher and save logic. They drift: a fix or a profile lands in one and the other keeps its old behaviour. This PR adds one desktop Flutter builder for both systems, built on `flutter_bloc` and the existing Slate look, and ports the open builder fixes into the shared engine so the same behaviour is tested without a game. The game never leaves the PC: every build, run and test below used the local dump only; no game file or anything made from it is in this branch. > **AI disclosure:** this PR was created by the AI coding agent **DeepSeek V4.1 Flash 1017** running in OpenCode, using the reporter's **Sage** tool. The reporter directed the work and tested it locally. ## What the Flutter builder does - **One window, one state machine.** Game files, install location, build options, progress, result/problem and the game launcher are BLoC states; no shell/Tk/WPF callbacks or duplicated `@@` parsers. - **A real game launcher.** Every **Play** opens the game's launcher first: profile, pinned version, rebuild, and (Linux) gamescope options. It no longer jumps straight into the game and hides those choices. - **Profiles (#1958).** Profile dropdown and Manage (new, rename, remove, import a save, export a save, open save folder). The engine side is `nx2/profiles.py`; the game validates `HOOKSHOT_PROFILE` against `profiles.txt`; `scripts/oot_saves.py` fills a new profile's save folder. `hookshot-launch` passes the saved profile. - **Version selector and pin (#2201).** Stable releases with this platform's assets, "Stay on the selected version", save/settings backup before a switch, and the game-side pin check. - **Gamescope on Linux only.** Off by default. Options: size, refresh, fullscreen, HDR, cursor grab, real-time scheduling, `gamemoderun`, and extra arguments (the Steam-style `-W 3840 -H 2160 -r 164 -f --hdr-enabled --force-grab-cursor --rt -- %command%` is covered). Already inside gamescope: started directly. - **"Close the builder after the game starts"** can be unticked, so the window stays open beside the game (the old behaviour is the default). - **Build feedback.** Explicit stage lines, files read (first ones and every N), files compiled/cached, numeric percentage through read, lift and compile, an animated current stage, a pinned auto-scrolling log, and a real "Build cancelled" result. - **Damaged dumps (#2237/#2254).** The ready/problem screens show the count and the list; `hookshot_damaged_files.txt` is written beside the game and both old/new note names are cleared on a clean check. - **Cancel (#2160), fresh stop reasons (#2159), clock/countdown (#2150), update offered again (#2141/#2129), rebuild (#2287, launcher action), disk preflight (#1388).** - **Animations.** Page fade/slide, animated step/stage markers, pop-in status icons and launcher status. ## Ported into the shared engine - #2254 / #2237 — damaged-file notes and the warning path; #2096 — stop when damage has no replacement. - #2253 / #2246 — UTF-8 cache stamps with a legacy code-page fallback, exact-or-empty decode. - #2250 / #2142 — stale-process cleanup only for proven work/output paths. - #2137 / #2257 — linker options one per `-Xlinker` (comma-safe build paths). - #2242 — shader-preparation watchdog treats growing child CPU time as work, not silence. - #1414 — repository-relative update URLs for the game, setup, Tk updater and Flutter updater. - #2275 — AppImage workflow retries PPA enrollment before the signed-index refresh. - #2045 item 3 — setup-list validation (tags, destinations, URLs, checksums, symlinks). ## Benefits over the split codebases | Split Tk + WPF | This branch | |---|---| | Two GUI implementations and two command/`@@` parsers | One Flutter UI and one BLoC state machine | | A fix or option often lands on one OS only | Shared Dart behaviour with platform-only gates (gamescope is Linux-only) | | Launcher/profile/version work is separate per GUI | One launcher screen for both; one profile engine | | Update/cancel/download code differs per GUI | One Dart updater, tested once, used on both | | Inconsistent layouts and icon sets | Fluent + Lucide on the same page chrome | | Play bypasses the launcher | Play always lands on the launcher options | ## Screenshots Linux, from this branch: ![Linux form](https://github.com/dazemc/Hookshot/raw/feat/flutter-builder/docs/builder/linux-form.png) ![Linux launcher](https://github.com/dazemc/Hookshot/raw/feat/flutter-builder/docs/builder/linux-launcher.png) ![Linux build log and percentage](https://github.com/dazemc/Hookshot/raw/feat/flutter-builder/docs/builder/linux-build.png) Windows 11 26H2 in a VM, `flutter build windows --debug` from this branch: ![Windows launcher](https://github.com/dazemc/Hookshot/raw/feat/flutter-builder/docs/builder/windows-launcher.png) ## Testing Linux (CachyOS, RX 7900-class, real 25.6 GB dump): - Full build via the Flutter UI: 12:34, shaders prepared; game launched to the title screen from the launcher. - Profile `5a3c9e1f0b7d24680000000000000002` selected; `hookshot_log.txt` says `account: profile 5a3c9e1f0b7d24680000000000000002`. - Gamescope: `gamescope -- /home/.../hookshot` observed; game precompiled 10,956 programs / 15,330 pipelines. - Cancel path exercised and shows "Build cancelled"; cache cleared after layout/log findings. Windows 11 26H2 VM: - `flutter build windows --debug` succeeds (Visual Studio Build Tools 17.14, Windows SDK 10.0.26100). - The app starts, the profile helper finds `python.exe` (`%LOCALAPPDATA%\Programs\Python\Python312`), profiles load, and free space reads correctly; gamescope is absent as intended. - A full Windows game build was not run in the VM (the game dump and llvm-mingw toolchain were not copied into it); this remains the one unverified platform path. Checks: - `python -I -B tests/run_all.py`: 368 passed, 10 skipped, 1 failed — `tests/test_updater_asset_digest.py` cannot resolve .NET 8 reference packs in this environment (pre-existing). - `dart analyze lib test`: clean. - `flutter test`: 40 passed. - `flutter build linux --debug` and `flutter build windows --debug`: succeed. ## Follow-ups (not this PR) - Flutter packaging in the Linux AppImage and Windows setup (the old Tk/WPF apps are still what the current release pipeline packages). - Separate versions for the builder GUI, the `nx2` engine/toolchain and the built game; the header now labels the builder (`Builder 1.0.0`) and tools separately, but the release/update pipeline still uses one tag. - #1896 (Windows setup `tar.exe` fallback) and #2218 (shader preparation time on slow AMD drivers). - Full Windows game build with the Windows toolchain. Closes #2237, #2246, #2142, #2257, #2242, #2129, #1388, #2287 (launcher rebuild), #1958, #1414, #2275, #1759, #2160, #2159, #2150, #2141, #2201, #2254, #2253, #2250, #2137, #2096, and #2045 item 3. #1002, #1700 and #2005 are the damage evidence this warning path is for.
hckrman101 commented 2026-11-11 17:36:34 +00:00 (Migrated from github.com)

@dazemc Hey! This is great work. We are having discussions of replacing the builder UI with a 'launcher', with a cross-platform tool, similar to this. Please join the Discord and join the conversation!

@dazemc Hey! This is great work. We are having discussions of replacing the builder UI with a 'launcher', with a cross-platform tool, similar to this. Please join the Discord and join the conversation!
Arkex-ai commented 2026-11-11 18:01:27 +00:00 (Migrated from github.com)

Thanks for linking the existing fixes. I maintain #2096, #2250, #2367 and #2406; there is useful overlap in the shared engine.

A few integration points from e1a79eb7:

  • #2096: keep the damaged-file report written before the unresolved-damage failure. The imported handler exits before writing it; I'm validating that integration with current main in the focused PR.
  • #2275 is already merged: main also bounds PPA enrollment to 180 seconds with a 5-second kill grace. Please retain that when rebasing the imported retry change.
  • #2367: portable storage covers setup/updater, build work, game association and relocation, beyond keeping GUI settings beside the executable. Let's agree one home/settings contract before packaging Flutter.
  • #2406: I can share downgrade cases for the Dart updater: offline older tools, rebuild entry points, a stale manifest equal to running tools, and confirmation surviving the handoff to the requested tools. Explicit version switching should remain available.

Could we coordinate the launcher direction with #2373 in the Discord discussion, while keeping engine fixes independently reviewable? I also have the requested workarea/scroll fallback and live release-label checks locally; I'll hold separate UI publication while that direction is being agreed.

Thanks for linking the existing fixes. I maintain #2096, #2250, #2367 and #2406; there is useful overlap in the shared engine. A few integration points from `e1a79eb7`: - **#2096:** keep the damaged-file report written **before** the unresolved-damage failure. The imported handler exits before writing it; I'm validating that integration with current main in the focused PR. - **#2275 is already merged:** main also bounds PPA enrollment to 180 seconds with a 5-second kill grace. Please retain that when rebasing the imported retry change. - **#2367:** portable storage covers setup/updater, build work, game association and relocation, beyond keeping GUI settings beside the executable. Let's agree one home/settings contract before packaging Flutter. - **#2406:** I can share downgrade cases for the Dart updater: offline older tools, rebuild entry points, a stale manifest equal to running tools, and confirmation surviving the handoff to the requested tools. Explicit version switching should remain available. Could we coordinate the launcher direction with #2373 in the Discord discussion, while keeping engine fixes independently reviewable? I also have the requested workarea/scroll fallback and live release-label checks locally; I'll hold separate UI publication while that direction is being agreed.
hckrman101 commented 2026-11-11 18:12:12 +00:00 (Migrated from github.com)

Hey, because of the launcher work in #2373, which will replace this UI completely and will use Avalonia (agreed to in Discord), I am going to close this. Thank you again for your contribution!

Hey, because of the launcher work in https://git.magicbean.dev/MagicBeanDev/Hookshot/pulls/2373, which will replace this UI completely and will use Avalonia (agreed to in Discord), I am going to close this. Thank you again for your contribution!
Commenting is not possible because the repository is archived.
No description provided.