feat(menu): a Recomp Options button in the pause menu that opens the Insert menu #2304

Open
ProbBC wants to merge 9 commits from ProbBC/menu/recomp-options into main
ProbBC commented 2026-11-11 14:09:09 +00:00 (Migrated from github.com)

The Insert menu's settings can only be reached with a key (Insert, F1) or a pad chord, which a controller player may never find. This adds a sixth button, "Recomp Options", to the pause menu's Menu page, below Title Screen, drawn and moved to like the game's own; A on it opens the Insert menu.

What changed

  • nx2/pause_layout.py (new): adds Pa_Button_05 to every PaSystemMenu_00 layout in Common.blarc at build time, a copy of Pa_Button_04 188 units below it. The page draws only the panes its layout has (a copy made at run time is never drawn), so the layout itself gets the sixth pane. Everything that grows is appended after the end of the layout and of the archive: no other offset moves, and the prompts' runs over Common.blarc stay valid.
  • nx2/prompt_assets.py: writes the edited archive to prompts/Common.blarc (with a prompts/recomp_button marker) and raises its entry in the resource size table.
  • host/prompts.c: serves prompts/Common.blarc with the button even when no prompt runs are written over it.
  • host/oot_pause_menu.c, nx2/oot_pause_menu.py (new): when the page is made (f_80f99400) the sixth pane gets a button object like the other five, and its text ("Recomp Options", localized for pt/es/fr/it/de) is put on its text boxes. A while the cursor is on it (its state, noted at f_80ad5310) opens the Insert menu and is not passed to the game: the page's script has no end for a sixth button's decision, and passing it left the pause menu impossible to close.
  • host/nn_hid.c: the buttons pass through hos_pausebtn_buttons.
  • host/nn_applet.c: hos_game_language() for the button's text.
  • Linux: the Insert menu's key queue (host/rx_backend.c) now exists on every platform, and host/posix/display_sdl.c notes each key on the look it goes down, as host/display.c's WM_KEYDOWN does on Windows. The Insert menu then reads its keys the same way on both, and the button's F1 request reaches it on Linux too.
  • Tests: tests/test_oot_pause_menu.py (the lifted-C edits all or none; unknown layouts and archives left as they are); tests/test_linux_menu_keys.py (Linux keys reach the queue once on their way down, none while the window is not in front); tests/test_input_background.py gets a pass-through stub for the new call in npad_states.

No game data is in the change: the layout is edited on the player's PC from their own files, and the original romfs is not modified.

How it was tested

  • Windows 11, NVIDIA GeForce RTX 2070 SUPER, Vulkan 1.4
  • The button shows below Title Screen with its text, the cursor reaches it from Title Screen, A opens the Insert menu, B then closes the pause menu, and A in play afterwards is the game's.
  • Linux: not tested.

Screenshots

image image
The Insert menu's settings can only be reached with a key (Insert, F1) or a pad chord, which a controller player may never find. This adds a sixth button, "Recomp Options", to the pause menu's Menu page, below Title Screen, drawn and moved to like the game's own; A on it opens the Insert menu. ## What changed - `nx2/pause_layout.py` (new): adds `Pa_Button_05` to every `PaSystemMenu_00` layout in `Common.blarc` at build time, a copy of `Pa_Button_04` 188 units below it. The page draws only the panes its layout has (a copy made at run time is never drawn), so the layout itself gets the sixth pane. Everything that grows is appended after the end of the layout and of the archive: no other offset moves, and the prompts' runs over `Common.blarc` stay valid. - `nx2/prompt_assets.py`: writes the edited archive to `prompts/Common.blarc` (with a `prompts/recomp_button` marker) and raises its entry in the resource size table. - `host/prompts.c`: serves `prompts/Common.blarc` with the button even when no prompt runs are written over it. - `host/oot_pause_menu.c`, `nx2/oot_pause_menu.py` (new): when the page is made (`f_80f99400`) the sixth pane gets a button object like the other five, and its text ("Recomp Options", localized for pt/es/fr/it/de) is put on its text boxes. A while the cursor is on it (its state, noted at `f_80ad5310`) opens the Insert menu and is not passed to the game: the page's script has no end for a sixth button's decision, and passing it left the pause menu impossible to close. - `host/nn_hid.c`: the buttons pass through `hos_pausebtn_buttons`. - `host/nn_applet.c`: `hos_game_language()` for the button's text. - Linux: the Insert menu's key queue (`host/rx_backend.c`) now exists on every platform, and `host/posix/display_sdl.c` notes each key on the look it goes down, as `host/display.c`'s `WM_KEYDOWN` does on Windows. The Insert menu then reads its keys the same way on both, and the button's F1 request reaches it on Linux too. - Tests: `tests/test_oot_pause_menu.py` (the lifted-C edits all or none; unknown layouts and archives left as they are); `tests/test_linux_menu_keys.py` (Linux keys reach the queue once on their way down, none while the window is not in front); `tests/test_input_background.py` gets a pass-through stub for the new call in `npad_states`. No game data is in the change: the layout is edited on the player's PC from their own files, and the original romfs is not modified. ## How it was tested - Windows 11, NVIDIA GeForce RTX 2070 SUPER, Vulkan 1.4 - The button shows below Title Screen with its text, the cursor reaches it from Title Screen, A opens the Insert menu, B then closes the pause menu, and A in play afterwards is the game's. - Linux: not tested. ## Screenshots <img width="1919" height="1079" alt="image" src="https://github.com/user-attachments/assets/36162a5b-712d-4489-b293-da17d9081994" /> <img width="1918" height="1079" alt="image" src="https://github.com/user-attachments/assets/b90df6c9-16bc-45b6-ae79-b3c9a161197f" />
Joey-Magnus (Migrated from github.com) reviewed 2026-11-11 14:09:09 +00:00
hckrman101 (Migrated from github.com) reviewed 2026-11-11 17:34:45 +00:00
hckrman101 (Migrated from github.com) left a comment

