feat(enhancement): item assignable buttons, items on A, B, X and Y used with a shoulder hotkey #2151
Open
Ori-Jakob wants to merge 18 commits from
Ori-Jakob/assignable-buttons into main
pull from: Ori-Jakob/assignable-buttons
merge into: MagicBeanDev:main
MagicBeanDev:main
MagicBeanDev:github/pr/2893
MagicBeanDev:github/pr/2890
MagicBeanDev:github/pr/2887
MagicBeanDev:github/pr/2878
MagicBeanDev:github/pr/2875
MagicBeanDev:github/pr/2874
MagicBeanDev:github/pr/2873
MagicBeanDev:github/pr/2871
MagicBeanDev:github/pr/2756
MagicBeanDev:github/pr/2897
MagicBeanDev:github/pr/2896
MagicBeanDev:github/pr/2718
MagicBeanDev:github/pr/2842
MagicBeanDev:github/pr/2691
MagicBeanDev:github/pr/2895
MagicBeanDev:github/pr/2743
MagicBeanDev:github/pr/2765
MagicBeanDev:github/pr/2708
MagicBeanDev:github/pr/2732
MagicBeanDev:github/pr/2755
MagicBeanDev:github/pr/2817
MagicBeanDev:github/pr/2876
MagicBeanDev:release-please--branches--main
MagicBeanDev:release-please--branches--main--release-notes
MagicBeanDev:github/pr/2778
MagicBeanDev:github/pr/2888
MagicBeanDev:github/pr/2866
MagicBeanDev:github/pr/2877
MagicBeanDev:github/pr/2800
MagicBeanDev:github/pr/2829
MagicBeanDev:github/pr/2850
MagicBeanDev:github/pr/2889
MagicBeanDev:github/pr/2882
MagicBeanDev:github/pr/2860
MagicBeanDev:github/pr/2849
MagicBeanDev:github/pr/2886
MagicBeanDev:github/pr/2794
MagicBeanDev:github/pr/2840
MagicBeanDev:github/pr/2830
MagicBeanDev:github/pr/2834
MagicBeanDev:github/pr/2719
MagicBeanDev:github/pr/2742
MagicBeanDev:github/pr/2741
MagicBeanDev:github/pr/2820
MagicBeanDev:github/pr/2771
MagicBeanDev:github/pr/2859
MagicBeanDev:github/pr/2858
MagicBeanDev:github/pr/2823
MagicBeanDev:github/pr/2757
MagicBeanDev:github/pr/2769
MagicBeanDev:github/pr/2844
MagicBeanDev:github/pr/2843
MagicBeanDev:github/pr/2673
MagicBeanDev:github/pr/2855
MagicBeanDev:github/pr/2739
MagicBeanDev:github/pr/2807
MagicBeanDev:github/pr/2851
MagicBeanDev:github/pr/2789
MagicBeanDev:github/pr/2814
MagicBeanDev:github/pr/2818
MagicBeanDev:github/pr/2774
MagicBeanDev:github/pr/2772
MagicBeanDev:github/pr/2750
MagicBeanDev:github/pr/2786
MagicBeanDev:github/pr/2785
MagicBeanDev:github/pr/2782
MagicBeanDev:github/pr/2779
MagicBeanDev:github/pr/2775
MagicBeanDev:github/pr/2706
MagicBeanDev:github/pr/2766
MagicBeanDev:github/pr/2747
MagicBeanDev:github/pr/2759
MagicBeanDev:github/pr/2745
MagicBeanDev:github/pr/2740
MagicBeanDev:github/pr/2733
MagicBeanDev:github/pr/2725
MagicBeanDev:github/pr/2727
MagicBeanDev:github/pr/2728
MagicBeanDev:github/pr/2729
MagicBeanDev:github/pr/2721
MagicBeanDev:github/pr/2722
MagicBeanDev:github/pr/2715
MagicBeanDev:github/pr/2714
MagicBeanDev:github/pr/2707
MagicBeanDev:github/pr/2703
MagicBeanDev:github/pr/2705
MagicBeanDev:github/pr/2697
MagicBeanDev:github/pr/2679
MagicBeanDev:github/pr/2676
MagicBeanDev:github/pr/2669
MagicBeanDev:docs/what-hookshot-connects-to
MagicBeanDev:fix/hdr-encode-test
MagicBeanDev:fix/main-tests
MagicBeanDev:fix/play-cache-files
MagicBeanDev:perf/game-barrier-order
MagicBeanDev:feat/heap-lazy
MagicBeanDev:feat/texture-detail
MagicBeanDev:perf/astc-opaque-bc1
MagicBeanDev:fix/fs-dir-lock
MagicBeanDev:fix/framegen-native-kept
MagicBeanDev:fix/update-no-downgrade
MagicBeanDev:port/k10-gcn-f16-mirrors
MagicBeanDev:fix/framegen-mult-cap
MagicBeanDev:chore/release-as-2.1.1
MagicBeanDev:perf/pcache-load-switch
MagicBeanDev:fix/prepare-progress-log
MagicBeanDev:fix/update-closes-stale-game
MagicBeanDev:fix/hdr-scaled-sampled
MagicBeanDev:perf/first-start-preset
MagicBeanDev:perf/zero-upload
MagicBeanDev:fix/vcore-escape-2s
MagicBeanDev:test/fake-gh-atomic
MagicBeanDev:perf/batch-idle-flush
MagicBeanDev:test/finish-stub-a
MagicBeanDev:chore/record-2.1.0
MagicBeanDev:fix/main-red-tests
MagicBeanDev:fix/test-display-startup-driver-cache
MagicBeanDev:feat/mesh-shader-mode
MagicBeanDev:fix/menu-probes-at-startup
MagicBeanDev:fix/fps-counter-classic
MagicBeanDev:fix/test-log-batch-flake
MagicBeanDev:fix/narrow-screen-hud
MagicBeanDev:ci/prereleases
MagicBeanDev:release/v2.0.15
MagicBeanDev:hotfix/v2.0.15
MagicBeanDev:fix/present-ahead-replay
MagicBeanDev:release/v2.0.13
MagicBeanDev:fix/bracket-paths-2013
MagicBeanDev:fix/hdr-overlays-2013
MagicBeanDev:fix/fps-counter-scale-2013
MagicBeanDev:fix/stick-seen-at-rest
MagicBeanDev:hotfix/menu-fits-screen
MagicBeanDev:fix/f1-always-opens-menu
MagicBeanDev:fix/ini-keep-lines
MagicBeanDev:claude/cranky-pike-7cf247
MagicBeanDev:perf/texture-answer-flat
MagicBeanDev:fix/stamp-test-modules
MagicBeanDev:fix/appimage-fonts
MagicBeanDev:fix/display-awake
MagicBeanDev:release/v2.0.12
MagicBeanDev:fix/discord-windows
MagicBeanDev:rc/v2.0.12-tests
MagicBeanDev:fix/dlss-keep-feature
MagicBeanDev:rc/v2.0.12
MagicBeanDev:button-icons
No reviewers
Labels
Clear labels
accessibility
Barrier affecting people with disabilities
autorelease: pending
autorelease: tagged
bug
Something isn't working
dependencies
Pull requests that update a dependency file
documentation
Improvements or additions to documentation
duplicate
This issue or pull request already exists
enhancement
New feature or request
github_actions
Pull requests that update GitHub Actions code
good first issue
Good for newcomers
help wanted
Extra attention is needed
invalid
This doesn't seem right
question
Further information is requested
testing wanted
wontfix
This will not be worked on
No labels
accessibility
autorelease: pending
autorelease: tagged
bug
dependencies
documentation
duplicate
enhancement
github_actions
good first issue
help wanted
invalid
question
testing wanted
wontfix
Milestone
Clear milestone
No items
No milestone
Assignees
Clear assignees
No assignees
2 participants
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".
No due date set.
Dependencies
No dependencies set
Reference
MagicBeanDev/Hookshot!2151
Loading…
Reference in a new issue
No description provided.
Delete branch "Ori-Jakob/assignable-buttons"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
The remake has one item button, ZR, so using a second item in the middle of a fight means opening the quick-switch menu
first. This adds an option that puts an item on each of A, B, X and Y, used while a shoulder button is held, much like the
C buttons of the N64 game.
What it does
plays as before: its input untouched, and its own layouts loaded (see Layouts below).
shoot. The hotkey no longer opens the quick-switch menu; the other shoulder button still does.
The same press again takes it off. An item is on one button at a time; child and adult Link have four each.
style of the ZR box's hint (which now shows the other shoulder). While the hotkey is held the diamond grows over the ZR
box's place, no longer faded, with each item's button under it; the ZR box and its hint fade out meanwhile.
The ZR item keeps its gold box, with the icon, when it is also on a face button. Both menus show an Assign guide:
hotkey + the four face buttons.
"Shoot Arrow R2, L1 + △", "Throw RT, LB + B".
with ZL and ZR alone, as the game already allows, and the tab bar shows ZL and ZR.
item_buttons.iniin its slot folder (save/<user id>/slot_NN/), written onlywhen the game saves, into the slot it saves to. Choosing items changes them in memory, so quitting without saving
loses the change, as with the ZR item. When a save loads, items it does not own or cannot use at that age (or that
the file names wrongly) come off their buttons, and a new game starts with none. The game's own save files are
untouched, and the game never reads or lists the extra file.
How it works
host/oot_item_buttons.c: the controller filter, called inhost/nn_hid.cafter the frame step (its step,ib_step,takes one poll of the buttons and what the hooks publish, so
tests/test_item_buttons_filter.pyruns it without thegame); the item slot's answers; assigning in both menus; the action guide; each save's ini file.
host/oot_item_buttons_hud.c: the HUD's four boxes and hint.The game is hooked in ten functions by
nx2/oot_patches.py(IB_HOOKS), in the lift of the supported build(e0f3ccfa, the one
make_game.pybuilds) only.f_83323ca0f_80f89630f_80594ab0PlayerComponent::calcVirtualKeysf_8019b6e0PouchMgr::getAssignableItemf_81231c00PouchMgr::setAssignableItemf_833e98f0ShortCutSlideList::decidef_802f1380Screen::calc, after the screen's updatef_833ea110ShortCutSlideListControl::onBindItemf_83415d20UIPlayerStatusScreen::initLayoutf_80aa2ad0ScreenBase::~ScreenBaseThe ZR slot's getter answers the face button's item only at seven return addresses: Link's item selector, the bottle
nodes and the shooter lookups (one of them a helper the aiming reticle also calls, so during a use the reticle counts
the face item's ammo); and to
ItemBtn::setItemwhile it gives one of the HUD's four boxes its item, so a bow boxgets the bow's icon. Every other caller sees the real ZR item. The host never writes the slot, so the save keeps the
game's ZR item.
Saves:
host/nn_fs.c'sOpenFiletellshos_oot_ib_save_fileeach file the game opens. A slot'sprogress.savopened for reading is a load of that slot (its
item_buttons.iniis read); opened for writing, a save (the items inmemory go to that slot's file, which is removed when there are none). The slot is taken from that call, because a
save often goes elsewhere than the slot loaded: a manual save to the one manual-save slot, an autosave to the oldest
autosave's. On the first tick of play after a load (hook 1) the current age's items are checked with the game's own
quick-switch test (
PouchMgr::canListInShortCutover the pouch's ItemArray slots, plus the Items page's rule forMagic Beans at 0); the other age's wait, unanswered, until the game is at that age. The title screen coming up with
no load before the next tick of play or save is a new game: no items. This runs with the option off too, so a save
keeps the items it was loaded with; with none it is one
removeof a file that is not there. The file is written asprogress.savis opened for writing, where the slot is known: a save that fails after the open leaves it newer thanthe save, which the check at the next load makes harmless.
Layouts:
nx2/item_buttons_layout.pychanges four layouts (PlayerStatus_00,ShortCut_01,MenuOverlay_00,PaItemMenuBtn_00) at build time, from the player's own RomFS, with the game's own parts (PaItemBtn_00,PaGuideKey_00). The archive keeps the game's size and every other layout keeps its place and bytes, so the prompts'runs still fit.
host/prompts.cserves it in place of the game's only when its stamp is there and the option is onas the game starts (read from
hookshot_options.ini, as the prompt face is); with the option off the game loads itsown archive. Turned on while the game runs, the buttons work at once, and the HUD's boxes, the Assign guides and the
Items page's icons come with the next start (the row's hint says so). A change of button prompts while the game runs
(#2366) writes the Talk ring's and the ocarina hint's runs over whichever archive was served, and puts them back from
it. Nothing from the game is in the repository. If the step cannot run (no
zstandard), the buttons still work by the code (not run that way), withoutthe HUD's boxes and hint, the Assign guides and the Items page's button icons; with the hotkey on R the ZR box's hint
then keeps the game's R.
The four copies: the game's layout archives hold each layout four times (
ZeldaNotesthree). The changed layoutsare larger, so three copies go into the room of four and the fourth entry is removed. Nothing reads a copy other than
the one a name resolves to.
engine::ui::Layout::load(f_8045a0e0) asks the archive for<name>.buil, andsead::SharcArchiveRes's lookup returns one fixed entry per name (every entry's collision index is 1).LayoutArchiveRes::parse(f_807978c0) relocates every entry once as the archive loads, and every instance of alayout points into the copy its lookup returned: the Items page's 24 slots come from one. The by-index read
(
getFileFastImpl_) is called only fromparseand from a walk over shader archive entries. On the generatedarchive every name resolves, the three copies are equal and each on bytes of its own, and the 612 entries of the
layouts it leaves keep their offsets and bytes. Why the archives have four copies is not known.
The comma and plus in the action guide:
nx2/prompt_assets.pycopies the two glyphs from the game's font into theserved prompt font. When the font served lacks them (the game's own button icons, or a mod's font), the guide shows
ZR, a gap, then the hotkey and the face button side by side.
A change of button prompts while the game runs (#2366) rewrites the button codes in each text to codes of the new face.
The control guide finds ZR's entry, and the tab bar checks its glyphs, by the button a code shows
(
hos_prompts_game_code,ib_same_glyph), not byte for byte, so the combo still shows after a change.The two options are ids 12 and 13 of the existing
option()query, as 11, 16 and 17 are:vk_api.his part of theshader cache's stamp, so it is unchanged and
MKWVK_API_VERSIONis not bumped.Testing
python -I -B tests/run_all.pyon Windows: 370 passed, 15 failed, 73 skipped. The same 15 fail onmain(692922d9)on this machine (360 passed). Each commit was run the same way, and none adds a failure. New checks:
test_item_buttons_{filter,hooks,hud,ini,layout,live_prompts,menu,saves,serve}.pyandtest_prompt_guide_marks.py;test_input_background.py,test_oot_menu_mouse.py,test_prompt_font_bind.pyandtest_vcore_fs_open.pywereextended.
main692922d9(#1990's second binding and paddles, #2324's per-file patching): the ten hooks go inthrough
patch_filewith the others; in the game, the HUD's boxes, the bow from L1 + ○ with its guide, the Itemspage and the quick-switch menu as before.
mainwith #2366: an adult save loaded and checked; the HUD at rest and with the hotkeyheld; the bow drawn from L1 + ○, its guide "Shoot Arrow R2, L1 + ○"; the Items page and the quick-switch menu with
their icons and Assign guides; the pages turned with ZL and ZR. Then the prompts changed from PS5 to Xbox and back
while the game ran: the HUD's icons and hint, the bow's guide ("Shoot Arrow RT, LB + B"), the Items page's tabs, icons
and Assign guide all followed. On this PC that change needed a local fix to
main'shos_prompts_glyph, which neverfinds the icon font here (the game keeps each font decrypted at its stream's start, with the file's length as the
stream's size); the fix is not part of this PR.
then turned on over the control bridge (
scripts/hookshot_ctl.py option set item_assignable_buttons 1): the bow drawnfrom L1 + ○ with its guide combo, the pause menu's ZL and ZR tabs and light-blue slots, and, as expected, no HUD boxes,
Assign guides or slot icons. With it on at the start, the HUD's boxes as before.
Assigning and clearing in both menus; bombs, bow and hookshot used from the face buttons; ZR on an item drawn with
the hotkey; the action guides; the pause menu's page turning; the assignments surviving a restart.
not owned, a duplicate, an unknown name: only the owned adult items kept); an assignment changing no file; a manual
save writing only the slot it went to; a load from the pause menu reading that slot's file and checking it after the
load; a child save (Bottom of the Well) checked, with the Deku Nut thrown, the Deku Stick drawn and the Slingshot
aimed from the face buttons; a new game starting with none, its autosave removing the file of the slot it overwrote.
menu. Using and assigning with R is covered by the filter's tests, not by a run in the game.
windows.hshim'sMoveFileExAand SRW locks, ashos_main.candnn_fs.cdo);the mouse on the new guides in the game (covered by
test_oot_menu_mouse.py). fps not measured. With the feature on,each frame runs the filter, Link's input hook, the HUD's four boxes and a check after each screen's update; with it
off most of these return at once.
Known limits
picture, left as it is.
Screenshots
HUD (PS5 prompts). At rest: the small, half-transparent diamond with L1 over it, and R1 over the ZR box. With L1 held: grown over the ZR box's place, each item with its button.
Action guides while an item used from a face button is out, PS5 and Xbox.
Quick-switch menu (Xbox prompts, hotkey LB, the menu on RB): LB + A moves the bomb from B to A, the same press takes it off.
Items page (PS5 prompts): the button icons on the light-blue slots, ZL and ZR on the tab bar (L2 and R2 here), the Assign guide; R2 turns to the System page, whose controller picture is the game's own (see Known limits).
Items page (Xbox prompts): LB + A puts the Deku Nut, the ZR item, on A (it stays gold) and takes A off the bow; the same press takes it off; LB + Y puts a bottle on Y and takes Y off the hammer.
Controls tab: the two rows and their help.
Feature off (PS5 prompts): the Items page as the game has it...
...the pages turning with R1 and L1, and the quick-switch menu.
🤖 Generated with Claude Code
This is a big, careful piece of work: the filter is a pure step function with its own tests, the hooks are in new files, the save-side file is handled conservatively, and the existing checks it touches (
test_vcore_fs_open,test_input_background,test_prompt_font_bind,test_oot_menu_mouse) were extended rather than loosened. CI is green and it merges cleanly onto main. At 7.9k lines I focused on what runs for players who never turn the option on, and on the host's file and input paths:host/oot_item_buttons.c(the filter, the hooks' entry points, the save-slot file), thenn_hid.c/nn_fs.c/oot_menu.c/prompts.cchanges,nx2/oot_patches.py's hooks, and the menu rows. I readoot_item_buttons_hud.cand the layout writer (item_buttons_buil.py/_rules.py/_layout.py) only in outline.What I checked and found fine:
hos_oot_ib_filterreturns the buttons as they came (host/oot_item_buttons.c:251).hos_oot_ib_playerreturns unless a use is still ending, and the slot getter hook returns 0 unless an item is being answered, so the game's own ZR path is untouched.t.optionfollows the precedent of 11, 16 and 17, so novk_api.hchange or API bump is needed.ib_slot_ofonly accepts exactlysave:/slot_NN/progress.sav. The ini reader bounds its lines and passes over unknown keys and names. The write goes through.tmpandMoveFileExA, and the game exe's UTF-8 manifest makes the narrowfopen/MoveFileExAcalls safe for non-ASCII folders.zstandard, which the pipeline already has; the fontTools use stays insidenx2/prompt_assets.pyas CONTRIBUTING requires. I didn't see anything from the game committed: the layout tests build synthetic.builfiles.should fix / question
host/prompts.c:1126-1144(build_layouts): the rebuiltitem_buttons/Common.blarcis served whenever its stamp exists, andmake_game.pybuilds it for everyone withzstandard, that is, every normal build. So every player runs the game with the four rewritten layouts, even with Item assignable buttons off. That includes the renumbered panes inShortCut_01, and the archive with one of the four copies of each changed layout removed (nx2/item_buttons_layout.py's docstring: "three copies of it go into the room of the four … and the fourth entry goes"). The PR says "With it off the game plays as before", which is true for input, but the UI data every player loads changes, and that has been played only on one Windows/NVIDIA machine, mostly with the feature on. Two asks:PlayerStatus_00,ShortCut_01,MenuOverlay_00orPaItemMenuBtn_00, what happens without it? If you've confirmed it's never reached, please say how in the description.item_buttons/only when the option was on at start (the HUD's boxes and guides would then show after a restart, like the other restart rows), or at least add aHOS_switch that serves the game's own archive. Then a layout problem found later can't reach players who never asked for the feature, and has a one-line workaround for those who did.minor
host/oot_item_buttons.c:638-648: the slot'sitem_buttons.iniis written whenprogress.savis opened for writing, so a save that fails after the open still updates the ini. That's harmless today (the items are only checked against the save at load), but worth a comment, since the function's comment reads as if it follows the save.minor Not tested on Linux or the Steam Deck. It compiles there (the appimage job is green), but since the layout change reaches every player (see above), one boot to the title and into play on Linux with the option off would be worth doing before merging.
Thanks for the careful read. Going through your points:
Every player loads the changed layouts. You're right: "with it off the game plays as before" was only true for input.
host/prompts.cnow servesitem_buttons/Common.blarconly when Item assignable buttons is on as the game starts (read fromhookshot_options.ini, asbutton_promptsis). With it off, the game loads its own archive, as on main. Turned on while the game runs, the buttons work at once, and the HUD's boxes, the Assign guides and the Items page's icons come with the next start; the row's hint says so. Turning it off and restarting is the workaround if a layout problem turns up.I checked both cases in the game. Off at the start, the log says the game's own layouts were loaded. With the option then turned on over the control bridge, the bow works from L1 + ○ with its guide, the pause menu shows ZL and ZR, and the HUD has no boxes. On at the start, the boxes are there.
tests/test_item_buttons_serve.pycovers the option off, on and missing, and checks that the key read is the one the menu writes.The fourth copy. As far as I can find, nothing reads a copy other than the one a name resolves to:
engine::ui::Layout::load(0x710043a0e0, liftedf_8045a0e0) formats"%s.buil"and asks the archive for that path; parts go through the same call.sead::SharcArchiveRes::convertPathToEntryIDImpl_is a binary search by hash, and every entry of the game's layout archives has collision index 1 in its name word, so a name resolves to one fixed entry. Following that lookup in the game's Common,PlayerStatus_00resolves to its first copy,MenuOverlay_00to its second andShortCut_01to its third.LayoutArchiveRes::parse(f_807978c0) walks every entry by index once, as the archive loads, to relocate it. Each instance keeps a pointer into the copy its lookup returned. The Items page shows 24PaItemMenuBtn_00slots from an archive with 4 copies of it.getFileFastImpl_) is called only fromparseand from a walk over shader archive (FSHA) entries.ZeldaNotes. (Common,TitleandBoothave four.)Why the archives have four copies, I don't know. This is in the description now.
The ini written at the open. Agreed. It's written when
progress.savis opened for writing because that's where the slot is known. So a save that fails after the open leaves the ini newer than the save; the next load checks the items against that save, so nothing it doesn't own stays on a button. The function's comment says so now.Also worth saying: with the option off,
hos_oot_ib_save_filestill runs at each save and load of a slot'sprogress.sav, so a save keeps the items it was loaded with. With no items, that's oneremoveof a file that isn't there, and a log line.CI and Linux. A small correction: only the title check has run on this PR. The Windows builder, Linux AppImage, DLSS back end and game-free regression workflows are waiting for a maintainer's approval. Could you approve them? I don't have a Linux machine or a Steam Deck to boot it on. With the change above, players who leave the option off load the game's own archive on every platform.
CI has never run here (first-contributor workflows need maintainer approval), so none of the 7.9k lines has been built by CI yet. I said earlier that CI was green, and that was wrong.
What I found:
item_buttons/Common.blarcis only served when the option is on at start (host/prompts.c:1150).IB_HOOKSinnx2/oot_patches.py). They use hard-coded return addresses and vtables for build e0f3ccfa, and several run with the option off: the per-frame screen-update hook, the slot getter, and the save hook inhost/nn_fs.c:403, which writes or removesitem_buttons.iniat every save. Each gates its changes on the option, and the ini write is atomic through a .tmp file.prompt_assets.pyadds a comma and a plus glyph to the prompt font, falling back cleanly if that fails.Whether to own ten build-specific hooks is the maintainers' call.
This now conflicts with main in
nx2/oot_patches.py,tests/test_prompt_font_bind.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.Branch has been rebased. It is ready to look at :)
The points from my earlier reviews are fixed on this head.
item_buttons/Common.blarcis only served when the option is on at the start (b5af3839,ae241537). The ini written when the save opens is now documented (e3b82bb7). The question about the fourth layout copy is answered in your reply.Main has moved 92 commits since your base, and the branch now conflicts in
gpu/vk/vk_menu.cpp,vk_menu.h,host/nn_hid.c,host/prompts.c,nx2/oot_patches.py,nx2/prompt_assets.pyandtests/test_prompt_font_bind.py. Other open PRs also changenn_hid.c,prompts.candoot_patches.py, for example #2304 and #2755. I couldn't run the tests because of the conflicts, and CI has still never run here.Before you spend time on another rebase, the maintainers should decide whether they want to own the ten hooks in
IB_HOOKS. They hard-code return addresses and vtables for this build, and several run with the option off. After a rebase, please also add readme lines for the new Controls rows. #2105's check needs them. Linux and the Steam Deck are still untested.