feat(menu): draw the F1 menu's button prompts from the game's prompt fonts #1882

Open
nicholasgower wants to merge 1 commit from nicholasgower/feat/menu-button-prompts into main
nicholasgower commented 2026-11-10 19:17:08 +00:00 (Migrated from github.com)

Why

The F1/Insert menu's prompt strip drew generic circles and D-pad crosses, so on a DualSense it showed dots where the game shows Cross, Circle, L1/R1 and the D-pad. The menu's prompts should use the same button icons as the game.

What changed

  • Read the dump's Font2/Scalable/External button-icon font and the per-face prompts/<face>/External.bfotf fonts (from nx2/prompt_assets.py) into ImGui.
  • Draw the menu's confirm/back, tabs and D-pad prompts with the icon of the pad input each action uses, following the controls.
  • Fall back to the drawn prompts when the files have no icon font.
  • Read the fonts in memory only; nothing from the game is written.

Testing

Hand-tested.
Game-free checks: test_menu_prompt_icons, test_game_font, test_menu_keys, test_menu_utf8, test_ui_record.

F1 menu with the game's Switch button prompts

### Why The F1/Insert menu's prompt strip drew generic circles and D-pad crosses, so on a DualSense it showed dots where the game shows Cross, Circle, L1/R1 and the D-pad. The menu's prompts should use the same button icons as the game. ### What changed - Read the dump's `Font2/Scalable/External` button-icon font and the per-face `prompts/<face>/External.bfotf` fonts (from `nx2/prompt_assets.py`) into ImGui. - Draw the menu's confirm/back, tabs and D-pad prompts with the icon of the pad input each action uses, following the controls. - Fall back to the drawn prompts when the files have no icon font. - Read the fonts in memory only; nothing from the game is written. ### Testing Hand-tested. Game-free checks: `test_menu_prompt_icons`, `test_game_font`, `test_menu_keys`, `test_menu_utf8`, `test_ui_record`. ![F1 menu with the game's Switch button prompts](https://raw.githubusercontent.com/nicholasgower/Hookshot/pr-assets-button-prompts/docs/pr-button-prompts.jpg)
hckrman101 (Migrated from github.com) reviewed 2026-11-10 20:53:06 +00:00
hckrman101 (Migrated from github.com) left a comment

Nice change, and the game-asset rules are respected. I checked that: nothing from the game is added to the repo (the diff is code, a test and the readme). The icon font is read at runtime from the user's own files: Font2/Scalable/External.Nin_Ounce_NVN2.bfotf.zs through the existing game_font::Reader (zstd + XOR in memory), and prompts/<face>/External.bfotf through read_file + bfotf_font. Both go to AddFontFromMemoryTTF with the atlas owning the copy, so nothing is written back out. The new test only reads code points from nx2/prompt_assets.py and compiles a cut-out of vk_ui.h with a fixture, so no dump is needed. The screenshot is a picture of the menu, which CONTRIBUTING allows.