Read through it and I like the approach: the button is a real pane added to the layout at build time from the player's own files, nothing game-derived is committed, the lift edits are all-or-none with a warning, and swallowing A (instead of letting the game see a decision its script has no end for) is the right call given the pause-menu-can't-close problem you describe. Screenshots are there, and the Linux key-queue change is small and covered by a test. Nothing blocking in the code; a few things before this can go in:

  • CI has not run. The three real checks (Windows builder, Linux AppImage, game-free regressions) are all action_required, waiting on a maintainer to approve the workflow run. Only the title check is green, so the new tests are unproven in CI.
  • Overlap with #2366 (live prompts). It touches the same places: build_layouts in host/prompts.c, size_table/main in nx2/prompt_assets.py, host/nn_hid.c, nx2/oot_patches.py and host/rx_backend.c. Both change how Common.blarc is built and served (here prompts/recomp_button plus a size/mtime hash; there the live reload), so whichever lands second needs a real merge, and the two should agree on who owns the served-archive key. Worth sorting out the order with the author of #2366 and @NotRexed.
  • #2197 (Esc opens the Insert menu) and #2378 (tuning bar) are about the menu itself, not the pause page, so no code conflict that I can see. #2197 is a nice companion (keyboard side) and this covers the controller side; the button's synthetic F1 goes through the same hos_vk_note_key_press queue, so it keeps working if the shortcut default changes.
  • New UI in the game's own menu. This is a visible new entry in the pause menu, so it is @NotRexed's call whether he wants it there (and whether the label should be localized by us rather than by the game's message tables). I am not approving over that.
  • Linux is untested (the PR says so). The display_sdl.c change now feeds every key-down into the Insert menu queue on Linux, which also means keys pressed while the menu is closed sit in the queue until it is next opened, same as on Windows today; fine, just worth a quick run by someone on Linux.

Nit: hos_pausebtn_buttons reads the button state with a raw GP(control + 0x38) while the rest of the file uses recomp_load*; consistent use would be a little safer if the object is ever freed while our_control is still set (it is cleared on the next page build, but not when the page is torn down).

Read through it and I like the approach: the button is a real pane added to the layout at build time from the player's own files, nothing game-derived is committed, the lift edits are all-or-none with a warning, and swallowing A (instead of letting the game see a decision its script has no end for) is the right call given the pause-menu-can't-close problem you describe. Screenshots are there, and the Linux key-queue change is small and covered by a test. Nothing blocking in the code; a few things before this can go in: - **CI has not run.** The three real checks (Windows builder, Linux AppImage, game-free regressions) are all `action_required`, waiting on a maintainer to approve the workflow run. Only the title check is green, so the new tests are unproven in CI. - **Overlap with #2366 (live prompts).** It touches the same places: `build_layouts` in `host/prompts.c`, `size_table`/`main` in `nx2/prompt_assets.py`, `host/nn_hid.c`, `nx2/oot_patches.py` and `host/rx_backend.c`. Both change how `Common.blarc` is built and served (here `prompts/recomp_button` plus a size/mtime hash; there the live reload), so whichever lands second needs a real merge, and the two should agree on who owns the served-archive key. Worth sorting out the order with the author of #2366 and @NotRexed. - **#2197 (Esc opens the Insert menu) and #2378 (tuning bar)** are about the menu itself, not the pause page, so no code conflict that I can see. #2197 is a nice companion (keyboard side) and this covers the controller side; the button's synthetic F1 goes through the same `hos_vk_note_key_press` queue, so it keeps working if the shortcut default changes. - **New UI in the game's own menu.** This is a visible new entry in the pause menu, so it is @NotRexed's call whether he wants it there (and whether the label should be localized by us rather than by the game's message tables). I am not approving over that. - **Linux is untested** (the PR says so). The `display_sdl.c` change now feeds every key-down into the Insert menu queue on Linux, which also means keys pressed while the menu is closed sit in the queue until it is next opened, same as on Windows today; fine, just worth a quick run by someone on Linux. Nit: `hos_pausebtn_buttons` reads the button state with a raw `GP(control + 0x38)` while the rest of the file uses `recomp_load*`; consistent use would be a little safer if the object is ever freed while `our_control` is still set (it is cleared on the next page build, but not when the page is torn down).
ProbBC commented 2026-11-11 18:58:49 +00:00 (Migrated from github.com)

@hckrman101 Thanks for the review! Both points are addressed:

Button state read (nit): fixed in 9d678754.

  • hos_pausebtn_buttons now reads the control's state with recomp_load8 instead of a raw GP().
  • The page's teardown now clears it as well: f_80aa2ad0, the base of the page's destructor (shared with other screens), calls hos_pausebtn_gone on entry. When the page going is ours, that forgets the page, part, button and control, so nothing is read after the object is freed.
  • tests/test_oot_pause_menu.py checks both (no raw GP() read, and the control cleared on teardown).

Overlap with #2366: merged main after it landed (b5aa48ff). The conflicts were in host/prompts.c and nx2/prompt_assets.py:

  • #2366's build_layouts now serves Common.blarc on its own, so this PR's separate serving (the prompts/recomp_button marker and the nruns || button path) is gone. #2366 owns the served-archive key; this PR only folds prompts/Common.blarc's size and mtime into it (layout_runs), so a build that adds the button rebuilds the served file.
  • size_table takes len(font) as in #2366 and still raises the layouts' entry by the bytes the button adds.
@hckrman101 Thanks for the review! Both points are addressed: **Button state read (nit)**: fixed in 9d678754. - `hos_pausebtn_buttons` now reads the control's state with `recomp_load8` instead of a raw `GP()`. - The page's teardown now clears it as well: `f_80aa2ad0`, the base of the page's destructor (shared with other screens), calls `hos_pausebtn_gone` on entry. When the page going is ours, that forgets the page, part, button and control, so nothing is read after the object is freed. - `tests/test_oot_pause_menu.py` checks both (no raw `GP()` read, and the control cleared on teardown). **Overlap with #2366**: merged `main` after it landed (b5aa48ff). The conflicts were in `host/prompts.c` and `nx2/prompt_assets.py`: - #2366's `build_layouts` now serves `Common.blarc` on its own, so this PR's separate serving (the `prompts/recomp_button` marker and the `nruns || button` path) is gone. #2366 owns the served-archive key; this PR only folds `prompts/Common.blarc`'s size and mtime into it (`layout_runs`), so a build that adds the button rebuilds the served file. - `size_table` takes `len(font)` as in #2366 and still raises the layouts' entry by the bytes the button adds.
Joey-Magnus (Migrated from github.com) requested changes 2026-11-11 21:23:51 +00:00
Joey-Magnus (Migrated from github.com) left a comment

The new test will fail on Linux once the workflows run: tests/test_oot_pause_menu.py:130 builds its include list without host/posix, so host/oot_pause_menu.c:17 (#include <windows.h>) isn't found. Add -I host/posix off Windows.

Also:

  • The display_sdl.c change routes every key-down into the Insert menu's queue, which moves Linux menu input from polled state to a queue. Nobody has run it on Linux yet.
  • The earlier GP() nit is only fixed on paper (recomp_load8 ignores its context). The real fix is the hos_pausebtn_gone teardown hook, which looks right.

Whether a new entry belongs in the game's own pause menu is the maintainers' call.

The new test will fail on Linux once the workflows run: `tests/test_oot_pause_menu.py:130` builds its include list without `host/posix`, so `host/oot_pause_menu.c:17` (`#include <windows.h>`) isn't found. Add `-I host/posix` off Windows. Also: - The `display_sdl.c` change routes every key-down into the Insert menu's queue, which moves Linux menu input from polled state to a queue. Nobody has run it on Linux yet. - The earlier `GP()` nit is only fixed on paper (`recomp_load8` ignores its context). The real fix is the `hos_pausebtn_gone` teardown hook, which looks right. Whether a new entry belongs in the game's own pause menu is the maintainers' call.
hckrman101 commented 2026-11-11 22:17:24 +00:00 (Migrated from github.com)

This now conflicts with main in nx2/oot_patches.py. Could you merge main into the branch (or rebase) when you get a chance? I'll take another look once it's up to date.

This now conflicts with main in `nx2/oot_patches.py`. Could you merge main into the branch (or rebase) when you get a chance? I'll take another look once it's up to date.
ProbBC commented 2026-11-12 02:16:12 +00:00 (Migrated from github.com)

@Joey-Magnus
Thanks! Addressed:

  • Linux compile check: fixed in 5cd55e09. tests/test_oot_pause_menu.py adds -I host/posix off Windows (as tests/test_display_pointer.py does).
  • GP() nit: recomp_load8 is only for consistency with the rest of the file. The actual fix is the hos_pausebtn_gone teardown hook.

@hckrman101 merged current main and fixed conflicts

@Joey-Magnus Thanks! Addressed: - **Linux compile check**: fixed in 5cd55e09. `tests/test_oot_pause_menu.py` adds `-I host/posix` off Windows (as `tests/test_display_pointer.py` does). - **`GP()` nit**: `recomp_load8` is only for consistency with the rest of the file. The actual fix is the `hos_pausebtn_gone` teardown hook. @hckrman101 merged current `main` and fixed conflicts
Owner

@Joey-Magnus requested changes on GitHub · 2026-11-12 13:38 UTC · original

Thanks. The Linux include fix (5cd55e09) is right, and I'm fine with the recomp_load8 reasoning, since the teardown hook is the real fix.

One new problem from main: #2698 (merged this morning) keeps the button-prompt assets when nothing they're made from has changed, and prompt_key() in nx2/prompt_assets.py lists those sources. This PR's prompt_assets.py now imports pause_layout, which isn't in that list. So after a change to nx2/pause_layout.py, a source build keeps the old prompts/Common.blarc. Merged into main (51da0464), tests/test_prompt_skip.py catches it:

AssertionError: 'pause_layout' not found in {'prompt_assets', 'prompt_faces', 'kit', 'prompt_draw', 'prompt_keys'} : new prompt dependency needs fingerprint coverage

The fix is to add "pause_layout.py" to source in prompt_key() and "pause_layout" to sources in that test. The rest of tests/run_all.py passes here, apart from a lift-cache check that fails on main too.

Still open from before: the display_sdl.c change moves Linux menu input from polled state to a queue, and nobody has said they ran it on Linux. A short run there (open the Insert menu from the new button, move around, bind a key) would settle it. CI hasn't built this head yet either (only the title check has run).

> [@Joey-Magnus](https://github.com/Joey-Magnus) requested changes on GitHub · 2026-11-12 13:38 UTC · [original](https://github.com/NotRexed/Hookshot/pull/2304#pullrequestreview-5875382589) Thanks. The Linux include fix (5cd55e09) is right, and I'm fine with the `recomp_load8` reasoning, since the teardown hook is the real fix. One new problem from main: **#2698** (merged this morning) keeps the button-prompt assets when nothing they're made from has changed, and `prompt_key()` in `nx2/prompt_assets.py` lists those sources. This PR's `prompt_assets.py` now imports `pause_layout`, which isn't in that list. So after a change to `nx2/pause_layout.py`, a source build keeps the old `prompts/Common.blarc`. Merged into main (51da0464), `tests/test_prompt_skip.py` catches it: ``` AssertionError: 'pause_layout' not found in {'prompt_assets', 'prompt_faces', 'kit', 'prompt_draw', 'prompt_keys'} : new prompt dependency needs fingerprint coverage ``` The fix is to add `"pause_layout.py"` to `source` in `prompt_key()` and `"pause_layout"` to `sources` in that test. The rest of `tests/run_all.py` passes here, apart from a lift-cache check that fails on main too. Still open from before: the `display_sdl.c` change moves Linux menu input from polled state to a queue, and nobody has said they ran it on Linux. A short run there (open the Insert menu from the new button, move around, bind a key) would settle it. CI hasn't built this head yet either (only the title check has run). <!-- github-sync:r:5875382589 -->
Commenting is not possible because the repository is archived.
No description provided.