fix: hardening part 3, updates, shared files, guest buffers and texture sizes #1684

Merged
hckrman101 merged 7 commits from hardening-part3 into main 2026-11-10 15:46:51 +00:00
hckrman101 commented 2026-11-10 13:27:51 +00:00 (Migrated from github.com)

Linux players on v2.0.12 and v2.0.13 cannot update: every download the builder makes fails before it connects, so no fix reaches them. This PR repairs that, and is the third hardening pass after #1398 and #1422. It covers what has landed since and the parts those passes did not reach: files players share, the updater, the OS layer and the renderer. Each commit is one topic.

Issues

  1. Linux updates: every download failed (high: no update reaches players).

    • What: linux_update._request passed the URL as urllib.request.Request's second argument, which is the POST body. Every request became a POST with a str body, and urlopen raised TypeError before connecting. The effects:

      • latest_tag() returned nothing, so the builder acted as if it were offline;
      • --update failed with "update-error";
      • the in-game offer (curl) still appeared, but pressing Update failed.

      This came in with #1470 and shipped in v2.0.12.

    • Who: every AppImage player.

    • Fix: the request has no body.

    • Test: test_linux_update.py test_a_download_is_a_get (it fails on main).

  2. Linux updates: a link chain could leave the install folder (medium, defence in depth).

    • What: _symlink checked a link's target as written, with abspath. x/../.. looks like it stays inside the folder, but the system resolves x first, and the same zip or a later part can make x a link afterwards. With such a chain, AppRun -> a/.../L and _finish_tree's copyfile plus chmod 0755 wrote an executable file anywhere in the home folder.
    • Who: a crafted part in a list. Parts are hash-pinned in the release's list, so this keeps the rule that a part cannot write outside the install, rather than closing a remote hole.
    • Fix:
      • a link may use .. only as its leading parts;
      • every link in the finished tree must resolve inside it before the tree is used;
      • AppRun, the icon and the desktop file are never written through a link.
    • Test: test_a_link_chain_cannot_leave_the_staging_tree, with two chains through the real install(). Both fail on main, and the canary file is overwritten. Toolchain-style ../lib/x links still unpack.
  3. Linux updates: the requested release is the one installed (low).

    • What: when an update asked for a tag, the builder installed whatever list GitHub's "latest" link served. Right after a publish, that link lags and serves the release before, which reinstalled the old tools. Windows already refuses this case in setup.c and Updater.cs.
    • Fix: the same refusal on Linux, with the same message.
  4. Linux updates: file modes from a part (low).

    • What: group- and world-writable modes in a zip were kept as given.
    • Fix: files are 0755 or 0644, the modes make_parts.py writes.
    • Test: test_unpacked_files_are_not_group_or_world_writable.
  5. Windows setup: an unchecked zip could be unpacked (low).

    • What: after the hash check on <sha>.zip.part, DeleteFileW(zip); MoveFileW(part, zip) ignored both results. An old <sha>.zip that could not be removed (locked by an antivirus, or read-only) was then unpacked in place of the checked one.
    • Fix: MoveFileExW(..., MOVEFILE_REPLACE_EXISTING), and stop if it fails.
    • Tested: cross-compiled with llvm-mingw.
  6. hookshot_env.txt could overwrite game files and press buttons (medium).

    • What: #1398/#1422 refuse names that end in _FILE/_DIR and values that are paths. Several test switches name a file or folder under other names, and a bare name resolves in the game folder:

      • HOS_AUDIO_DUMP=image.bin truncated the game's image at the first sound, so the next start failed until a rebuild;
      • RX_VK_HANG_DUMP=hookshot_options.ini overwrote the options file;
      • RX_VK_SHOTS=user with a large RX_VK_SHOT_W/H filled the disk.

      HOS_INPUT_SCRIPT and RX_VK_MENU_SCRIPT press game buttons and Insert menu keys on a timer, for example walking the file select to Erase.

    • Who: anyone who hands a player a hookshot_env.txt, which players are asked to share.

    • Fix: these switches take no value from the player's file. HOS_ENV_FILE's file still sets anything. The readme says so.

    • Test: test_env_file.py, with new lines for each case.

  7. The NV12 reader could copy any host memory to the game (high, for a game led astray).

    • What: InitializeDecoderImagePool keeps the pool when its buffers fail to allocate (#342 guarded only the decode path). ReadDecoderOutputImageNv12 then read NULL + i*w*h*3/2 + y*w + x, which can be any host address, and copied it into guest memory. A fixture read a host string back this way.
    • Who: guest code led astray by crafted data, such as a save. This is the class #880 limits for lifted code; host code does its own reads.
    • Fix: no read without the NV12 buffer. A pool whose object cannot be allocated stops with a message instead of writing through NULL.
  8. nn::fs reads and writes, and the libc copy hooks, stayed unbounded (medium).

    • What:
      • read_at wrote up to 1 GB chunks at GP(buf + done), and WriteFile read from GP(buf), for any 64-bit buf and size;
      • the memcpy, memmove and memset hooks passed guest addresses and lengths straight to the host's own calls;
      • GetFileSize on a folder's handle answered with 8 bytes of host stack.
    • Fix:
      • hos_guest_range on each file buffer (a write larger than a DWORD is refused, not cut);
      • the hooks hand a range outside the window back to the game's own lifted copy (one compare each);
      • the size starts at 0.
    • Test: tests/test_guest_copies.py (new) builds nn_libc.c over a small window with a watched page after it, and checks the fs and NV12 guards in the source.
  9. A texture's storage size wrapped in 32 bits (medium: multi-GB allocations, sizes past the GPU's limits).

    • What: nvn_layout.c worked out the level sizes in uint32_t. A width of 0x40000001 RGBA8 texels came out as 512 bytes of storage, so the game allocated 512 bytes. The renderer then made a 4 GB-wide VkImage (beyond the device's limits, which vkCreateImage does not define) and asked for multi-GB staging and scratch buffers (bad_alloc). A level count near 2^32 also looped for minutes, and shifts of 32 or more were undefined.

    • Who: a texture whose size comes from game data (a mod, or a file the game reads).

    • Fix:

      • the layout and the linear stride are computed in 64 bits, and at most 32 levels are walked;
      • create() refuses a size, layer count or sample count the GPU cannot make (texture_size_ok).

      For ordinary sizes the layout is unchanged: 529,200 descriptions (formats, targets, sizes, levels, samples, MINIMAL_LAYOUT) give the same sizes, tiles and level offsets as before.

    • Test: tests/test_texture_layout_sizes.py (new) builds the real nvn_layout.c and the cut-out texture_size_ok. It fails on main.

  10. Cached descriptor counts and noted formats went to the driver unchecked (medium if a cache is shared, else low).

    • What:
      • a hookshot_programs.bin record with two descriptor counts of 2^31 wrapped ensure_layout's uint32_t total to 0. That made a push layout with billions of descriptors, and the bind loop ran that many times;
      • the pipeline state list, which is meant to be shareable, and the pipeline notes passed any number to the driver as a VkFormat for colour and depth targets. Linux has no crash guard, so the preparation crashed at every start until the file was deleted.
    • Fix:
      • a record whose descriptor arrays exceed 4096 is left out and translated again, and the total is 64-bit;
      • formats must be ones this GPU draws to or uses as depth (formats_ok, beside targets_ok).
    • Test: test_program_cache_reader.py test_descriptor_counts_are_bounded and test_pipeline_state_counts.py (formats the device does not draw to). program_cache_cleanup_test.cpp's stand-in descriptors now have a count.
  11. Smaller checks (low):

    • prompts.c bind_font:
      • so + 14 > len wrapped for a subtable offset near 2^32, and the check then read 4 GB past the font (a SEGV in the test). It needs a changed prompts/<face>/External.bfotf. test_prompt_font_bind.py has a new case, which crashes on main;
      • cmap4_bind now checks the same span as cmap4_glyph.
    • Microphone name: a microphone's name, which comes from SDL and can be set by any local audio source or a USB device, went into hookshot_options.ini as microphone_device=. A line break in it added lines to the file. mic_key turns control characters into spaces (test_menu_utf8.py).
    • rx_backend.c load_vulkan: strcpy of hookshot_vk.dll over hookshot.exe overran path[MAX_PATH] for an exe path near 260 characters. It now refuses a name that does not fit.
    • hos_sdl.c load_one: when GetModuleFileNameA failed, the bare SDL3.dll went to LoadLibraryA (current folder, PATH). It now goes straight to the System32/default-dirs lookup.
    • vk_dlss.cpp: when the game folder could not be found, game_dir() was empty and NGX searched and wrote a relative ngx folder in the current directory. NGX is not started then.
    • discord_rpc.c: the pipe is opened with SECURITY_SQOS_PRESENT | SECURITY_IDENTIFICATION, so a program that made a pipe of Discord's name first cannot impersonate the game.
  12. Building from extracted folders followed links (low).

    • What: link_or_copy (os.link, copy2) and copytree follow symlinks. A link in --romfs/--exefs put the file it named, from anywhere on the PC, into the game folder.
    • Fix: refuse_dump_links stops the build with a message. The folders themselves may still be links.
    • Test: test_make_game_cache.py test_links_in_an_extracted_dump_are_refused.

Not changed

Checked and found fine, or left for a decision:

  • CI:

    • every action is pinned by commit;
    • no pull_request_target or workflow_run;
    • no ${{ }} of untrusted input in a run:;
    • contents: read by default, with write only in the release jobs (tags);
    • checkouts use persist-credentials: false;
    • a tag build restores nothing from caches other runs write (only the hash-checked downloads).

    The release workflow's npm install without a lock file was noted in #1398.

  • pip installs (decision): deps.sh and the Windows Python part install pinned versions without --require-hashes (nx2/prompt_requirements.txt too). A hijacked PyPI account can add a new, more specific wheel for a pinned version. Hash pins for every wheel and platform, plus --no-deps, would close it. That changes the release's dependency step, which I could not run here. The dav1d, Vulkan, SPIR-V and VMA archives without hashes are as #1422 noted.

  • Source-checkout fallbacks on Windows: C:\emsdk and the old ../Eglantine and ../mkw-recomp CPM caches are used when nothing else is set. For a checkout at C:\Hookshot, those resolve to folders any local user can create. The builders set EMSDK and RX_CPM themselves, so players are not affected. Dropping the fallbacks is a maintainer call (design.md names Eglantine's cache as a source).

  • Lifetime races a game led astray could use (two threads on one object). Each needs its own change and testing:

    • CloseFile/CloseDirectory against a read in progress;
    • opus Finalize against Decode;
    • video pool and decoder Finalize against their readers;
    • fibers switched to while running elsewhere;
    • concurrent DestroyThread.
  • Read-only libc hooks: memcmp, strlen and strcmp still read guest addresses unbounded. They only read, and #880 covers the addressing.

  • Program cache SPIR-V: a record's SPIR-V still goes to the driver unvalidated, and a cache from someone else is trusted once its stamp matches. An install key in the stamp remains open (#1422).

  • Renderer, game-code values (not file data):

    • a linear texture with a small explicit stride can still make a 4 GB image within the GPU's limits;
    • texture_read_texels uses a 32-bit byte counter (a hang with a 4 GB guest buffer);
    • negative window crops reach vkCmdBlitImage;
    • c1's uniform range is not clamped to maxUniformBufferRange.
  • Eden's UNREACHABLE() is __builtin_unreachable(). It is not reachable from game data today, since lowering never passes raw ISBERD bits. Throwing there would be safer, but it is third-party code.

  • Linux updater: _unpack reads each member into memory (a hash-pinned part only). The Windows Updater.cs does not cap a download with no Content-Length. reuse_part trusts the previous version's parts.txt.

  • Discord on Linux falls back to $TMPDIR or /tmp for its socket, as Discord itself does. A local user who made that socket learns the pid and version.

  • Fine:

    • the ImGui menu uses no format strings with game or user text (everything goes through AddText), and its fixed buffers fit;
    • options-file values are clamped before use as indices;
    • RX_VK_MENU_SCRIPT parsing, rumble report sizes and handles, and Discord frame lengths are fine;
    • hos_update.c passes only safe_tag values to zenity and exec;
    • NUP extraction is fine;
    • ngx_sdk.py and the hash-checked downloads in deps.sh are fine;
    • FSR is compiled in, with no DLL;
    • the game font comes from the player's own dump.

How it was tested

  • python -I -B tests/run_all.py: 254 passed, 7 skipped (no GPU, game or .NET here).
  • Each new test was run against main's code and fails there:
    • the download method, the link chains and the file modes (test_linux_update.py);
    • the font offset;
    • the guest copies;
    • the texture sizes.
  • Linux build of the game and hookshot_vk.so: a full build of the game and hookshot_vk.so passes. The ABI check is clean, and the shader preparation at the end of the build ran through the changed cache and state-list readers.
  • Windows: hookshot_vk.dll cross-built with llvm-mingw, with NVIDIA's NGX SDK, so vk_dlss.cpp was compiled. The changed host files and builder/setup/setup.c pass a syntax check with llvm-mingw's clang.
  • The layout change was compared with main's nvn_layout.c over 529,200 texture descriptions. No size, tile or level offset differs.
  • No game run. The changes refuse inputs that a normal game, cache or update does not produce.
Linux players on v2.0.12 and v2.0.13 cannot update: every download the builder makes fails before it connects, so no fix reaches them. This PR repairs that, and is the third hardening pass after #1398 and #1422. It covers what has landed since and the parts those passes did not reach: files players share, the updater, the OS layer and the renderer. Each commit is one topic. ## Issues 1. **Linux updates: every download failed (high: no update reaches players).** - **What:** `linux_update._request` passed the URL as `urllib.request.Request`'s second argument, which is the POST body. Every request became a POST with a `str` body, and `urlopen` raised `TypeError` before connecting. The effects: - `latest_tag()` returned nothing, so the builder acted as if it were offline; - `--update` failed with "update-error"; - the in-game offer (curl) still appeared, but pressing Update failed. This came in with #1470 and shipped in v2.0.12. - **Who:** every AppImage player. - **Fix:** the request has no body. - **Test:** `test_linux_update.py test_a_download_is_a_get` (it fails on main). 2. **Linux updates: a link chain could leave the install folder (medium, defence in depth).** - **What:** `_symlink` checked a link's target as written, with `abspath`. `x/../..` looks like it stays inside the folder, but the system resolves `x` first, and the same zip or a later part can make `x` a link afterwards. With such a chain, `AppRun -> a/.../L` and `_finish_tree`'s `copyfile` plus `chmod 0755` wrote an executable file anywhere in the home folder. - **Who:** a crafted part in a list. Parts are hash-pinned in the release's list, so this keeps the rule that a part cannot write outside the install, rather than closing a remote hole. - **Fix:** - a link may use `..` only as its leading parts; - every link in the finished tree must resolve inside it before the tree is used; - AppRun, the icon and the desktop file are never written through a link. - **Test:** `test_a_link_chain_cannot_leave_the_staging_tree`, with two chains through the real `install()`. Both fail on main, and the canary file is overwritten. Toolchain-style `../lib/x` links still unpack. 3. **Linux updates: the requested release is the one installed (low).** - **What:** when an update asked for a tag, the builder installed whatever list GitHub's "latest" link served. Right after a publish, that link lags and serves the release before, which reinstalled the old tools. Windows already refuses this case in `setup.c` and `Updater.cs`. - **Fix:** the same refusal on Linux, with the same message. 4. **Linux updates: file modes from a part (low).** - **What:** group- and world-writable modes in a zip were kept as given. - **Fix:** files are 0755 or 0644, the modes `make_parts.py` writes. - **Test:** `test_unpacked_files_are_not_group_or_world_writable`. 5. **Windows setup: an unchecked zip could be unpacked (low).** - **What:** after the hash check on `<sha>.zip.part`, `DeleteFileW(zip); MoveFileW(part, zip)` ignored both results. An old `<sha>.zip` that could not be removed (locked by an antivirus, or read-only) was then unpacked in place of the checked one. - **Fix:** `MoveFileExW(..., MOVEFILE_REPLACE_EXISTING)`, and stop if it fails. - **Tested:** cross-compiled with llvm-mingw. 6. **`hookshot_env.txt` could overwrite game files and press buttons (medium).** - **What:** #1398/#1422 refuse names that end in `_FILE`/`_DIR` and values that are paths. Several test switches name a file or folder under other names, and a bare name resolves in the game folder: - `HOS_AUDIO_DUMP=image.bin` truncated the game's image at the first sound, so the next start failed until a rebuild; - `RX_VK_HANG_DUMP=hookshot_options.ini` overwrote the options file; - `RX_VK_SHOTS=user` with a large `RX_VK_SHOT_W/H` filled the disk. `HOS_INPUT_SCRIPT` and `RX_VK_MENU_SCRIPT` press game buttons and Insert menu keys on a timer, for example walking the file select to Erase. - **Who:** anyone who hands a player a `hookshot_env.txt`, which players are asked to share. - **Fix:** these switches take no value from the player's file. `HOS_ENV_FILE`'s file still sets anything. The readme says so. - **Test:** `test_env_file.py`, with new lines for each case. 7. **The NV12 reader could copy any host memory to the game (high, for a game led astray).** - **What:** `InitializeDecoderImagePool` keeps the pool when its buffers fail to allocate (#342 guarded only the decode path). `ReadDecoderOutputImageNv12` then read `NULL + i*w*h*3/2 + y*w + x`, which can be any host address, and copied it into guest memory. A fixture read a host string back this way. - **Who:** guest code led astray by crafted data, such as a save. This is the class #880 limits for lifted code; host code does its own reads. - **Fix:** no read without the NV12 buffer. A pool whose object cannot be allocated stops with a message instead of writing through `NULL`. 8. **nn::fs reads and writes, and the libc copy hooks, stayed unbounded (medium).** - **What:** - `read_at` wrote up to 1 GB chunks at `GP(buf + done)`, and `WriteFile` read from `GP(buf)`, for any 64-bit `buf` and size; - the `memcpy`, `memmove` and `memset` hooks passed guest addresses and lengths straight to the host's own calls; - `GetFileSize` on a folder's handle answered with 8 bytes of host stack. - **Fix:** - `hos_guest_range` on each file buffer (a write larger than a DWORD is refused, not cut); - the hooks hand a range outside the window back to the game's own lifted copy (one compare each); - the size starts at 0. - **Test:** `tests/test_guest_copies.py` (new) builds `nn_libc.c` over a small window with a watched page after it, and checks the fs and NV12 guards in the source. 9. **A texture's storage size wrapped in 32 bits (medium: multi-GB allocations, sizes past the GPU's limits).** - **What:** `nvn_layout.c` worked out the level sizes in `uint32_t`. A width of `0x40000001` RGBA8 texels came out as 512 bytes of storage, so the game allocated 512 bytes. The renderer then made a 4 GB-wide `VkImage` (beyond the device's limits, which `vkCreateImage` does not define) and asked for multi-GB staging and scratch buffers (`bad_alloc`). A level count near 2^32 also looped for minutes, and shifts of 32 or more were undefined. - **Who:** a texture whose size comes from game data (a mod, or a file the game reads). - **Fix:** - the layout and the linear stride are computed in 64 bits, and at most 32 levels are walked; - `create()` refuses a size, layer count or sample count the GPU cannot make (`texture_size_ok`). For ordinary sizes the layout is unchanged: 529,200 descriptions (formats, targets, sizes, levels, samples, MINIMAL_LAYOUT) give the same sizes, tiles and level offsets as before. - **Test:** `tests/test_texture_layout_sizes.py` (new) builds the real `nvn_layout.c` and the cut-out `texture_size_ok`. It fails on main. 10. **Cached descriptor counts and noted formats went to the driver unchecked (medium if a cache is shared, else low).** - **What:** - a `hookshot_programs.bin` record with two descriptor counts of 2^31 wrapped `ensure_layout`'s `uint32_t` total to 0. That made a push layout with billions of descriptors, and the bind loop ran that many times; - the pipeline state list, which is meant to be shareable, and the pipeline notes passed any number to the driver as a `VkFormat` for colour and depth targets. Linux has no crash guard, so the preparation crashed at every start until the file was deleted. - **Fix:** - a record whose descriptor arrays exceed 4096 is left out and translated again, and the total is 64-bit; - formats must be ones this GPU draws to or uses as depth (`formats_ok`, beside `targets_ok`). - **Test:** `test_program_cache_reader.py test_descriptor_counts_are_bounded` and `test_pipeline_state_counts.py` (formats the device does not draw to). `program_cache_cleanup_test.cpp`'s stand-in descriptors now have a count. 11. **Smaller checks (low):** - **`prompts.c` `bind_font`:** - `so + 14 > len` wrapped for a subtable offset near 2^32, and the check then read 4 GB past the font (a SEGV in the test). It needs a changed `prompts/<face>/External.bfotf`. `test_prompt_font_bind.py` has a new case, which crashes on main; - `cmap4_bind` now checks the same span as `cmap4_glyph`. - **Microphone name:** a microphone's name, which comes from SDL and can be set by any local audio source or a USB device, went into `hookshot_options.ini` as `microphone_device=`. A line break in it added lines to the file. `mic_key` turns control characters into spaces (`test_menu_utf8.py`). - **`rx_backend.c` `load_vulkan`:** `strcpy` of `hookshot_vk.dll` over `hookshot.exe` overran `path[MAX_PATH]` for an exe path near 260 characters. It now refuses a name that does not fit. - **`hos_sdl.c` `load_one`:** when `GetModuleFileNameA` failed, the bare `SDL3.dll` went to `LoadLibraryA` (current folder, PATH). It now goes straight to the System32/default-dirs lookup. - **`vk_dlss.cpp`:** when the game folder could not be found, `game_dir()` was empty and NGX searched and wrote a relative `ngx` folder in the current directory. NGX is not started then. - **`discord_rpc.c`:** the pipe is opened with `SECURITY_SQOS_PRESENT | SECURITY_IDENTIFICATION`, so a program that made a pipe of Discord's name first cannot impersonate the game. 12. **Building from extracted folders followed links (low).** - **What:** `link_or_copy` (`os.link`, `copy2`) and `copytree` follow symlinks. A link in `--romfs`/`--exefs` put the file it named, from anywhere on the PC, into the game folder. - **Fix:** `refuse_dump_links` stops the build with a message. The folders themselves may still be links. - **Test:** `test_make_game_cache.py test_links_in_an_extracted_dump_are_refused`. ## Not changed Checked and found fine, or left for a decision: - **CI:** - every action is pinned by commit; - no `pull_request_target` or `workflow_run`; - no `${{ }}` of untrusted input in a `run:`; - `contents: read` by default, with `write` only in the release jobs (tags); - checkouts use `persist-credentials: false`; - a tag build restores nothing from caches other runs write (only the hash-checked downloads). The release workflow's `npm install` without a lock file was noted in #1398. - **pip installs (decision):** `deps.sh` and the Windows Python part install pinned versions without `--require-hashes` (`nx2/prompt_requirements.txt` too). A hijacked PyPI account can add a new, more specific wheel for a pinned version. Hash pins for every wheel and platform, plus `--no-deps`, would close it. That changes the release's dependency step, which I could not run here. The dav1d, Vulkan, SPIR-V and VMA archives without hashes are as #1422 noted. - **Source-checkout fallbacks on Windows:** `C:\emsdk` and the old `../Eglantine` and `../mkw-recomp` CPM caches are used when nothing else is set. For a checkout at `C:\Hookshot`, those resolve to folders any local user can create. The builders set `EMSDK` and `RX_CPM` themselves, so players are not affected. Dropping the fallbacks is a maintainer call (design.md names Eglantine's cache as a source). - **Lifetime races a game led astray could use** (two threads on one object). Each needs its own change and testing: - `CloseFile`/`CloseDirectory` against a read in progress; - opus `Finalize` against `Decode`; - video pool and decoder `Finalize` against their readers; - fibers switched to while running elsewhere; - concurrent `DestroyThread`. - **Read-only libc hooks:** `memcmp`, `strlen` and `strcmp` still read guest addresses unbounded. They only read, and #880 covers the addressing. - **Program cache SPIR-V:** a record's SPIR-V still goes to the driver unvalidated, and a cache from someone else is trusted once its stamp matches. An install key in the stamp remains open (#1422). - **Renderer, game-code values (not file data):** - a linear texture with a small explicit stride can still make a 4 GB image within the GPU's limits; - `texture_read_texels` uses a 32-bit byte counter (a hang with a 4 GB guest buffer); - negative window crops reach `vkCmdBlitImage`; - c1's uniform range is not clamped to `maxUniformBufferRange`. - **Eden's `UNREACHABLE()`** is `__builtin_unreachable()`. It is not reachable from game data today, since lowering never passes raw ISBERD bits. Throwing there would be safer, but it is third-party code. - **Linux updater:** `_unpack` reads each member into memory (a hash-pinned part only). The Windows `Updater.cs` does not cap a download with no `Content-Length`. `reuse_part` trusts the previous version's `parts.txt`. - **Discord on Linux** falls back to `$TMPDIR` or `/tmp` for its socket, as Discord itself does. A local user who made that socket learns the pid and version. - **Fine:** - the ImGui menu uses no format strings with game or user text (everything goes through `AddText`), and its fixed buffers fit; - options-file values are clamped before use as indices; - `RX_VK_MENU_SCRIPT` parsing, rumble report sizes and handles, and Discord frame lengths are fine; - `hos_update.c` passes only `safe_tag` values to zenity and exec; - NUP extraction is fine; - `ngx_sdk.py` and the hash-checked downloads in `deps.sh` are fine; - FSR is compiled in, with no DLL; - the game font comes from the player's own dump. ## How it was tested - `python -I -B tests/run_all.py`: 254 passed, 7 skipped (no GPU, game or .NET here). - Each new test was run against main's code and fails there: - the download method, the link chains and the file modes (`test_linux_update.py`); - the font offset; - the guest copies; - the texture sizes. - Linux build of the game and `hookshot_vk.so`: a full build of the game and `hookshot_vk.so` passes. The ABI check is clean, and the shader preparation at the end of the build ran through the changed cache and state-list readers. - Windows: `hookshot_vk.dll` cross-built with llvm-mingw, with NVIDIA's NGX SDK, so `vk_dlss.cpp` was compiled. The changed host files and `builder/setup/setup.c` pass a syntax check with llvm-mingw's clang. - The layout change was compared with main's `nvn_layout.c` over 529,200 texture descriptions. No size, tile or level offset differs. - No game run. The changes refuse inputs that a normal game, cache or update does not produce.
Commenting is not possible because the repository is archived.
No description provided.