One thing to check: game_dir() takes the parent of romfs_dir(), but host/prompts.c game_dir() uses the folder of the game executable (hos_game_path_a), and that is where nx2/prompt_assets.py --out <game folder>/prompts writes. When --romfs points at a dump folder somewhere else (your own comment says that is allowed), the menu will look for prompts/<face>/External.bfotf in the wrong place and quietly fall back to the ROM's Switch icons, while the game itself shows the face's icons. It would be safer to use the same folder the game uses (a host call, or the executable's folder), or to note why the romfs parent is right. Nothing breaks if it is wrong, the icons are just the Switch's.

Smaller points:

  • nit: prompt_face_font and the External font are loaded even with RX_VK_UI_GAME_FONT=0. That variable's comment says Libertinus whatever the game's files have, so you may want it to cover the icons too.
  • nit: the last check in test_menu_prompt_icons.py greps for the literal strings 0xF700 + pad and 0xF700, 0xF700 + HOS_PAD_N - 1, which will break on any reformat. Fine as a guard, just brittle.

CI: the C++ builds (Windows llvm-mingw, AppImage) pass. The two Python source checks fail, but on tests this PR does not touch (test_antilag on both, nx2/test_draw_parameters on Linux, test_nup_out_of_space and test_wayland_libdecor_hint on Windows), and the new tests pass. They look like main's own failures, but I did not compare against a finished main run.

Nice change, and the game-asset rules are respected. I checked that: nothing from the game is added to the repo (the diff is code, a test and the readme). The icon font is read at runtime from the user's own files: `Font2/Scalable/External.Nin_Ounce_NVN2.bfotf.zs` through the existing `game_font::Reader` (zstd + XOR in memory), and `prompts/<face>/External.bfotf` through `read_file` + `bfotf_font`. Both go to `AddFontFromMemoryTTF` with the atlas owning the copy, so nothing is written back out. The new test only reads code points from `nx2/prompt_assets.py` and compiles a cut-out of `vk_ui.h` with a fixture, so no dump is needed. The screenshot is a picture of the menu, which CONTRIBUTING allows. One thing to check: `game_dir()` takes the parent of `romfs_dir()`, but `host/prompts.c` `game_dir()` uses the folder of the game executable (`hos_game_path_a`), and that is where `nx2/prompt_assets.py --out <game folder>/prompts` writes. When `--romfs` points at a dump folder somewhere else (your own comment says that is allowed), the menu will look for `prompts/<face>/External.bfotf` in the wrong place and quietly fall back to the ROM's Switch icons, while the game itself shows the face's icons. It would be safer to use the same folder the game uses (a host call, or the executable's folder), or to note why the romfs parent is right. Nothing breaks if it is wrong, the icons are just the Switch's. Smaller points: - nit: `prompt_face_font` and the `External` font are loaded even with `RX_VK_UI_GAME_FONT=0`. That variable's comment says Libertinus whatever the game's files have, so you may want it to cover the icons too. - nit: the last check in `test_menu_prompt_icons.py` greps for the literal strings `0xF700 + pad` and `0xF700, 0xF700 + HOS_PAD_N - 1`, which will break on any reformat. Fine as a guard, just brittle. CI: the C++ builds (Windows llvm-mingw, AppImage) pass. The two Python source checks fail, but on tests this PR does not touch (`test_antilag` on both, `nx2/test_draw_parameters` on Linux, `test_nup_out_of_space` and `test_wayland_libdecor_hint` on Windows), and the new tests pass. They look like main's own failures, but I did not compare against a finished main run.
hckrman101 commented 2026-11-11 15:50:59 +00:00 (Migrated from github.com)

Heads-up: this conflicts with main now. Could you rebase onto main and resolve the conflicts? Thanks!

Heads-up: this conflicts with main now. Could you rebase onto main and resolve the conflicts? Thanks!
Joey-Magnus (Migrated from github.com) requested changes 2026-11-11 21:46:12 +00:00
Joey-Magnus (Migrated from github.com) left a comment

Main changed the file this reads. nx2/prompt_assets.py on main now writes a single prompts/External.bfotf with every face in it, each face's pad icons at U+F700 + 32 x HOS_PROMPT_* + HOS_PAD_* (prompt_assets.py:6-10, written at line 966; host/prompts.c's header says the same). prompt_face_font() looks for prompts//External.bfotf with icons at U+F700 + HOS_PAD_*. After a rebase, face_icons() is false for every face and the menu silently shows the ROM's Switch icons, which is the bug this PR sets out to fix. test_menu_prompt_icons only checks the Switch code points, so it won't catch this. Loading the one file and offsetting the code point by 32 x prompt_face would fix it, and a check against prompt_assets.PAD_BASE and the per-face stride would cover it. hckrman101's point about game_dir() using the romfs parent instead of the executable's folder (host/prompts.c game_dir) is still open.

Main changed the file this reads. nx2/prompt_assets.py on main now writes a single prompts/External.bfotf with every face in it, each face's pad icons at U+F700 + 32 x HOS_PROMPT_* + HOS_PAD_* (prompt_assets.py:6-10, written at line 966; host/prompts.c's header says the same). prompt_face_font() looks for prompts/<face>/External.bfotf with icons at U+F700 + HOS_PAD_*. After a rebase, face_icons() is false for every face and the menu silently shows the ROM's Switch icons, which is the bug this PR sets out to fix. test_menu_prompt_icons only checks the Switch code points, so it won't catch this. Loading the one file and offsetting the code point by 32 x prompt_face would fix it, and a check against prompt_assets.PAD_BASE and the per-face stride would cover it. hckrman101's point about game_dir() using the romfs parent instead of the executable's folder (host/prompts.c game_dir) is still open.
Commenting is not possible because the repository is archived.
No description provided.