Skip to content

feat: route double-clicked patch files to the right game client#49

Merged
lallaria merged 1 commit into
mainfrom
feat/patch-file-routing
Jun 16, 2026
Merged

feat: route double-clicked patch files to the right game client#49
lallaria merged 1 commit into
mainfrom
feat/patch-file-routing

Conversation

@lallaria

Copy link
Copy Markdown
Contributor

Problem

Every installer file association (.aplttp, .apsm, .apz5, .apemerald, ...) and URL protocol (archipelago://, mwgg://, multiworldgg://) points at MultiWorldGG.exe "%1", but the argument parser defined only flags — a double-clicked patch file was an unrecognized argument and died with SystemExit(2) before logging even started. The upstream Archipelago "double-click and play" flow was broken beta-wide, for every game.

Approach

Beta clients are takeover-only: per-world clients swap themselves into the running launcher frontend (CommonContext._takeover_existing_ui); the SNI client, text client, and launch_textclient all refuse to run standalone. So unlike upstream (which launches the matched component as a subprocess pre-GUI), routing here is GUI-first:

  1. MultiWorld.py accepts an optional positional (make_arg_parser()), resolves it before the GUI with no world imports: patch containers are identified by their root archipelago.json (the same pre-load pattern as Patch.py) and mapped to a module slug via GameIndex (custom_worlds included via the launch-time scan).
  2. The launcher starts normally; a _route_module_when_ui_ready task waits for the frontend, then routes through Utils.discover_and_launch_module — the exact code path the GUI's launch button uses, so UI takeover, on-demand world install, and the SNI/BizHawk registry fallbacks behave identically to a button click. Screen-flip hooks (client_console_init/console_init/change_screen) are feature-detected, so the TUI skips them gracefully.
  3. The patch_file kwarg is forwarded to whichever client resolves: positional CLI arg for entry-point/component launch functions (new pure helper _client_launch_argv), diff_file= for the SNI registry path, patch positional for the BizHawk registry fallback — none of which previously received a file.
  4. Files claimed by a builtin tool component (.archipelago/.mwgg/.zip multidata → Host) launch that component directly (frozen: MultiWorldGGServer.exe). Launch URLs no longer crash; they open the launcher. Unroutable files log a warning and fall back to the launcher, matching upstream.

Latent bugs fixed along the way

  • run_client(*args) packed the Namespace into a tuple, so the --game/--server-address branch (used by the GUI's restart-after-update flow) always died on a swallowed AttributeError and silently fell through to the plain GUI. It now routes through the same UI-ready deferred launch as patch files, which also makes it work cold-start.
  • The cached-stub launch helpers in worlds/LauncherComponents.py did from Launcher import get_exe, launch — a module that doesn't exist in beta (ImportError if ever exercised). get_exe/launch_exe are now implemented beta-natively, and the cached-callable relaunch (an upstream Launcher-subprocess mechanism) degrades to the loaded-component fallback. Launcher-cache machinery otherwise untouched.

Testing

  • 19 new tests in test/general/test_patch_routing.py: parser positional (file paths, URLs, flag combos), read_patch_game_name (root manifest vs multidata/apworld/missing), _client_launch_argv translation, LauncherComponents.identify/get_exe routing.
  • Full suite green locally (823 passed, 5 skipped, 993 subtests, single-process).
  • Not yet verified: the live double-click → takeover flow on a frozen build; the deferred path mirrors the GUI button flow exactly, but a manual smoke test on a built exe is the remaining confidence step.

🤖 Generated with Claude Code

@lallaria lallaria marked this pull request as draft June 12, 2026 12:18
@lallaria lallaria force-pushed the main branch 2 times, most recently from 316172e to 74f03a4 Compare June 15, 2026 18:00
@lallaria lallaria marked this pull request as ready for review June 16, 2026 11:29
Every installer file association and URL protocol points at
MultiWorldGG.exe "%1", but the argument parser had no positional, so any
double-clicked patch file died with SystemExit(2) before logging existed.

MultiWorld.py now accepts an optional positional (patch file, tool file,
or archipelago:// URL):
- Patch containers are identified by their root archipelago.json (the
  Patch.py pre-load pattern, no world imports), resolved to a module via
  GameIndex (custom_worlds included via the launch-time scan), and routed
  through discover_and_launch_module once the launcher frontend is up --
  beta clients take over the launcher UI rather than running standalone.
- Files claimed by a builtin tool component (.archipelago/.mwgg/.zip
  multidata -> Host) launch that component directly.
- Launch URLs no longer crash; they open the launcher.

Utils plumbing forwards the patch file to whichever client resolves: as
the positional CLI arg for entry-point/component launch functions (via
the new _client_launch_argv), as diff_file= for the SNI registry path,
and as the patch positional for the BizHawk registry fallback, none of
which previously received a file.

Also fixes two latent bugs this work exposed:
- run_client(*args) packed the Namespace into a tuple, so the
  --game/--server-address branch always died on AttributeError and fell
  through to the plain GUI; it now routes through the same UI-ready
  deferred launch as patch files.
- The cached-stub launch helpers in LauncherComponents imported the
  nonexistent Launcher module; get_exe/launch_exe are now implemented
  beta-natively and the callable-stub relaunch (a Launcher-subprocess
  mechanism upstream) degrades to the loaded-component fallback.
@lallaria lallaria force-pushed the feat/patch-file-routing branch from 81fbc9a to 4ed0111 Compare June 16, 2026 11:38
@lallaria lallaria merged commit f9584e3 into main Jun 16, 2026
15 checks passed
@lallaria lallaria deleted the feat/patch-file-routing branch June 16, 2026 11:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant