The VM refused C0 (register conditional), so cheats like C046B400 FFFFFFFF were greyed out as unsupported. C0 now
runs with dmnt's operand types 0-5, widths and conditions; C1 and C2 save, restore and clear registers, C3 moves
registers to and from the static registers. tools/cheats.py gives the same opcode lengths and accepts the same opcodes.
skip() took the end of a nested block at depth 1 for the else of the failed outer block; it now counts depth the way
dmnt does, and 2x000000 is an else only for x = 1.
tests/cheats_vm_check.py runs tools/cheats.py and runtime/cheats.c on cheats of its own, the two from the issue
included.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The arrow alone read as a direction (review of #585). The key keeps the width of the game's own glyph, so the name goes
under a solid arrow rather than on a wider key.
The setup wrote romfs-patches-pc for 16:9 only, so prompts = pc on a 21:9, 32:9, 16:10 or 3:2 screen kept the game
resolution at 1920x1080 (cfg.c) or, with output_res set to the screen's shape, fell back to the Switch prompts.
- make_romfs_patches.py --reuse <folder>: a file of an earlier overlay whose contents match is hard-linked (copied
where links fail) instead of compressed again; the output is byte for byte the same.
- The setup builds romfs-patches-pc-<w>x<h> for each of ASPECT_SIZES from romfs-patches-pc with --reuse: only the
Common archive is compressed, so the fifteen folders take about two minutes and 6 MiB each instead of 22 minutes
and 73 MiB each.
- cfg.c: the display-shaped output_res default applies with prompts = pc when romfs-patches-pc-<w>x<h> is there
(romfs_shaped_pc_ready); Xbox prompts keep 1920x1080 as before.
tools/worktree.py links config.local.toml and CLAUDE.local.md since e3e72df, but the test's .gitignore listed only the first, so git status in the new worktree showed CLAUDE.local.md and the test failed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An IPS, IPS32 or pchtxt file for main that writes main's .rodata, .data or .bss was refused as a whole. Such writes
need no lifting: tools/exefs_patches.py now puts them in build-cache/exefs.data, and runtime/mods.c stores them into
the loaded main before any game code runs, as emulators and Atmosphere apply a patch file to the module before its
dynamic linker runs. A DT_RELR word gets main's base added; bytes over words an explicit-addend relocation fills change
nothing and are dropped; a write into the dynamic linker's tables, a lazily bound function pointer or outside main's
image keeps the file unsupported. A mod whose data lines do not all fit the build keeps its code patches off too.
The mods are applied from the bottom one up (mods_exefs_apply), so where two mods write the same bytes the higher one
wins, as it does for files. --probe-mods checks it with two mods writing one byte.
The lifted code reads some of main's bytes at lift time (the jump tables). recomp/gen.py lists those ranges in
<gen>/main/folded.txt, and the setup checks the data writes against them after the lift: a file that writes them
becomes unsupported, since the change would not reach the generated code. Generated C without mods is unchanged.
This is the data part of #790 (ericdallo's ExeFS work), split out on its own; the plugin part follows separately.
Co-Authored-By: itaskaev2 <165683571+itaskaev2@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
All 72 1.4.3 sites mapped to 1.2.1 main (82 patch lines), the values that differ in a per-version MouseLayout. The
player state (ability, bow in hand) is not known on 1.2.1 yet, so mouse_aim_scale has no effect there, and the menu
pointer and map cursor stay 1.4.3 only.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The 72 mouse_direct sites of patches-v143.txt mapped to the 1.2.1 main by enclosing function, each original word
checked: the camera input update, free orbit, Ultrahand and the 16 player camera modes. 1.2.1 keeps some values in
other registers and stack slots (camera input x in s11, orbit angles at sp+0x3c/0x40, Ultrahand x in s9), so the
hooks read a per-version MouseLayout; the 1.4.3 values and behaviour are unchanged.
Where 1.2.1 adds a turn in a shared helper, the degrees go to the store of the helper's result (Aiming's pitch
target, new ROLE_BASE_STORE) or to the turn it is given (Horse yaw). Wakeboard, ShieldSurfing and Tail read the stick
twice (REPEAT on the first read), the dialogue cameras' angle loads are pre-indexed (mov -> add, as ClimbObj).
The player state (ability, bow in hand) is not known on 1.2.1 and reads as not found.
tests/mouse_direct_check.py runs the hooks with both layouts and checks each load-site table against its patch file.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The menu pointer stays through tab switches, the mouse moves the map cursor and the wheel zooms it (#769). Every player
camera turns at the 0.3.10 rate of 0.08 degrees a count, the pitch no longer springs back after an aim (#717) and a
mouse shake detaches a fused part from what Ultrahand holds (#734). The load hooks return the game's own value in a step
without mouse motion, so a gamepad player gets the game's values unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PR #753 unified the cameras at 70/1500 = 0.0467 degrees a count and doubled Ultrahand's rate, which made every
existing mouse_sens turn the free camera 42% slower. The free camera keeps 0.08 degrees a count at mouse_sens 1, every
camera #753 unifies takes the same 0.08, and the held object keeps 0.01 stick units a count over the frame ratio (with
#753's cap of three full tilts a step). A mouse shake now needs 125 counts a swing at sensitivity 1 (10 degrees).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The stick loads returned the sample's pad stick even in a step without mouse motion, which drops anything the game
writes to the camera input's stick after the input update (main has 426 stores to +0x420/+0x424). The base loads added
0 degrees, which turns a -0 into +0. Both now return the loaded value untouched when the step has no mouse motion and no
mouse shake.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Another install, say 1.2.1 while the recorded update is 1.4.3, would extract again from them. Each dest change compares the prefilled file's name and size (one stat) with the install's totk-install.json inputs and clears the field on a mismatch. Fields the user picked or typed are never cleared, and a folder without state keeps the prefill.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A recorded path on a disconnected network share can block its stat call. The paths load in a daemon thread and nothing is prefilled when it has not returned in time.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A reinstall or a switch to another game version starts with the form filled in. Only absolute paths are stored, never the keys file's contents, and an entry that no longer exists, has the wrong kind or cannot be statted is dropped on load. Follows #715 by DanielSvoboda.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An update needs only the install folder: the recorded game language and the other inputs come from totk-install.json. installer/recent.py stores the folder in %LOCALAPPDATA%\TotK Recomp\setup.json (XDG state dir elsewhere), outside the release folder, so a new release finds it.
Idea from #715 by DanielSvoboda.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The ClimbObj pitch hook replaced the pre-indexed load ldr s1, [x22, #0x88]! and wrote the write-back to x22 from the
host call. The lifter keeps X19-X29 in locals across a call and does not reload them, so the write-back was lost and
the following str s0, [x22] wrote the pitch over the camera object's vtable pointer; the next virtual call crashed
(seen climbing a Stone Talus). The mov x22, x19 before the load is now patched to add x22, x19, #0x88, the hook
performs a plain ldr s1, [x22], and load hooks no longer support write-back loads at all.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
146 of the 254 names in known[] (runtime/cfg.c) were described nowhere for a reader: diagnostics, test hooks, paths and
experiments. docs/settings.md gives each one what it does, the effect of changing it and a safety level. The rows come
from reading the code, and most effects have not been checked in a run.
tests/settings_doc_check.py fails when a known name is in neither file, when the page lists an unknown name, or when a
default on the page differs from metas[] or an rt_knob call.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Only the free camera took the mouse as degrees. The other camera modes took it as stick input, which their own speeds,
frame time, slow motion and curves reshaped, with the on-foot camera's square-root pitch shaping on top: bow aiming was
far too sensitive vertically on slow movements, abilities and the Purah Pad camera eased their pitch, lock-on turned
27% slower, and climbing, shield surfing and the rest each turned at their own rate.
Each camera mode's update now loads the gamepad's part of the stick where it computes its turn and adds the mouse's
degrees where it adds that turn to its angle (runtime/mouse_direct.c camera_load, the load sites of
recomp/patches-v143.txt): aiming, scope, abilities (and Recall's cursor), horse, Purah Pad camera, lock-on, bow
lock-on, wall and object climbing, the waterfall's turn, shield surfing, wakeboard and the dialogue cameras. Recall's
cursor moves so that its aim turns as many degrees a count. The waterfall's height and the horse's empty lock-on are
not angles and stay stick input, now linear. A step whose mouse motion no hook took is logged with the camera's name.
Holding L to soothe the horse gave the mouse to the ability wheel's virtual cursor, a held stick for the camera: the
mouse goes to the wheel and the D-pad strips only while their screen has the focus (menu_cursor.c
menu_cursor_shortcut). A mouse shake no longer plays a 0.8 s swing after it: the right stick follows the mouse's own
swings and lets go when it rests, and the camera does not turn by that swing.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
player_state follows about fifteen pointers from the player root to its action verbs, and each was checked with
plat_mem_readable, a VirtualQuery. That query costs tens of microseconds in this process, so the read stalled the
game thread while the mouse moved. mem_guest_readable answers from the layout for the committed heap and the loaded
modules, where the player and its components live, and asks the OS only for anything else. Together with the same
change in the menu pointer, a Ryzen 5 4500 runs the scene that fell to 50-54 fps at 58-60 fps again.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The game detaches a fused part when the right stick swings left and right a few times. In direct mode the mouse never
reaches that stick, so shaking the mouse did nothing and only the shake key worked. A shake of the mouse while
Ultrahand holds an object (at least three changes of direction within 0.6 s, each swing worth 10 degrees of camera turn
or more) now plays the shake key's right-stick swing. Turning a held object back and forth at an ordinary pace takes
longer and does not count.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The free-orbit camera turned 0.08 degrees per count while the other cameras (the bow, the abilities) took the mouse as
0.01 stick units a count, about 0.023 degrees for yaw and through the camera's signed-square pitch for y, so the bow
turned at half the speed and small vertical moves barely moved it. Every camera now turns 70/1500 = 0.0467 degrees
per count: the stick path gets 0.02 a count for x and the signed square root for y, so pitch is linear and as fast as
yaw. While the player uses an ability (the action verb UseSpecialPower, read from the player actor with the 1.4.3
placement), the ability camera's own linear scales are undone instead. stick_sens is divided out, Ultrahand's held
object gets the camera's stick value a count (at most three full tilts a step), and mouse_aim_scale (0.1 to 3)
scales the mouse while the bow is drawn.
The game's lock-on, recenter and aiming override released the mouse's pitch hold and restored the target from before
it, so after a shot the camera sprang back to that pitch, or to the game's default target. The mouse now lets go
without restoring, and when the override ends (or the camera went 100 ms without a step) it holds the pitch the
camera has then. A gamepad's vertical input still hands the pitch back to the game.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
direct_mode read TOTK_MOUSE_MODE with cfg_get, which is NULL when the player never set it, so a fresh totk.ini kept
stick emulation (and its speed cap) while the menu showed Direct. cfg_value gives the settings table's default.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
In the 1.4.3 Common archive Nt_KeyTexLSt_00 is the left stick tilted up (U+E052) and Nt_KeyTexLSt_01 the left stick
(U+E05E): they showed WASD and Ctrl, now W and WASD. U+E068 (the right stick shaken) showed the mouse, which never
shakes the stick; it shows the SHAKE key (G by default).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Button prompts row gets a third choice, PC: every button glyph of the game's button font and every image of the
button guide is drawn as the key or mouse button the keyboard defaults press it with (E, Shift, Space, left click, Q,
R, right click, F, Tab, M, 1 to 4, W A S D, Ctrl, C) and the right stick as the mouse, whatever the pad layout. The
setup writes romfs-patches-pc next to the Xbox folders (make_romfs_patches.py --prompts pc, button_icons.py pc_icons);
--binds <totk.ini> draws the keys of its bind_ lines instead. At a game resolution of another shape the game takes
romfs-patches-pc-<w>x<h> (--prompts pc --aspect) in place of romfs-patches-<w>x<h> when it is there.
(cherry picked from commit 3f81cf02be664100bd9c569c53588d620d90b527)
#483 draws the stick glyphs on the wrong code points: U+E056 and U+E057 (the stick presses) get WASD and the mouse,
U+E05E and U+E05F (the sticks) get Ctrl and C, and both left-stick HUD images show Ctrl. #585 maps them by the game's
font and keeps each glyph's advance.
This reverts commit cd35ffa.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add keyboard and mouse button icons to tools/button_icons.py and
tools/make_romfs_patches.py --prompts keyboard, with a third 'Keyboard
and mouse' choice under Controls > Button prompts (prompts = keyboard).
Keycaps cut their labels out of the game's own text fonts, mouse clicks
and right-stick camera look draw clean mouse silhouettes, and
make_romfs_patches.py --ini reads any bind_ overrides from totk.ini.
The camera input's activity hook wrote the flags to +0x418, but the game's next instruction stores w8 over them
(1.4.3 main+0x874d80), so a mouse-only turn still read as idle. The hook now changes w8.
The free-orbit hooks rewrote the camera stick input from the last sample even in steps without mouse motion; they now
take a step only when the mouse moved in it or holds the pitch, so a gamepad-only step reaches the camera as the game
made it.
The mouse shake gesture for detaching parts is gone: three 24-count reversals fire while a held object is turned back
and forth. Arrow keys or the pad's right stick shake as before.
The menu pointer comes only while the keyboard and mouse are the device used last (input_kbm_last): a gamepad player
sees the menus as on the console. Raw mouse travel is counted while the cursor is released too, so moving the mouse
brings the pointer back.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The window ran in a DPI-unaware process, so Windows drew it at 96 DPI and stretched the result on a display at 125% or
more, which is why it was blurry at 200%. The process is now system DPI aware before Tk starts (installer/dpi.py: Tk 9
has no per-monitor support, so system awareness is what it can honour); Tk then sizes the fonts with the display's DPI
and dpi.px sizes the paddings, the wrap widths and the progress bar with it. The launcher makes its own process aware
the same way for its error box. tests/installer_gui_test.py checks that a fresh process ends up aware and that Tk's
scaling follows the display.
Co-Authored-By: DeepSeek V4.1 Flash 1017 (DeepSeek Harness) <noreply@deepseek.com>
A connection cut mid-file raises http.client.IncompleteRead, which is not an OSError, so it failed the whole setup at
the DLSS step instead of warning. The download box is ticked by default, so every player without an RTX GPU got a
warning for a download they never chose; that case is a log line now. The game file dialog keeps its "All files"
filter: the check tells an NSZ or XCZ by its content, not its name.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
dlaa and the DLSS modes need nvngx_dlss.dll next to totk.exe, which the release does not ship, so most players
never had DLSS. A new setup step after prepare downloads NVIDIA's own copy from the DLSS SDK repository
(github.com/NVIDIA/DLSS, tag v310.9.1, NVIDIA RTX SDK license) and checks its size and SHA-256.
The window has a tick box for it, on by default and named with its source and licence; the CLI takes --dlss. The
step runs on Windows with an NVIDIA RTX GPU (the host check's Vulkan devices), keeps a DLSS library the player
already put there, and on a failed download warns and lets the game run without DLSS; the next setup run tries
again. Texts in English, Russian, Brazilian Portuguese and Simplified Chinese, docs/installer.md step 7, tests in
tests/installer_test.py (DlssTest, with a file: URL).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Releases before 0.3.1 recorded the 0x20000 update as 1.1.1. After the rename (#371) nothing mapped the recorded
name, so an update of such an install asked make_romfs_patches.py for --version v111 and failed at the end (#332);
in a fresh setup folder it would not find expected-v1.1.1.json at all. migrate_state maps the old name before the
plan and gives the extract record the key of the renamed expected file; the full check of kept output still hashes
the extraction against it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The check step tells a compressed file by its content (an NSP or XCI holding NCZ files), whatever its name, and
unpacks it into the temp folder with tools/ncz.py before the extraction reads it. Every NCA comes back byte for byte
and is checked against the hash in its name. The file dialog has one filter for all game files, and the form says
that compressed files are unpacked automatically, in every setup language.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The lift key hashed each cheat and ExeFS patch file by its name and content only, but tools/cheats.py and
tools/exefs_patches.py name every patch set after "<mod folder>/<cheat or file>". After a mod folder was renamed,
totk.exe saw none of its code cheats in the build and started the rebuild, the setup found the lift current and skipped
it, and the cheats stayed "after the rebuild" for good. The key now takes each file's path under mods/ as well.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The rebuild command puts the release folder on sys.path itself: totk.exe starts it in the install folder, and only the
Windows release Python's ._pth names that folder. In a repository the window and its setup run use the Python that
runs the setup, since the tools' embeddable Python there has neither tkinter nor the repository on its path.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A mod that patched the game code (.ips, .pchtxt) stayed off as a whole.
tools/exefs_patches.py now reads IPS, IPS32 and IPSwitch files from each
mod's exefs folder and from atmosphere/exefs_patches/<name>/. It keeps the
ones whose build id is main's and turns their writes into main's code into
one patch set per file. The writes go through the same rules as the code
cheats (tools/cheats.py convert). A file that writes main's data, or that the
rules cannot express, is unsupported as a whole. The setup's lift runs it
after cheats.py and writes build-cache/exefs.status.
runtime/mods.c checks each patch file against that status:
- a built file's set is switched on with its mod;
- a file the build lacks keeps the mod off and starts the rebuild at the next
start, like a code cheat does;
- an unsupported file keeps the mod off, and the Mods tab shows the reason;
- a file for another version is counted and left out.
A mod patch on a site of one of the port's own sets turns that set off while
the mod is on, and the Mods tab names it. The Mods tab also gets the "Rebuild
the game now" row.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Code cheats need the game lifted and compiled with them. Running the setup
again did that, but it lifted and compiled everything and prepared the
shaders again.
- The setup keeps the generated C and the objects in its temp folder (about
1.3 GB). gen.py rewrites only the chunks whose C changed, so Ninja
recompiles only those. One new code cheat on 1.4.3 took a 47 s lift and
19 s of compile and link on 24 cores.
- Prepare's key no longer follows the compile key, only the runtime, the
translator and the game version, so a rebuild keeps the prepared shaders.
- The cleanup writes build-cache/rebuild.txt, the command that opens the
setup window in a new --rebuild mode on the install folder. That mode runs
at once and starts the game again when it succeeds.
- At start, runtime/cheats.c runs that command and exits when an enabled
mod has code cheats the build lacks (plat_spawn, new for Windows and
Linux). build-cache/rebuild.skip keeps a failed or cancelled rebuild from
starting again for the same cheats. "Rebuild the game now" on the Cheats
tab tries again, and [mods] mods_rebuild = off turns the rebuild off.
Scripted runs never start it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>