feat(mods): a Mods tab, merged resource size tables, ExeFS patches and Atmosphere cheats #3

Open
snaildev99 wants to merge 6 commits from snaildev99/Hookshot:feat/mods-exefs-patches into main
First-time contributor

Mods that change the game's code (ExeFS patches: .ips, .pchtxt) do nothing on Hookshot, two mods that each ship a resource size table cannot load together, and there is no place in the game to see or order the mods. sirhumanbean's draft for all of this was pull request 2478 on GitHub and went with the issues and pull requests there. This brings it back onto current main, with the fixes from its review on Longshot.

Built from this branch and played on Hookshot by hand (below, with two pictures); the same code was played more widely on Longshot before the port.

What changed

sirhumanbean's two commits are kept under his name; the commits after them are the port and the review's fixes. It is squashed at the merge, so please credit him there (Co-authored-by: hckrman101).

  • Mods (host/mods.c, host/restbl.c, host/zs.c): the mods' order and switches are read from hookshot_mods.txt; one size table is served, merged from every enabled mod's and raised for each file a mod serves (written to modcache/rsizetable.zs), so mods that each grow files load together; the menu gets the list (hos_mod_info, hos_mod_edit, hos_mods_changed).
  • ExeFS patches (nx2/exefs_patches.py, nx2/image.py, nx2/make_game.py): mods/<name>/exefs/*.ips and *.pchtxt are read when the game is built and written into main before it is relocated and lifted; a write into the padding after the code makes it code (a code cave). They are part of the lift's key, and exefs_patches.txt in the game folder lists what the build applied.
  • Cheats (host/cheats.c): Atmosphere cheats of the mods that are on; data cheats are switched in the menu.
  • Menu (gpu/vk/vk_menu.cpp): a Mods tab, shown when there are mods: each mod On or Off, Enter and Up or Down to move it, its cheats under it. A change asks for a restart as a row that takes one does.
  • The port to current main: main took API version 22 for wide_cover, so the Mods tab's five functions come after it as version 23 (gpu/vk/vk_api.h, host/rx_backend.c, can_mods(), tests/test_mods_menu.py). Six conflict blocks in five files; nothing of main's was dropped.
  • From the review on Longshot: no size table for the prompts when the merged one is served; a mod that is off is not told to wait for a build; a Restart or Quit waiting for its second press is cleared on the Mods tab; a mod file whose path differs in case from the game's takes the game's entry; bytes a patch writes into a relocated word are left out and counted in exefs_patches.txt; the compact bar shows the Mods tab's full page.
  • Tests: tests/test_exefs_patches.py, test_cheat_vm.py, test_mods_menu.py, test_mods_sizes.py, and additions to test_mods_size_table.py and test_fs_links.py. Docs: the README's Mods section, docs/development.md.

The lift fix a code-cave routine needs is already on main (commit 56ca2f89: a function with code below its entry starts at its entry).

How it was tested

Game-free, on this branch (Windows 11, llvm-mingw; the full run was on main @ 34389c34, the branch has since been rebased onto f532c100, three commits of docs, and the mod checks pass again there):

  • python -I -B tests/run_all.py: 536 passed, 38 skipped, 8 failed. The same eight fail on main @ 34389c34 on this PC (532 passed there): test_updater_asset_digest, test_builder_details_hitbox, test_builder_dlss_update, test_builder_upscaler_parts, test_controls_key_slots, test_portable_archive, test_portable_builder_startup, test_portable_builder_home.
  • The mod checks by themselves pass: test_mods_menu, test_exefs_patches, test_cheat_vm, test_menu_restart, test_menu_tune (test_mods_sizes, test_mods_size_table and test_fs_links skip their file parts off Linux).

In the game on Hookshot, this branch @ b18ab96c (v2.2.0-rc.1-124-gb18ab96c), Windows 11, RTX 3090, by hand, with a mod that has RomFS files, a size table and six code patches in one .ips (https://github.com/snaildev99/OoT-Character-Select):

  • A build with nx2/make_game.py, the mod in mods: 13 min 8 s, compiled in 10 min 44 s, 0 failed.
  • The Insert menu's Mods tab lists the mod, On (first picture).
  • The mod's own page opens from the game's menu, with its fourth tab and its character picker: all of it comes from the code patches (second picture). A character picked there is the player in the field.
  • The mod switched Off on the tab: closing the menu asks to restart; Restart now starts the game again with the mod off. Its RomFS files are then not served, its patch stays built in as the log says, and the game runs (60 fps).
  • No crash in either start.
exefs: code        OoT Character Select/exefs/E0F3CCFAF50065AE4EA01EB5AEB13B1A45DA3348.ips (4935 bytes)
exefs: main's code reaches 0x3c47000 now (a code cave)
exefs: 4935 bytes of main patched by 1 files
compiled in 633s, 0 failed
the build took 0:13:08, compiled in 0:10:44

mods: OoT Character Select: 534 RomFS files (16 replace the game's, 518 added), ExeFS patches
mods: OoT Character Select: ExeFS E0F3CCFAF50065AE4EA01EB5AEB13B1A45DA3348.ips: built into the game
mods: resource size table: 1 tables of mods merged (90 entries), 15 entries raised for mod files (0 added files estimated), served from modcache, 504 ms
mods: OoT Character Select switched off (at the next start)
menu: restart now, to apply the mods
(the next start) mods: OoT Character Select: off (the Mods tab)
mods: 1 ExeFS patch files built into the game belong to mods that are off or gone: they stay in it until it is built again
vi: 4725 frames presented (60.0 fps over the last 5.0 s, interval 1)

this branch @ b18ab96c on Hookshot: the Insert menu's Mods tab with the mod listed

this branch @ b18ab96c on Hookshot: the mod's Characters page in the game's own menu, which its code patches add

In the game, the same code before this port, on Longshot (its pull request 134, where the pictures and the log lines are: https://git.magicbean.dev/MagicBeanDev/Longshot/pulls/134):

  • Windows 11, RTX 3090, by hand, with a mod that has RomFS files, a size table and six code patches in one .ips (https://github.com/snaildev99/OoT-Character-Select, v0.1.3): the Mods tab lists it, the mod's menu page, its character swap, its cloth and voice hooks all work, 60 fps, the size table's merge takes 56 to 111 ms at the start.
  • The same mod as eight folders at once: four size tables merged, six .ips files for one build id built in (exefs: 4935 bytes of main patched by 6 files), a mod switched Off and On from the tab with Restart now, a mod moved.
  • Linux (Arch, RTX 4090) and Windows (RTX 5090) by two other testers: a full build with the mod, the tab, a switch and a move kept over a restart.

Not tested anywhere: a .pchtxt mod, a cheat file, the mouse on the tab, the compact bar with Mods current, a patch into a relocated word. Not tested on Hookshot itself: several mods at once (on Longshot only), and Linux.

For mod authors

A patch routine that is entered with bl has to leave with ret. One that jumps back into its caller runs the caller's tail twice once the code is lifted; the console and the emulators do not show it.

Risk

Without a mods folder nothing of this runs (hos_mods_init returns first). With mods, the size table the game gets is the merged one, where before it was the last mod's own. A build with ExeFS patches lifts patched code; without such patches the lift and its key are as before. Reverting the merge is enough.

🤖 Generated with Claude Code

Mods that change the game's code (ExeFS patches: `.ips`, `.pchtxt`) do nothing on Hookshot, two mods that each ship a resource size table cannot load together, and there is no place in the game to see or order the mods. sirhumanbean's draft for all of this was pull request 2478 on GitHub and went with the issues and pull requests there. This brings it back onto current `main`, with the fixes from its review on Longshot. Built from this branch and played on Hookshot by hand (below, with two pictures); the same code was played more widely on Longshot before the port. ## What changed sirhumanbean's two commits are kept under his name; the commits after them are the port and the review's fixes. It is squashed at the merge, so please credit him there (`Co-authored-by: hckrman101`). - **Mods** (`host/mods.c`, `host/restbl.c`, `host/zs.c`): the mods' order and switches are read from `hookshot_mods.txt`; one size table is served, merged from every enabled mod's and raised for each file a mod serves (written to `modcache/rsizetable.zs`), so mods that each grow files load together; the menu gets the list (`hos_mod_info`, `hos_mod_edit`, `hos_mods_changed`). - **ExeFS patches** (`nx2/exefs_patches.py`, `nx2/image.py`, `nx2/make_game.py`): `mods/<name>/exefs/*.ips` and `*.pchtxt` are read when the game is built and written into `main` before it is relocated and lifted; a write into the padding after the code makes it code (a code cave). They are part of the lift's key, and `exefs_patches.txt` in the game folder lists what the build applied. - **Cheats** (`host/cheats.c`): Atmosphere cheats of the mods that are on; data cheats are switched in the menu. - **Menu** (`gpu/vk/vk_menu.cpp`): a **Mods** tab, shown when there are mods: each mod On or Off, Enter and Up or Down to move it, its cheats under it. A change asks for a restart as a row that takes one does. - **The port to current `main`**: `main` took API version 22 for `wide_cover`, so the Mods tab's five functions come after it as version 23 (`gpu/vk/vk_api.h`, `host/rx_backend.c`, `can_mods()`, `tests/test_mods_menu.py`). Six conflict blocks in five files; nothing of `main`'s was dropped. - **From the review on Longshot**: no size table for the prompts when the merged one is served; a mod that is off is not told to wait for a build; a Restart or Quit waiting for its second press is cleared on the Mods tab; a mod file whose path differs in case from the game's takes the game's entry; bytes a patch writes into a relocated word are left out and counted in `exefs_patches.txt`; the compact bar shows the Mods tab's full page. - **Tests**: `tests/test_exefs_patches.py`, `test_cheat_vm.py`, `test_mods_menu.py`, `test_mods_sizes.py`, and additions to `test_mods_size_table.py` and `test_fs_links.py`. Docs: the README's Mods section, `docs/development.md`. The lift fix a code-cave routine needs is already on `main` (commit 56ca2f89: a function with code below its entry starts at its entry). ## How it was tested Game-free, on this branch (Windows 11, llvm-mingw; the full run was on `main` @ 34389c34, the branch has since been rebased onto f532c100, three commits of docs, and the mod checks pass again there): - `python -I -B tests/run_all.py`: 536 passed, 38 skipped, 8 failed. The same eight fail on `main` @ 34389c34 on this PC (532 passed there): `test_updater_asset_digest`, `test_builder_details_hitbox`, `test_builder_dlss_update`, `test_builder_upscaler_parts`, `test_controls_key_slots`, `test_portable_archive`, `test_portable_builder_startup`, `test_portable_builder_home`. - The mod checks by themselves pass: `test_mods_menu`, `test_exefs_patches`, `test_cheat_vm`, `test_menu_restart`, `test_menu_tune` (`test_mods_sizes`, `test_mods_size_table` and `test_fs_links` skip their file parts off Linux). In the game on Hookshot, this branch @ b18ab96c (`v2.2.0-rc.1-124-gb18ab96c`), Windows 11, RTX 3090, by hand, with a mod that has RomFS files, a size table and six code patches in one `.ips` (https://github.com/snaildev99/OoT-Character-Select): - A build with `nx2/make_game.py`, the mod in `mods`: 13 min 8 s, compiled in 10 min 44 s, 0 failed. - The Insert menu's Mods tab lists the mod, On (first picture). - The mod's own page opens from the game's menu, with its fourth tab and its character picker: all of it comes from the code patches (second picture). A character picked there is the player in the field. - The mod switched Off on the tab: closing the menu asks to restart; Restart now starts the game again with the mod off. Its RomFS files are then not served, its patch stays built in as the log says, and the game runs (60 fps). - No crash in either start. ``` exefs: code OoT Character Select/exefs/E0F3CCFAF50065AE4EA01EB5AEB13B1A45DA3348.ips (4935 bytes) exefs: main's code reaches 0x3c47000 now (a code cave) exefs: 4935 bytes of main patched by 1 files compiled in 633s, 0 failed the build took 0:13:08, compiled in 0:10:44 mods: OoT Character Select: 534 RomFS files (16 replace the game's, 518 added), ExeFS patches mods: OoT Character Select: ExeFS E0F3CCFAF50065AE4EA01EB5AEB13B1A45DA3348.ips: built into the game mods: resource size table: 1 tables of mods merged (90 entries), 15 entries raised for mod files (0 added files estimated), served from modcache, 504 ms mods: OoT Character Select switched off (at the next start) menu: restart now, to apply the mods (the next start) mods: OoT Character Select: off (the Mods tab) mods: 1 ExeFS patch files built into the game belong to mods that are off or gone: they stay in it until it is built again vi: 4725 frames presented (60.0 fps over the last 5.0 s, interval 1) ``` ![this branch @ b18ab96c on Hookshot: the Insert menu's Mods tab with the mod listed](https://git.notrexed.dev/attachments/9d30710e-4f73-44ee-bf44-5a08c20f54e1) ![this branch @ b18ab96c on Hookshot: the mod's Characters page in the game's own menu, which its code patches add](https://git.notrexed.dev/attachments/fcdd3898-3e61-4b6d-814e-43db50b41a3c) In the game, the same code before this port, on Longshot (its pull request 134, where the pictures and the log lines are: https://git.magicbean.dev/MagicBeanDev/Longshot/pulls/134): - Windows 11, RTX 3090, by hand, with a mod that has RomFS files, a size table and six code patches in one `.ips` (https://github.com/snaildev99/OoT-Character-Select, v0.1.3): the Mods tab lists it, the mod's menu page, its character swap, its cloth and voice hooks all work, 60 fps, the size table's merge takes 56 to 111 ms at the start. - The same mod as eight folders at once: four size tables merged, six `.ips` files for one build id built in (`exefs: 4935 bytes of main patched by 6 files`), a mod switched Off and On from the tab with Restart now, a mod moved. - Linux (Arch, RTX 4090) and Windows (RTX 5090) by two other testers: a full build with the mod, the tab, a switch and a move kept over a restart. Not tested anywhere: a `.pchtxt` mod, a cheat file, the mouse on the tab, the compact bar with Mods current, a patch into a relocated word. Not tested on Hookshot itself: several mods at once (on Longshot only), and Linux. ## For mod authors A patch routine that is entered with `bl` has to leave with `ret`. One that jumps back into its caller runs the caller's tail twice once the code is lifted; the console and the emulators do not show it. ## Risk Without a `mods` folder nothing of this runs (`hos_mods_init` returns first). With mods, the size table the game gets is the merged one, where before it was the last mod's own. A build with ExeFS patches lifts patched code; without such patches the lift and its key are as before. Reverting the merge is enough. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Mods that each grow files (HD texture packs) could not be combined: only one
resource size table was used. Hookshot now merges every enabled mod's table
into the game's at start (the button prompts' entries kept) and raises the
entry of every file a mod serves to fit its size (host/mods.c, host/restbl.c,
host/zs.c). #2270's merge of the prompts' entries into the winning mod's table
stays as the fallback when no merged table can be made.

The Insert menu gets a Mods tab (shown only when there are mods): each mod on
or off and its place in the list, kept in hookshot_mods.txt and applied at the
next start (the restart question names them), and the cheats of the enabled
mods, switched at once.

ExeFS patches (IPS, IPS32, IPSwitch .pchtxt) are applied to main's image when
the game is built (nx2/exefs_patches.py, nx2/image.py), so the code is
translated from the patched instructions; a write into the padding after the
code (a code cave) makes it code. exefs_patches.txt in the game folder lists
what a build applied; the Mods tab says when a patch waits for a build.

Atmosphere cheat-code files (separate from the Debug tab's built-in cheats)
run in a cheat VM after dmnt's (host/cheats.c), every presented frame, on
guest memory the game was given; cheats that write the game's code are
listed as not supported.

The list, order and switches, the size table's merge, the patch formats and
the cheat VM follow valentderah/totk-pc-recomp (MIT), whose notice is kept in
host/restbl.h and nx2/exefs_patches.py.

Co-authored-by: Valiantsin Dzerakh <[email protected]>
Co-authored-by: conongnang <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The control bridge's bridge_fill names each MenuTab and checks the count: with the Mods tab
added before Debug, its table needs "mods" there.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
The compact bar (added to main after the Mods tab was written) draws the
selected row of the current tab. The Mods tab has no such rows: its lines come
from the host. With the bar asked for on the Mods tab the full page is drawn.

Co-Authored-By: Claude Fable 5.2 <[email protected]>
- hos_mods_file gives prompts.c no table when the merged one is served.
- A mod that is off: its ExeFS files are not counted as waiting for a build
  (the Mods tab's text and the log's line).
- The Mods tab's texts and longshot_mods.txt's header say Longshot.
- A Restart or Quit waiting for its second press is cleared on the Mods tab.
- The merged size table: a mod file whose path is in another case than the
  game's takes the game's entry (looked up only when the mod's own case
  misses the table).
- exefs_patches.py: bytes into a relocated word are left out and the file's
  note says so; the comment on DT_RELR words says what happens.
- The compact bar on the Mods tab dims the game behind, as the full menu.

Co-Authored-By: Claude Fable 5.2 <[email protected]>
Co-Authored-By: Claude Fable 5.2 <[email protected]>
fix(mods): the port to Hookshot's main: Hookshot's names, and API version 23 for the Mods tab's functions
Some checks failed
PR title / Conventional Commit title (pull_request_target) Has been cancelled
Linux AppImage / appimage (pull_request) Has been cancelled
Linux AppImage / Release asset (pull_request) Has been cancelled
Game-free source regressions / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Has been cancelled
Game-free source regressions / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Has been cancelled
Game-free source regressions / Python source checks (windows-2022, part 1/2) (pull_request) Has been cancelled
Game-free source regressions / Python source checks (windows-2022, part 2/2) (pull_request) Has been cancelled
Game-free source regressions / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Has been cancelled
Game-free source regressions / Vulkan fixtures (lavapipe, validation) (pull_request) Has been cancelled
Game-free source regressions / Workflow lint (actionlint) (pull_request) Has been cancelled
Vulkan back end with NVIDIA DLSS / Windows Vulkan back end with NVIDIA DLSS and AMD FSR 4 (llvm-mingw, link check) (pull_request) Has been cancelled
Windows builder / Hookshot Builder package and setup (pull_request) Has been cancelled
Windows builder / Windows Vulkan back end (llvm-mingw) (pull_request) Has been cancelled
Windows builder / dav1d for Windows (llvm-mingw) (pull_request) Has been cancelled
Windows builder / Release assets (pull_request) Has been cancelled
7d76d216bc
main took version 22 for wide_cover while this was on its branch: the Mods tab's five functions come after it, as
version 23 (vk_api.h, can_mods, tests/test_mods_menu.py). The file of the Mods tab is hookshot_mods.txt and its texts
say Hookshot.

Co-Authored-By: Claude Fable 5.2 <[email protected]>
snaildev99 force-pushed feat/mods-exefs-patches from 7d76d216bc
Some checks failed
PR title / Conventional Commit title (pull_request_target) Has been cancelled
Linux AppImage / appimage (pull_request) Has been cancelled
Linux AppImage / Release asset (pull_request) Has been cancelled
Game-free source regressions / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Has been cancelled
Game-free source regressions / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Has been cancelled
Game-free source regressions / Python source checks (windows-2022, part 1/2) (pull_request) Has been cancelled
Game-free source regressions / Python source checks (windows-2022, part 2/2) (pull_request) Has been cancelled
Game-free source regressions / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Has been cancelled
Game-free source regressions / Vulkan fixtures (lavapipe, validation) (pull_request) Has been cancelled
Game-free source regressions / Workflow lint (actionlint) (pull_request) Has been cancelled
Vulkan back end with NVIDIA DLSS / Windows Vulkan back end with NVIDIA DLSS and AMD FSR 4 (llvm-mingw, link check) (pull_request) Has been cancelled
Windows builder / Hookshot Builder package and setup (pull_request) Has been cancelled
Windows builder / Windows Vulkan back end (llvm-mingw) (pull_request) Has been cancelled
Windows builder / dav1d for Windows (llvm-mingw) (pull_request) Has been cancelled
Windows builder / Release assets (pull_request) Has been cancelled
to b18ab96c94
Some checks are pending
PR title / Conventional Commit title (pull_request_target) Waiting to run
Linux AppImage / appimage (pull_request) Blocked by required conditions
Linux AppImage / Release asset (pull_request) Blocked by required conditions
Game-free source regressions / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Blocked by required conditions
Game-free source regressions / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Blocked by required conditions
Game-free source regressions / Python source checks (windows-2022, part 1/2) (pull_request) Blocked by required conditions
Game-free source regressions / Python source checks (windows-2022, part 2/2) (pull_request) Blocked by required conditions
Game-free source regressions / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Blocked by required conditions
Game-free source regressions / Vulkan fixtures (lavapipe, validation) (pull_request) Blocked by required conditions
Game-free source regressions / Workflow lint (actionlint) (pull_request) Blocked by required conditions
Vulkan back end with NVIDIA DLSS / Windows Vulkan back end with NVIDIA DLSS and AMD FSR 4 (llvm-mingw, link check) (pull_request) Blocked by required conditions
Windows builder / Hookshot Builder package and setup (pull_request) Blocked by required conditions
Windows builder / Windows Vulkan back end (llvm-mingw) (pull_request) Blocked by required conditions
Windows builder / dav1d for Windows (llvm-mingw) (pull_request) Blocked by required conditions
Windows builder / Release assets (pull_request) Blocked by required conditions
2026-11-13 22:11:22 +00:00
Compare
snaildev99 changed title from WIP: feat(mods): a Mods tab, merged resource size tables, ExeFS patches and Atmosphere cheats to feat(mods): a Mods tab, merged resource size tables, ExeFS patches and Atmosphere cheats 2026-11-13 22:37:56 +00:00
Author
First-time contributor

Built from this branch (b18ab96c) and played on Hookshot by hand, Windows 11, RTX 3090: the Mods tab lists the mod, the mod's own menu page and its character swap work (they come from its six code patches), and a mod switched Off from the tab asks for the restart and stays off after it. The build took 13 min 8 s with 0 failed. The log lines and two pictures are in the description, and the draft mark is off. Left untested on Hookshot itself: several mods at once (played on Longshot), Linux, a .pchtxt mod and a cheat file.

Built from this branch (b18ab96c) and played on Hookshot by hand, Windows 11, RTX 3090: the Mods tab lists the mod, the mod's own menu page and its character swap work (they come from its six code patches), and a mod switched Off from the tab asks for the restart and stays off after it. The build took 13 min 8 s with 0 failed. The log lines and two pictures are in the description, and the draft mark is off. Left untested on Hookshot itself: several mods at once (played on Longshot), Linux, a .pchtxt mod and a cheat file.
Commenting is not possible because the repository is archived.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
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
Hookshot/Hookshot-old!3
No description provided.