feat(mods): a Mods tab, merged resource size tables, ExeFS patches and Atmosphere cheats (HumanBean's draft from Hookshot, with the lift fix) #134
No reviewers
Labels
No labels
area/accessibility
area/audio
area/input
area/install
area/menu
area/renderer
area/saves
area/upscaling
beanstalk: approved @ 1697603
beanstalk: approved @ 2c5a62e
beanstalk: approved @ 4fd82b7
beanstalk: approved @ 64d6d09
beanstalk: approved @ 6f91fae
beanstalk: approved @ 70a78f9
beanstalk: approved @ 8015c9a
beanstalk: approved @ 8d361dc
beanstalk: approved @ 9d8356e
beanstalk: approved @ abf2227
beanstalk: approved @ d82f202
beanstalk: approved @ e5f1e40
beanstalk: approved @ ee8ab5f
ci: approved
good first issue
gpu/amd
gpu/igpu
gpu/intel
gpu/nvidia
help wanted
kind
bug
kind
chore
kind
docs
kind
feature
kind
performance
kind
question
needs measurements
platform/linux
platform/macos
platform/steam-deck
platform/windows
preview
priority
critical
priority
high
priority
low
regression
status
blocked
status
confirmed
status
duplicate
status
invalid
status
needs-info
status
wontfix
testing wanted
ci: approved
No milestone
No project
No assignees
5 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
MagicBeanDev/Longshot!134
Loading…
Reference in a new issue
No description provided.
Delete branch "snaildev99/Longshot:feat/mods-exefs-patches"
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?
Type: feat · Risk: medium · Tested on: Windows 11, RTX 3090 (the head before the last rebase); Linux, RTX 4090 (an earlier head) · Needs: Linux, any GPU: one game-free test at this head
Why
Mods that change the game's code (ExeFS patches:
.ips,.pchtxt) do nothing on Longshot, 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. HumanBean's draft for all of this was on Hookshot (https://github.com/NotRexed/Hookshot/pull/2478) and never came over; he asked for it to be brought here.What changed
HumanBean's two commits are ported as they were, under his name; the changes of the port and of the review follow them.
host/mods.creads the mods' order and switches fromlongshot_mods.txt, serves one size table merged from every enabled mod's (host/restbl.c,host/zs.c, written tomodcache/rsizetable.zs), and gives the menu its list (hos_mod_info,hos_mod_edit,hos_mods_changed).nx2/exefs_patches.pyreadsmods/<name>/exefs/*.ipsand*.pchtxtwhen the game is built,nx2/image.pywrites them into main before it is relocated and lifted (a write into the padding after the code makes it code),nx2/make_game.pyputs them in the lift's key and writesexefs_patches.txt.host/cheats.c, Atmosphere cheats of the mods that are on, data cheats switched in the menu.gpu/vk/vk_menu.cpp(draw_mods,mods_tick), shown when there are mods;MkwVkHostgains five functions (gpu/vk/vk_api.h,host/rx_backend.c).draw_tune_bar).What the port changed in HumanBean's commits (7 files conflicted with main):
gpu/vk/vk_api.h,host/rx_backend.c: the five functions come after main'stake_text;MKWVK_API_VERSIONis 24 (the draft's was 18; main took 22 for its text fields and 23 for the settings file's path while this was open), andcan_mods()asks for 24.gpu/vk/vk_menu.cpp: the Mods block sits beforedraw_options;tab_titles(at file scope on main) gains "Mods"; the row handling ofmenu_tickis main's, inside the draft'selsefor the tabs that are not Mods.host/mods.c: main'shos_mods_file(an exact path) with the draft's rule for the size table in it.nx2/build.py:zs.cjoinsZSTD_HOST.tests/test_mods_size_table.py:zs.cbeside the others.README.md: the draft's Mods section with Longshot's names; main's note on tables for several game versions kept. The Mods tab's file islongshot_mods.txt.tests/test_mods_menu.py: version 23 and the new order of the host's functions.tests/test_menu_text_field.py(main's): its harness cutsrestart_valuesout of the menu, so it declares the Mods tab's slot.What the review changed (the last commit):
host/mods.chos_mods_file: no table for prompts.c when the merged one is served.host/mods.cmod_text,exefs_status: a mod that is off says "Its ExeFS patches are not applied while it is off", in the tab and in the log, instead of asking for a build.host/mods.c: "Longshot" in the Mods tab's texts and inlongshot_mods.txt's header.host/mods.cfit_sizes,game_case: a mod file whose path is in another case than the game's takes the game's entry. The game's case is looked up only when the mod's own case misses the table, so a mod whose paths match costs no extra reads at the start.gpu/vk/vk_menu.cppmenu_tick: a Restart or Quit waiting for its second press is cleared on the Mods tab.nx2/exefs_patches.pyclassify: bytes into a relocated word are left out as before, the file's note inexefs_patches.txtnow says how many, and the comment says what happens to DT_RELR words (tests/test_exefs_patches.py, the README).Measurements
Verdict: it works, at a small cost at a start with mods. A mod's code patches run (on main they do nothing), several mods' size tables load together, and the only added time is the size table's merge at a start: 84 ms median with one mod (56 to 258 ms over the starts timed, the largest for a table of 1222 entries), none without a
modsfolder.The table is of
5142c2d7against main @6b7fa356. The head before the last rebase (56fd6f8d) was built and played again (Testing, the second item), and the head now (4f8b975d, on main @6a013efd) differs from it in our commits only by the API version (24) and the host table's order after main'soptions_path; the rebases since changed nothing the merge or the patching runs through, and what they did change is named there.Hardware: RTX 3090, Windows 11
Scene: a child save in Kokiri Forest and Hyrule Field, 2048x1152, 60 fps
Method: one start and about twelve minutes of play by the mod's author, with the mod of the build lines below as the only mod; then a second start; the numbers are
longshot_log.txtlines. The merge's time is from the two starts of this PR's build (the first right after the build, the second with the files read before). main has no merge to time against it, and without amodsfolderhos_mods_initreturns before any of it, so there is no cost when no mods are installed (read in the code, not timed)How to reproduce: a mod with ExeFS patches in
mods,nx2/make_game.pyinto that game folder, start the game6b7fa3565142c2d7exefsfolder is left outThe build and the run are of 3c23346a, the same changes before the rebases onto
6b7fa356(main's commits since touch release and CI files and the README's download links only; the docs commit came after), Windows 11, llvm-mingw:(The second build, after the review's changes:
mods.candvk_menu.cppcompiled again, no warning in either. The first, of the port itself, took 15 min 18 s.)The run's log:
Flags and settings
This PR adds a menu tab, one file in the game folder and two kinds of mod files that are read now; no
RX_*/HOS_*switch and no ini key.longshot_mods.txt(game folder)mods/<name>/exefs/*.ips,*.pchtxt.pchtxtfile: two mods work for one tester on Windows (Serfrost, in Testing), not tried by memods/<name>/cheats/<build id>.txt{Master}codes were not triedTesting
Tested:
python -I -B tests/run_all.pyon Windows 11 at4f8b975d(rebased onto main @6a013efd, with llvm-mingw'sbinon the path): 516 passed, 74 skipped, 21 failed, the same twenty-one as main @6a013efdon this PC in the same run (512 passed there):test_appimage_player_build,test_auto_handheld,test_cpp_preflight,test_crash_dialog,test_env_on,test_fpx_sticky,test_lift_cleanup_error,test_linux_preflight,test_make_game_cache,test_make_game_prepare,test_missing_nup,test_oot_state_tap,test_options_budget_once,test_portable_builder_home,test_portable_builder_startup,test_readback_late,test_spawn_emit,test_tested_configurations,test_vk_occlusion_queries,test_vk_pgo,test_walk_key; none of them is a mods or menu check.gpu/vk/vk_menu.cppcompiles alone with the build's flags. This head is not built into a game or played: the played head is56fd6f8d(the next item).The head before the last rebase, built and played on Windows by hand (
56fd6f8d, API version 23; Windows 11, RTX 3090, a child save; 2026-11-14, the mod's author), with a larger mod than the runs below: the same mod's v0.2.0 (https://github.com/snaildev99/OoT-Character-Select/releases/tag/v0.2.0), one folder of 5804 RomFS files, a size table and one.ipsof seven code patches. The build: 10 min 25 s (compiled in 7 min 55 s, 0 failed; the patch changes code, so the lift ran again),exefs: code ... (7353 bytes),main's code reaches 0x3c47000 now (a code cave). The start:mods: ... 5804 RomFS files (20 replace the game's, 5784 added), ExeFS patches,ExeFS ....ips: built into the game, the size table merged in 258 ms (1 table, 1222 entries, 15 entries raised). In the game he was asked for six things and reports all of it working: the Mods tab lists the mod On with no patch waiting; the mod's page with seven pages of pictures; a pick and the swap in the field with the character's own voice; a dress as cloth; the mod's Customize mode (hair colour, hidden equipment: hooks that run every frame) showing in the field; one of the new characters. The mod replacesMals/USen...sarc.zs(the game's texts) among others, and they load.A Windows build with a mod that has six ExeFS code patches in one file (https://github.com/snaildev99/OoT-Character-Select): the lines above.
In the game (Windows 11, RTX 3090, a child save), played by the mod's author: the Insert menu's Mods tab lists the mod, and moving in it and switching tabs both ways behave as on the other tabs; the mod's own page opens from the game's menu, a character is picked and is the player in the field at once (young Zelda with her cloth, which goes through one of the code patches, and Saria), and both speak with their own voices (another of the patches). No crash, 60 fps.
Linux, by mooki for T-3AW7 at
5142c2d7(Arch Linux, kernel 7.2.15, i9-13900K, RTX 4090, NVIDIA 615.78.08; his comment of 2026-11-13 has the whole result): a full build withnx2/make_game.pywith the same mod inmods, 0 failed compiles, the same threeexefs:lines as on Windows; the Insert menu's Mods tab shows the mod On with its code patch built in; the size table's merge took 30 ms (1 table, 90 entries, 15 raised); the mod's page opens from the game's own menu, young Zelda is picked and is the player in Kokiri Forest. A windowless Vulkan run of 189 s with a normal exit.Eight mods at once, by hand (the mod's author, Windows 11, RTX 3090, an adult save, a build of
5142c2d7; 2026-11-13). The mod of the runs above as the eight folders it is developed in: four ship a size table, six ship an.ipsfile each for the same build id (4935 bytes of main in all, the same bytes as the one merged file), and four game files are shipped by more than one of them.longshot_mods.txtwas written by hand first, the folders in the reverse of their names' order, because the emulators these folders come from let the first folder by name win a file and here the lower one wins. The steps, each one seen:The log of the three starts:
8 mods,4 tables of mods merged (90 entries), 15 entries raised(679 ms at the first start after the build, 69 ms at the third), with the one mod off3 tables of mods merged, 14 entries raised(58 ms) and1 ExeFS patch files built into the game belong to mods that are off or gone: they stay in it until it is built again; the file two mods ship is served from the lower one, and from the other one while the lower is off;longshot_mods.txtwas written again by the game; 60 fps, no crash.Linux again, by mooki at
5142c2d7(his second comment of 2026-11-13, windowless): the mod moved below a second RomFS mod of one file, then switched Off; after a quit and a start the order and the Off state were kept in the Mods tab, its RomFS files were no longer served, and the log said that its built-in ExeFS patch stays until another build.Two
.pchtxtmods, by Serfrost (Windows 11, 32 GB, RTX 4080, driver 595.79; his comments of 2026-11-14 and 2026-11-15, by hand). His build is a nightly of his own, main with pending pull requests merged in, this one at its latest head among them (his log names source 5b33f6ba, which is not a commit of this branch). Six mods inmods, two with a.pchtxtfile each: Auto Jump (https://gamebanana.com/mods/739034, three records, two of them new code in the padding between functions) and Sprint Speed +20%. The log lists both asExeFS ...pchtxt: built into the game, two size tables merged in 34 ms, and he reports both working as intended in the game. An earlier crash of Auto Jump there came from an edit of the mod'soffset_shiftflag, not from the patcher; with the mod as published it works.A cheat file and a mod that is only a code module, by hand (the mod's author, Windows 11, RTX 3090, the build of
56fd6f8dof the second item, no rebuild; 2026-11-15). Two folders added tomodsbeside the mod of the runs above. (1)cheats/E0F3CCFAF50065AE.txtwith one cheat written for this test, six lines: a condition on a pointer in main's data (opcode 1), two loads that follow it (opcode 5), an add (opcode 7), a store of a 4-byte value (opcode 6), the block's end. The same file was first tried on an emulator, where it sets the rupee count. Here the log sayscheats: E0F3CCFAF50065AE.txt in 1 mods: 1 cheats, 0 on; 1 data, 0 master, 0 headings, 0 not supported, the Mods tab lists the cheat under its mod, Off; switched On there, the rupee count was the cheat's value as soon as the menu closed, without a start. (2) A published mod that is one file,exefs/subsdk7(a module of its own code, which Longshot does not load): the log lists it as0 RomFS files (0 replace the game's, 0 added), ExeFS patches, the Mods tab lists it without a note, and nothing of it happens in the game; no crash. That line and the tab do not say why the mod does nothing: see Reviewer notes.What has been tried, and how:
.ipsfile of six code patches.ipsfiles for one build idlongshot_mods.txtwritten by hand, and by the game.pchtxtpatchexefs/subsdk7)Not tested:
longshot_mods.txtfile being written.{Master}codes, a mod whose paths differ in case from the game's, a patch that writes a relocated word.subsdk9file): not handled by this pull request, which patchesmainand adds no module.Must be tested:
python -I -B tests/test_mods_size_table.pyat4f8b975d, expecting itsok: documented RomFS examplelines and no failure. It is the example ofdocs/mods.md(#93), whose expected log line for a mod with only anexefsfolder I changed by reading the code; its file checks run on Linux only (by hand: asked of mooki in a comment; any Linux checkout will do, no game needed).Screenshots
this PR @ 3c23346a (the build of the run above), Windows 11, RTX 3090, from the mod's author:
The mod's Characters page in the game's own menu with King Zora picked, the game in a window (its title bar says Longshot). The page's fourth tab, the picked character's model and its view all come from the mod's ExeFS code patches, so this is those patches at work in a Longshot build. On main the patches are left out: nothing to show beside it.
The Insert menu (F1) of the same run: the Mods tab, with the mod listed and On.
Risks
What could break: with mods, the size table the game gets is the merged one, where before it was the last mod's own; if the merge fails the last mod's table is served as before. The Mods tab adds a tab to the Insert menu's strip only when there are mods. A build with ExeFS patches lifts patched code; without such patches the lift and its key are as before.
Turning it off: there is no switch. Without a
modsfolder none of it runs; one mod is switched off on the Mods tab, or by a-or_before its folder's name. Reverting the commits is enough.Compatibility: saves and
longshot_options.iniare untouched.MKWVK_API_VERSIONis 24 (the Mods tab's five functions afteroptions_path), so the game and its renderer have to be of one build. A game folder with mods getsmodcache/rsizetable.zs, andlongshot_mods.txtonce the tab is used. A mod with code patches takes effect only after the game is built again, and switched Off its patches stay built in until the next build (the Mods tab and the log say so). The shader and pipeline caches after updating were not looked at.Reviewer notes
Not done here, a follow-up I would send once this is in: a mod whose
exefsfolder holds a code module (subsdk0tosubsdk9, or amain) is listed as having ExeFS patches and gets no note. It should say that code modules are not supported, on the Mods tab and in the log, since most of such a mod (and any.ipsbeside the module that only jumps into it) does nothing here.The lift fix a code-cave routine needs is on main (#131), so its commit is no longer here.
The compact bar on the Mods tab: I draw the full Mods page (dimmed) from
draw_tune_bar, becausetests/test_menu_tune.pyasks forif (tune_mode) draw_tune_bar(k);as it is. The other way would be to leave the Mods tab out of the strip while the bar is up. Say if you prefer that.DT_RELR words: I took the second of your two ways (leave them out and say so). Applying them needs the addends read again after
apply(); I can do that here or as a follow-up.The draft's third commit on that branch (the banner's header) is not here: another topic.
Issues
See #42
Checklist
Everyone
type(scope): description) of the status line's type.main.python -I -B tests/run_all.pypasses (any failure that also fails onmainis named).third_party/untouched (a new folder with aPROVENANCE.mdinstead).When it applies
Assisted-by:/Co-Authored-By:trailers.MKWVK_API_VERSIONbumped (gpu/vk/vk_api.h'sMkwVkHostorMkwVkRendererchanged).mainand passing here.tests/that reads changed source updated in this PR, not weakened.🤖 Generated with Claude Code
Code review only: I read the diff and the code around it, and did not build or run anything.
The port is careful. The API bump to 22 with
can_mods()checking it, the posix shims thatzs.candmods.cnow need, the exefs patches going into the lift key through the environment, and the checks that read source being updated all look right. Some things need to change before this can merge:hos_mods_filestill gives prompts.c the last mod's own table, which goes against its own comment and the description (inline).exefs_patches.txt, so the Mods tab tells the player to rebuild to apply patches that the build will always skip (inline).longshot_mods.txt(inline).cf0dbb34). The lift commit here (e20dc232) is the same change and becomes empty after a rebase. The "Rebased on current main" box is ticked, but the PR still sits on1e5fcc20, and the Reviewer notes about merging 131 first are out of date.There are also smaller points inline: RELR words in exefs_patches.py, the case of mod paths in the size table keys, and the Restart/Quit confirmation on the Mods tab. The size table is now merged at every start with mods (decoding the game's table and reading the header of every mod
.zs), so please put that startup time in the Measurements along with the in-game run.@ -5105,1 +5247,3 @@changed = true;Row& r = rows[tab_row_to_global(cur_tab, selected)];const bool confirm_selected = &r == &rows[kQuitRow] || &r == &rows[kRestartRow];if (pending && (pending != &r || !mine)) pending = nullptr, changed = true;Nit: now that this sits in the
elsebranch for the tabs that aren't Mods, a pending Restart/Quit confirmation is no longer cleared when the player goes to the Mods tab. It stays armed for when they come back to that row. Clearingpendingwhenevercur_tab == TAB_MODSwould keep main's behaviour.@ -167,3 +286,3 @@return;}qsort(mods, (size_t)nmods, sizeof *mods, mod_cmp);fputs("# Hookshot's mods: their order and switches, as the Mods tab sets them (Insert menu). One line a mod, top of the\n"This header goes into the player's
longshot_mods.txt: it should say Longshot's mods, not Hookshot's.@ -180,0 +350,4 @@while (line && line != built && line[-1] != '\n') line = strstr(line + 1, want);const char *kind = line ? line + strlen(want) : "";if (!line) {m->exefs_waiting++;exefs_statusalso runs over mods that are off, butwrite_listonly writes the files of mods that are on. So every ExeFS file of an off mod lands here asexefs_waiting, andmod_texttells the player to "build the game again (Hookshot Builder) to apply" patches that the build will skip. Count waiting files only for enabled mods. For an off mod, either say nothing or say that its patches are not applied while it is off.@ -180,0 +454,4 @@const Entry *e = &table[i];if (!e->key || e->is_dir) continue;char key[MAX_PATH], game[MAX_PATH], host[MAX_PATH];restbl_key(e->rel, key, sizeof key);Suggestion: the key uses the mod file's own path case (
e->relcomes fromfd.cFileName). RESTBL keys are CRC32s of the game's case-sensitive paths, so a mod that shipsui/...where the game hasUI/...gets an entry under the wrong hash. On Windows,game_filestill finds the game file, sorestbl_get(base, key)misses and thiscontinues as if the file were streamed. Taking the case from the game's own path (or from the shipped table's key) would make the raise work for those mods.@ -202,2 +609,4 @@snprintf(key, sizeof key, "%s", rel);lower_slash(key);if (sizes_key[0] && !strcmp(key, sizes_key)) {if (!sizes_fallback[0]) return 0;This returns the last mod's own table even when
sizes_served(the merged table) is what gets served. The comment above and the PR description both say "none when the merged one is served". prompts.cbuild()then checks and merges into that mod table. If prompts can't merge it (a frame without a content size, or a table not in the game's format),build()returns 0 and the player gets the game's own icons in texts, even though the served merged table already has the prompts' entries. Suggested fix:if (sizes_served[0] || !sizes_fallback[0]) return 0;.@ -238,0 +669,4 @@k = 0, d = m->note;if (m->overridden) ADD("%d of its files are taken by a mod lower in the list. ", m->overridden);if (m->exefs_waiting)ADD("%d ExeFS patch file%s %s not in the game yet: build the game again (Hookshot Builder) to apply %s. ", m->exefs_waiting,The player sees this in Longshot's Mods tab: "Hookshot Builder" should be "Longshot Builder". The same goes for "Nothing Hookshot uses." a few lines up (668).
@ -0,0 +237,4 @@if word is not None and self.bound[word] == "JUMP_SLOT":return "unsupported", "it writes a function pointer the dynamic linker binds at main+%X" % word, {}if word is not None:continue # (an explicit-addend relocation's word: the dynamic linker writes over it, as on the console. A DT_RELRnso2.py turns DT_RELR words into
RELATIVErecords whoseaddendis read from the file before the patch, so they are inself.boundand their patch bytes are dropped here. On the console, the RELR addend is the (patched) word, so the patch takes effect there. The comment says the opposite of what happens. Either apply RELR words, which needs the addend recomputed afterapply(), or fix the comment and treat them like the explicit-addend ones, with a note in exefs_patches.txt saying so.You cooking 🔥
1faee42e07841d80ed5dThanks for the review. Rebased onto main @
0eb3a3c6(the lift commit dropped out, as #131 is in), and the last commit (841d80ed) takes each point:hos_mods_filenow returns 0 then (sizes_served[0] || !sizes_fallback[0]), as you suggested. I checked thatfit_sizesstarts the merged table fromprompts/rsizetable.zs, so the prompts' entries are in what is served.longshot_mods.txt.pendingis cleared there.game_casegives the path as the game's folders spell it, andfit_sizesasks for it only when the mod's own case misses the table, so a mod whose paths match the game's does no extra reads at the start. Windows only: where the file system tells the cases apart the path is left as it is.exefs_patches.txt(and the README says so). Applying them instead needs the addends read again afterapply(): say if you want that here.Built on Windows after these changes (
mods.candvk_menu.cppcompile without a warning; the build's lines are in the description). The full run_all is 462 passed with the same 10 failures as main on this PC. Not run in the game yet: that is next, with the merge's start-up time from the log.841d80ed5dca32fd3b1aRun in the game (Windows 11, RTX 3090, a child save, the build of the review's changes): the Mods tab lists the mod and behaves; the mod's code patches all work (its menu page, picking a character, the swap in the field, the cloth and the voice hooks); the size table's merge took 111 ms at the start (1 table merged, 90 entries; 15 raised); 60 fps. The log's lines are in the description. Also: a line on longshot_mods.txt in docs/development.md, rebased onto main @
39647e1a, and a test request for Linux (T-3AW7). Still to come: a picture of the Mods tab, and what the description lists under Not tested (switching and moving mods on the tab, a cheat file, a pchtxt mod).A picture from the run is in the description's Screenshots section.
Two pictures from the run are in the description now: the mod's page in the game's menu (its code patches at work), and the Insert menu's Mods tab with the mod listed and On.
A second start of the same build: the size table's merge took 56 ms (111 ms on the first start, right after the build). Both lines are in the description. What keeps this WIP: the Linux build and run (T-3AW7 is open for it), which I cannot do here.
A correction for whoever takes T-3AW7: for its second step, do not use the mod's public v0.1.2 zip. Its patch file enters one routine with
bland comes back with a plainb, which the console and the emulators take as a return and the lift cannot (the rest of the calling function runs twice): it crashes at the first character picked. That is a fault of the mod, fixed in its source (one byte, theblis ab) and in the build I ran here; a v0.1.3 of the mod with it is on its way, and I will link it here. The first step (the Mods tab with any RomFS mod) is unaffected.A mod package for testing this, for T-3AW7 and anyone who wants to see ExeFS patches at work:
https://github.com/snaildev99/OoT-Character-Select/releases/tag/v0.1.3 (
OoT-Character-Select-v0.1.3-lite.zip, 171 MB, SHA-256ff60cd9bd4f7c5ca1cdbfbb2242fd8fd2dfa08e221e856dd1c2c65d64dd2d5e1)It is the mod of the measurements above: one mod folder with RomFS files, a resource size table and one
.ipsfile of six code patches, for the game's 1.1.1 build.OoT Character Selectfrom the zip'smodsfolder into the game folder'smods.nx2/make_game.py). Expected in the build's output:exefs: code OoT Character Select/exefs/E0F3CCFAF50065AE4EA01EB5AEB13B1A45DA3348.ips (4935 bytes)andexefs: main's code reaches 0x3c47000 now (a code cave).longshot_log.txt:ExeFS ... built into the gameand themods: resource size table: 1 tables of mods mergedline; the Insert menu shows the Mods tab with the mod On.Take v0.1.3, not v0.1.2: the older package has a routine that is entered with
bland goes back with a branch, which the lift cannot run (the function's tail runs twice), and it crashes at the first pick. That was the mod's fault and is fixed in v0.1.3; it is the one byte between the two packages.Still open here: the checklist's Linux box, which is why this stays WIP. Nothing changed on the branch since ca32fd3b.
Rebased onto main
6b7fa356(one commit, the launcher download links): no conflict, the five commits are unchanged (range-diff: all equal), head5142c2d7. tests/run_all.py at this head: 498 passed, 36 skipped, 8 failed, and the same 8 fail on main here (named in the description). Not built again: the rebase brought no host, renderer or lift change.ca32fd3b1a5142c2d7b9Linux result for T-3AW7: all four steps passed on
5142c2d7b9adc782077830cb6d655186494e7633, source1d80fd67(v3.0.0-rc.1-15-g5142c2d7b). Arch Linux, kernel 7.2.15-1-omarchy, i9-13900K, RTX 4090, NVIDIA 615.78.08. This was a windowless Vulkan run with a disposable child save.ff60cd9bd4f7c5ca1cdbfbb2242fd8fd2dfa08e221e856dd1c2c65d64dd2d5e1. v0.1.2 was not used.nx2/make_game.pycompleted with zero failed compiles. It reportedthe build took 0:09:39, compiled in 0:07:30and:The game exited normally after 189 seconds. No fatal, crash, device-loss or kernel-driver fault was recorded; the kernel-log read completed. One 20-second frame wait expired during the first load with shader preparation disabled; the game subsequently advanced and completed the character test. This was not a frame-rate measurement.
Not covered by this result: Windows, other GPUs, physical input, sound/voices, long play, every character, the compact menu, mouse interaction, pchtxt patches, cheats, path-case differences, relocated-word patches, or rebuilding after disabling a code mod. No comparison build was needed for these four acceptance steps.
Thank you, mooki: that is the Linux build and run this was waiting for (T-3AW7: a full build with 0 failed compiles, the Mods tab with the mod On, the mod in play, the merge in 30 ms).
What changed in the description:
longshot_mods.txtbeing written).With that I am taking the WIP mark off. Nothing changed on the branch: the head is still
5142c2d7, the commit mooki tested.main has moved 28 commits since this branch's base (
6b7fa356, nowfdc2840b). I tried the rebase in a throwaway worktree and did not push it, so the head on the page stays the tested one: no conflict, andtests/run_all.pythere gives 489 passed, 33 skipped, 6 failed. The 6 fail on main itself on this PC: four of the eight named in the description (test_check_third_party,test_portable_builder_home,test_portable_builder_startup,test_release) and two tests that are new on main,test_display_pointer(it callsg++, which this PC does not have) andtest_texture_staging_publication(it asks for--headers); I ran those two on main @fdc2840balone and they fail the same way. Say if you want the rebase pushed before the merge; a build of it would then be new, untested code on Linux.Still tested by nobody, as the description says: switching a mod Off and On and moving it on the tab, the compact bar with Mods current, a pchtxt mod, a cheat file, two mods with a size table each, a patch into a relocated word.
T-3AW7 stays open for now, in case someone with another GPU on Linux wants to add a result.
WIP: feat(mods): a Mods tab, merged resource size tables, ExeFS patches and Atmosphere cheats (HumanBean's draft from Hookshot, with the lift fix)to feat(mods): a Mods tab, merged resource size tables, ExeFS patches and Atmosphere cheats (HumanBean's draft from Hookshot, with the lift fix)A correction to my last comment: the reasons I gave for the two checks that are new failures on main were guesses from running them alone, and they are wrong. Both are known on main and have fixes open:
test_texture_staging_publicationfails since #177 (dyst's #202), andtest_display_pointerhas not linked since #178 (d4nkn3ss's #187). What I measured stands: both fail on main @fdc2840bon this PC as they do on the trial rebase, and this branch touches neither.Two additional Linux checks on
5142c2d7: moved OoT Character Select below a second RomFS mod containing only one invented text file, then switched Character Select Off. After quitting and starting again, both the order and Off state persisted in the Mods tab; its RomFS files were no longer indexed. The log correctly warned that its already-built ExeFS patch remains until another build.This was still windowless: the restart question requires a window, so this checks a manual quit/start, not "Restart now". No rebuild to remove the code patch, file-conflict priority, mouse interaction or compact-menu check was performed.
A hand test of the parts that were still open: several mods at once, on Windows (the mod's author played it; RTX 3090, an adult save, a build of
5142c2d7, nothing changed on the branch).The mod of the earlier runs was put in as the eight folders it is developed in, instead of its one merged folder: four of them ship a size table, six ship an
.ipsfile each for the same build id, and four game files are shipped by more than one of them. All eight steps passed; they are in the description under Tested, with a table of what has been tried on which system and how.4 tables of mods merged (90 entries), 15 entries raised, 679 ms at the first start after the build and 69 ms at a later one; with one of the four mods off,3 tables of mods merged, 14 entries raised, 58 ms.exefs: 4935 bytes of main patched by 6 files, the same bytes as the merged file), 2 min 16 s after the last build, and the mod plays as before.1 ExeFS patch files built into the game belong to mods that are off or gone).longshot_mods.txtwas written by the game afterwards.One thing for mod authors who come from the emulators, which I ran into setting this up: there the first folder by name wins a file two mods ship, here the lower one in the list wins. A set of folders that relies on the emulators' order needs its order turned round (I wrote
longshot_mods.txtfor it). The README says which one wins; nothing to change in the code, but it may be worth a line for people moving a set of mods over.Three notes from the tester on the restart texts, none of them a fault of the merge or the patches. I will put them up as a follow-up on top of this one rather than change this branch after its Linux run:
set_rowtells the restart state of a change,mods_tickdoes not).Still tried by nobody: a
.pchtxtpatch, a cheat file, a start with a changed order, the compact bar with Mods current, a patch into a relocated word.A correction: I wrote the comment above before reading mooki's second comment (16:14), so "a start with a changed order" was wrong in my list of what nobody has tried. mooki moved the mod below a second RomFS mod and switched it Off on Linux, and both were kept over a quit and a start, with the log's line about the patch that stays built in. Thank you. The description's table and Tested list now have that run beside the hand test; its Linux column says what a windowless run could not try (the restart question).
Tried by nobody, corrected: a
.pchtxtpatch, a cheat file, the mouse on the tab, the compact bar with Mods current, a patch into a relocated word.Test request T-AK6A: mixed (the full Mods menu and persistence pass; broader mod features remain untested).
Hardware: NVIDIA RTX 5090, 32607 MiB, driver 617.42; Ryzen 7 9800X3D, 96 GiB RAM; Windows 11 build 26300. Built exact PR head
5142c2d7b9, v3.0.0-rc.1-15-g5142c2d7 / source 1d80fd67. This is a fresh matched API22 game and renderer, not an API21 game with a swapped DLL. Cached dependency IDs/hashes match the branch's locked Windows dependencies.What I checked:
First launch, Alpha Off:
After the normal restart, reordered state persisted:
Not tested: compact-menu mode (no supported scripted trigger), physical keyboard/mouse/controller, the in-menu Restart-now confirmation (manual normal quit/start used), sound, gameplay or file-conflict priority, real code patches in a full build, pchtxt mods, live cheats, two real resource-size tables, relocated-word patch handling, other GPUs/OSes, long play, or performance. The parser/VM/size-table checks above use authored fixtures; they do not replace those in-game cases. No visual clipping defect was established. Installed player/saves/settings remained untouched; all 23 protected-file hashes were unchanged after the runs.
For the record: Hookshot is back on its own git server with the history kept and its issues and pull requests gone, so your draft (GitHub's 2478) is no longer there. I have offered this same work to Hookshot as a draft pull request on current main there, your two commits under your name and credited in the description: https://git.notrexed.dev/Hookshot/Hookshot/pulls/3. Its only change against this branch is the port: Hookshot's names, and API version 23 for the Mods tab's functions because its main took 22. Say if you would rather carry it there yourself.
A correction to the hand test's 'a mod Off whose patch is built in: works' (seen today on a Hookshot build of the same code, with the whole mod switched Off instead of one of its eight folders): the game runs, but not cleanly. The tester saw the tabs at the top of the game's own menu drawn wrong in that state. That is the half-off state the log names: the mod's RomFS files are no longer served, while its code patches stay built in until the next build. One of this mod's patches gives the menu's header a fourth tab, and the layout and text for it are RomFS files, so the patched code draws a header whose files are gone. Nothing in the merge or the patching is wrong, and the Mods tab does say 'Its ExeFS patches stay in the game until it is built again' for such a mod, but a player who switches a code mod Off gets half of it until they build again. A follow-up could say so in the restart question when the mod being switched off has patches built in; say if you want it here or in #305.
A new link for the Hookshot offer: their server restored its old issues and pull requests this evening and the numbers moved, so pull request 3 there is gone. It is https://git.notrexed.dev/Hookshot/Hookshot/pulls/2900 now, built and played on Hookshot by hand since. Your draft is back there as their #2478, shown as by Ghost and no longer merging; 2900 says it supersedes it.
Rebased onto main @
98e2d95f, since it no longer merged: head58b1e64d. Two conflicts, both small:restart_valuesingpu/vk/vk_menu.cpp(main now reads a row's value throughrow_restart_value; the mods' slot is added after that loop as before) and the list of posix shims intests/test_mods_size_table.py(main's comment kept,MoveFileExAadded).vk_menu.cppcompiles;tests/run_all.pyat this head: 519 passed, 34 skipped, 6 failed, the same six as main @98e2d95fon this PC (named in the description). Not built or played again after this rebase: the runs in the game, mooki's on Linux and monsterlag's on Windows are of5142c2d7. Thanks, mooki, for the labels on this one and on #304 and #305.5142c2d7b958b1e64dfdEdit: False behavior, ignore.
Found an offset issue while testing the ExeFS pchtxt support. This game’s main NSO has a .text file offset of 0x104, but the patcher assumed 0x100. That placed Auto Jump’s patch records four bytes late and caused a crash when jumping. Changed the build-time mapper to derive the NSO’s actual segment offsets. Testing confirmed Auto Jump works with the fix, without modifying the mod files.
@Serfrost thank you for trying a real
.pchtxtmod: that case had been tried by nobody. I could not reproduce the fault on this branch, though, and I think 0x100 is right here. What I checked, offline (not in the game):@flag offset_shift 0x100and three records, at 007F7574, 01EB4224 and 01EB4324.parse_pchtxtadds the shift and takes the header (NSO_HEADER, 0x100) off again, so the records land in main's image at exactly those offsets.retthat ends the function before (28 bytes of padding each; the records are 20 and 28 bytes). The mod's readme says its code sits in "two unused gaps between the game's functions", so that fits. Four bytes earlier, each record would overwrite thatret.bwhose target, counted from 0x7F7574, is 0x1EB4324: the second gap. Counted from 0x7F7570 it would point four bytes short of it..textsits in the file. That main's.textstarts at 0x104 in the file should not enter into it. The.ipsmod this pull request was tested with is built on the same rule and plays on Linux and Windows.So on this branch a mapper that follows the file's segment offsets would move Auto Jump's records four bytes early. Could you say which code you saw the crash on: this branch's head (
58b1e64d), or the patcher of #367? And where the change you made lives, so I can look at it? If the crash when jumping is on this branch as it is, I would like the build'sexefs_patches.txtline for the mod and the log around the crash: then it is something other than the offset, and worth finding.58b1e64dfd0f3a47dd9eRebased onto main @
9c9d69ed, since it no longer merged: head0f3a47dd. Three conflicts this time, and one of them changes a number reviewers should know:take_text) while this was open, so the Mods tab's five functions now followtake_textinMkwVkHostas version 23:vk_api.h, the table inhost/rx_backend.c(in the struct's order),can_mods()andtests/test_mods_menu.py. Nothing else about them changed.vk_menu.cpp, the rows' keys. Main's new text field line (field_row) joins the block that this branch moved inside the Mods tab'selse; that block is otherwise as it was.tests/test_menu_text_field.py(main's new test). Its harness cutsrestart_valuesout of the menu, and that function also fills the mods' slot here, so the harness declares the slot (none),can_modsand the host'smods_changedbeside itspad_raw. One small commit of its own, the last on the branch.tests/run_all.pyat this head on Windows: 535 passed, 34 skipped, 5 failed, the same five as main @9c9d69edon this PC (named in the description).vk_menu.cppcompiles alone with the build's flags. Not built or played after this rebase or the one before: the runs in the game, mooki's on Linux and monsterlag's on Windows are of5142c2d7. The description has the new numbers.mooki referenced this pull request2026-11-14 17:19:29 +00:00
@snaildev99
Thanks for checking the offsets and explaining where the patch records land. The 0x104 change was made by the coding assistant during the investigation; the mod’s original 0x100 flag should not have been changed. The assistant mistook the four-byte adjustment as something to put in the mod and treated a successful test as confirmation despite the guardrail meant to never edit mods. Your analysis indicates 0x100 is correct.
The packaged mod’s flag has been restored to 0x100; it now behaves as intended when used on this PR.
Rebased onto main @
82886e89, since it no longer merged: head56fd6f8d. Two conflicts and one new commit:README.md, Mods. Main's pointer todocs/mods.md(#93) is kept after this branch's ExeFS and cheats paragraphs.vk_menu.cpp, the rows' keys. Main's new laptop row line joins the block this branch moved inside the Mods tab'selse; the script that resolved it checks that every line the branch had there is still there.docs/mods.mdand its example (new commit, the last). #93 documents the loader as it is on main: ExeFS left out, one mod's size table, no Mods tab. This branch changes those three, so the document now says so: anexefsfolder's patches belong to the build and are only reported at a start, the size table is merged at a start (one mod's table when that cannot be made), andlongshot_mods.txtcan change the order and the switches. Without that file the order is exactly the documented one (mod_cmpis untouched), so the example's expected files are unchanged. The one expectation I had to change is the log line: main logs03-code-only: exefs patches are left out, this branch lists the mod as03-code-only: 0 RomFS files (0 replace the game's, 0 added), ExeFS patches.@mooki the example's checks run on Linux only, and I have no Linux here: I changed that one expected line by reading
hos_mods_init, not by running it. Could you (or the Linux job) runpython -I -B tests/test_mods_size_table.pyat this head? If the wording of the document is not what you meant by the contract, say so and I will change it.tests/run_all.pyat this head on Windows: 534 passed, 34 skipped, 13 failed, the same thirteen as main @82886e89on this PC in the same run (530 passed there).vk_menu.cppcompiles alone with the build's flags. Not built or played after this rebase.@Serfrost thank you for following it up and for saying plainly what happened; that is useful to know for anyone else who tests this with an assistant.
So Auto Jump, unchanged, works on this pull request: that is the first real
.pchtxtmod anybody has run on it. May I put it in the description's table of what has been tried? For that I would need the head you built (the branch has been rebased since;longshot_log.txt's first line names the source), your system (Windows or Linux, GPU), and what you saw in the game (running off a ledge jumps, I take it). The build'sexefs_patches.txtline for the mod would be welcome too.0f3a47dd9e56fd6f8d82This head (
56fd6f8d, after the four rebases, API version 23) is now built and played on Windows, by hand, with a larger code mod than before..ipsof seven code patches (7353 bytes of main changed; v0.1.3's was 4935).nx2/make_game.pyinto the test copy, 10 min 25 s (compiled in 7 min 55 s, 0 failed). The patch bytes changed, so the lift ran again: this is the time for a changed code mod on this PC (Ryzen, 24 threads), where a build with unchanged patches took about two minutes.The description's Tested list has it as its second item. Still not run by anybody: the Linux part of
tests/test_mods_size_table.pyat this head (the example of #93; my question to @mooki above), and a cheat file. A.pchtxtmod is Serfrost's report above.@snaildev99 wrote in #134 (comment):
I've refreshed the repository with current commits and pending PRs quite a few times now, including the latest version of this PR. I have not tested other exefs mods, as most require subsdk9 or other support to actually work in the game. Auto Jump works as intended. Sprint Speed +20% as listed in the log also works as intended, both mods being pchtxt.
Device is Windows 11, 32GB DDR4, RTX4080 Driver ver. 595.79
No further problems to report other than hoping we can get an implementation for subsdk modding with exefs.
@Serfrost thank you, that is exactly what the table needed. Two
.pchtxtmods (Auto Jump and Sprint Speed +20%) on Windows 11 with an RTX 4080 are in the description now: a new item in Testing, the.pchtxtrow of the table, and the Flags row. I wrote it as your build, a nightly with other pending pull requests merged in (the log's source 5b33f6ba is not a commit of this branch); say so if that reads wrong.On mods that need
subsdk9: this pull request patchesmainand adds no module, so those are outside it, and the description now says so under Not tested. It is a fair next step once this is in: a module of a mod's own would have to be placed, linked and lifted with the game's at build time, which is a change to the image builder and not to the patcher. I would rather not grow this pull request by it; an issue with one such mod named as the test case would be a good place to start.Two more rows of the "what has been tried" table are filled, both by hand on Windows 11 (RTX 3090) with the build of this head (
56fd6f8d), no rebuild:cheats/E0F3CCFAF50065AE.txt, six lines: a condition on a pointer in main's data (opcode 1), two loads along it (opcode 5), an add (opcode 7), a 4-byte store (opcode 6), the block's end. Tried on an emulator first, where it sets the rupee count. Here:cheats: E0F3CCFAF50065AE.txt in 1 mods: 1 cheats, 0 on; 1 data, 0 master, 0 headings, 0 not supported, the Mods tab lists it under its mod, Off; switched On, the count was the cheat's value as soon as the menu closed, without a start. Not tried: a cheat that writes code,{Master}codes.exefs/subsdk7): listed as0 RomFS files (0 replace the game's, 0 added), ExeFS patches, no note on the Mods tab, nothing of it happens in the game, no crash. That is what the code says should happen (only.ipsand.pchtxtare read), but neither the log nor the tab tells the player why the mod does nothing.On the second: about half of the code mods I have seen for this game ship such a module, some with a small
.ipsthat only jumps into it. I read two of those.ipsfiles: their stubs load a pointer the module would have filled in, find it empty and run the game's own instruction, so built in here they would do nothing rather than crash (read, not built). I would like to send a small follow-up once this is merged: a mod withsubsdk*ormainin itsexefsfolder gets a note on the Mods tab and a log line saying code modules are not supported. I have left it out of this pull request so the head that four people tested stays as it is; say so if you would rather have it here.The description has both under Testing, the table, the Flags row for cheats and Reviewer notes.
This pull request now conflicts with
main: gpu/vk/vk_api.h and host/rx_backend.c, touched onmainby #729, #577, #95, #593; both defineMKWVK_API_VERSION23 now, so this one needs 24. Could you rebase it onto the currentmain? Thank you.56fd6f8d824f8b975d6fRebased onto main @
6a013efd, since it no longer merged: head4f8b975d.options_path) since the last rebase, so the Mods tab's five functions now follow it inMkwVkHostas version 24:vk_api.h, the table inhost/rx_backend.c(in the struct's order),can_mods()andtests/test_mods_menu.py. These were the only two conflicts, and nothing else in the seven commits changed (git range-diffshows the first commit's version lines and the other six as equal).gpu/vk/vk_menu.cppcompiles alone with the build's flags, andtests/run_all.pyon Windows gives 516 passed, 21 failed, 74 skipped, the same twenty-one as main @6a013efdin the same run (512 passed there); the description names them.56fd6f8d.A note for whoever merges second: #799 takes version 24 as well, so one of the two moves to 25.
Still open: a Linux run of
python -I -B tests/test_mods_size_table.py(now at4f8b975d), and a second look at the review from before the answers.View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.