Skip to content

Sync with upstream seedds/nxnative (48 commits, to 2026-10-28) - #2

Merged
zenvyzenshiro merged 51 commits into
mainfrom
sync-upstream
Oct 28, 2026
Merged

zenvyzenshiro merged 51 commits into
mainfrom
sync-upstream

Conversation

@zenvyzenshiro

@zenvyzenshiro zenvyzenshiro commented Oct 28, 2026 •

Copy link
Copy Markdown
Owner

Merges seedds/nxnative main (48 commits after the 2026-10-27 sync in #1), plus a Windows fix that #1 needs.

Conflicts resolved: README.md (the fork's), front_ios.m (the fork's data help screen), main.m (both the split-build data roots and upstream's romfs.id check).

Fix for main as of #1: upstream's committed dmsh_xlate.dll is an arm64 PE (machine 0xaa64). dotnet on Windows x64 refuses it ("assembly architecture is not compatible with the current process architecture"), so the shader translator did not run. Rebuilt from the in-repo source with dotnet build (machine 0x14c, like the DLL before the sync). The Ryujinx DLLs stay upstream's. The lock's dmsh_xlate.dll line and DMSH_CHAIN (4e91e9103b44) follow.

Checked on Windows 11 x64 (.NET 10.0.403, Vulkan SDK spirv-cross):

  • dmsh_chain.py prints 4e91e9103b44, matching the header.
  • dmsh_chain.py --check-tools: DLL fingerprints, xlate_source and spirv-cross match the lock. Only the two Metal compiler lines differ, because Metal Developer Tools for Windows are not installed on this PC (the lock's Metal lines are carried over, not re-measured).
  • win/tests/test_win.py: 11 tests OK.
  • Not run: a full IPA build (needs the iPhoneOS SDK and Metal tools).

🤖 Generated with Claude Code

seedds and others added 30 commits October 28, 2026 07:13
Size module tables from the NRO manifest, snapshot initialized data on first
load, and synchronize NRO-backed stack lifetimes. Preserve NRO headers during
rewriting and isolate NVN bindings by queue across launcher/game handoffs.

Read Phyre effects from Virtuos VBF archives, add integer sampler border
setters, and register the game profile and 5,752 shaders. Brief Mac validation
covers the FFX opening and X-2's first battle; iOS is built but untested.
The game note records tests, the Wonder route mismatch and remaining findings.

Refs seedds#372

Co-authored-by: Naced28 <105175582+Naced28@users.noreply.github.com>
…e failures by kind

The StandardAllocator cap was checked with main rewritten without its cut on Paper Mario:
The Thousand-Year Door 1.0.1 (62 GB -> 4.06 GB, frame 1,800 in 75 s) and Absolum 1.1.1.
Absolum's .NET garbage collector takes 1.5 GB after malloc, more than the 256 MB the
default leaves: the default 4.06 GB aborts at sdk+0x13a684, so its profile gets
stdalloc_cap 0xa0000000 (2.5 GB, its cut's size), with which it ran to frame 3,900; the
region check computes 2.72 GB from a 64 GB boot. Registered builds are unchanged.

regress.py matched a smoke failure the baseline had too as new when the number in its
parentheses differed by run (Pikmin 4's reach, "(max 4797)" against "(max 4774)"). It now
matches by kind: those numbers are dropped, words and exit statuses kept, so a crash with
another signal or another exit status is still new.

regress.py check --root /Volumes/S790/harness --games-dir /Volumes/S790/nsp, baseline
3283fee (before stage 1), candidate stage 1, headless, full routes: Pikmin 4 (2 runs each),
Super Mario Bros. Wonder, Super Mario Odyssey, Kirby and the Forgotten Land and Xenoblade
Chronicles 2 PASS, with the same shaders, interface and [RED] kinds and fps within noise
(Pikmin 4's route misses its reach on both revisions). The iOS app builds and links
(dm/ios/tools/build_smoke.py).
The test imports image and RET from test_nso_match, which is on the path only when the
tests folder is; python -m unittest <file>, as CI runs each test file, failed at the
import (since 3e77816). Its own folder goes on sys.path first.
Add the game profile, build-gated allocator cuts, recorded pipelines, and a
Mac gameplay screenshot. Read legacy and nested Snowdrop shader records,
ignore GLSL-only preambles, and follow checked jump-table index copies to
the function boundary. Fold absent secondary blend sources to the host's
zero choice so both Metal pipeline paths accept the first level.

Validation: full Python tool and graphics checks, native Metal suite,
brief World 1-1 movement and coin collection, and five-game quick
regressions (Three Houses, Sparks of Hope, Luigi's Mansion 3, Odyssey,
and Xenoblade Chronicles 2). Unsigned IPA built; iOS remains unvalidated.

Co-authored-by: seedds <seedds@gmail.com>
Pass the platform's Metal target from the NSP builder and skip the other
platform's base, fetch, pulling, integer-input and geometry libraries.

Keep MSL and reflection in every shader cache entry because cache hits,
paired vertex padding and profile tables still need those analysis files.
Apply target selection to side libraries, Metal cache prefill and reuse keys.
Regenerate the shared chain header and document the cache invariant.

Validated with 100 unit/documentation tests, the Python lint/type ratchet,
five-game shader/interface and quick-boot regression checks, byte-identical
Mac/iOS library comparisons, and platform-switch cache round trips.

Co-authored-by: JMRBDev <josemrb99@gmail.com>
Add a standalone diagnostic ZIP collector for logs, versions, device details
and settings without game assets. Read packaged-app provenance and shader
folder names instead of attributing the collector checkout to the app, and
include the bundle's environment settings alongside the player's overrides.

Count report.md and truncation markers within the byte limits, and refuse
explicit .keys inputs before opening them. App commits remain unknown when
the package has no stamp; adding producer stamps remains a follow-up.

Verified on a packaged Breath of the Wild 1.9.0 app and a freshly compiled
and packaged fixture. The focused collector and documentation tests pass,
with no new Python lint or type findings. The standalone collector does not
change game execution or the shader chain.

Based on PR seedds#465.

Co-authored-by: v0lumehi <aljic.zijad@gmail.com>
t_sparse_layout, t_timestamp, t_rt_depth_ro, t_rt_range_shape, t_ub, t_pso_pending and t_vk_desc
were compiled by the build check but never run. None goes through mtl_init; each runs in under a second.
Register Absolum 1.1.2 alongside 1.1.1 and update the package and shader
version metadata. The SDK and movie modules keep their existing build IDs.

Use the existing StandardAllocator symbol gate and stdalloc_cap for the
new main build instead of adding another fixed-address cut. Preserve the
original 1.1.1 cut, distinguish both builds' addresses, and record the
contributor's gameplay evidence and the open cap-only gameplay rerun.

Validation: all 414 local Python tool tests (the 7 initially skipped
translator cases passed separately with the committed compiler configured),
35 focused documentation/patch/profile tests, symptom-index and diff
checks, and four passing GitHub CI checks (lint, host/CPU graphics,
sanitizers and the unsigned iOS app). The tool and build-check jobs remained
queued in GitHub's Mac runner backlog at merge. No shared runtime behavior
changes.

Co-authored-by: Hanns Adrian Böhme <me@hannsadrian.de>
…#478)

Apply DM_COPY_ROMFS=NO to unsigned IPA packing as well as the Xcode resource phase. Keep add-on content bundled. Carry the requested entitlements in an ad-hoc executable signature for the sideloader to read; an empty DM_ENTITLEMENTS opts out.

Validated 43 focused iOS packing, Xcode workflow, documentation and environment tests. No on-device install or startup claim.

Co-authored-by: v0lumehi <aljic.zijad@gmail.com>
…s#493)

Add --romfs separate to unsigned IPA and prepared Xcode builds. Keep the extracted image available for one-time transfer and compare its size and header/index identity before using Documents/romfs.img. Keep bundle as the default and keep add-on content bundled.

Reject separate deployment with extraction cleanup before building, since cleanup otherwise deletes the image the printed push command needs. Reject mismatching images while retaining the existing validated folder fallback; regression tests reproduce both original failures.

Validate 451 tool tests (8 optional skips), 38 iOS tests, the actual prepared-project settings in both modes, 178 iOS compilation units, and the lint ratchet. Game regression and build results are recorded in the PR reply. Device startup and sideloader entitlement preservation are not claimed.

Co-authored-by: v0lumehi <aljic.zijad@gmail.com>
Hash the parsed Python code for CODE_FP so comments, docstrings and
formatting preserve per-shader keys while executable changes invalidate
them. Keep the target-selective builds from seedds#275 and regenerate the
shared chain header for the integrated source.

Checked 111 Python tests, the pinned lint/type ratchet, the Mac host and
the full Metal graphics suite. The five-game static comparison built
all 66,126 shaders on both revisions with no MSL, base-reflection or
interface regressions; 18 geometry JSON changes were chain stamps only.
The original PR's paired BotW comment-edit build fell from 246.230 s to
96.259 s, with all 19,592 shader-cache hits and zero retranslations.

Co-authored-by: v0lumehi <aljic.zijad@gmail.com>
Reconcile the current seedds#336 geometry, upload and resolution work with seedds#416's
lean passthrough and mesh paths on main. Preserve buffer dependencies,
query ordering, target selection and chain/cache validation.

Keep mesh primitive IDs draw-wide and rebase only local capture reads.
Snapshot recursive split descriptors before child draws reuse the shared
geometry job, fixing the Octopath first-attack index-range corruption.
Retain emitted-vertex feedback, pause/resume ordering and raster discard
through the new jobs without recapturing the synthetic raster reader.

Co-authored-by: JioChoi <jiochoi7@gmail.com>
Co-authored-by: ThrecL <91959522+ThrecL@users.noreply.github.com>
How to play now opens the page, above the games gallery: community Mac apps
(NX2Apple, NX Play, NxNative Launcher), then a three-step command-line build
and the install step. Known issues is gone (each is in its game note), and the
playing details moved to docs/playing.md. The text below the gallery is small
print, and the gallery's captions are too (docs_gallery.py), since GitHub keeps
its own line height in a README.
…p replaces

The host's StandardAllocator cap (stage 1) now does what three games' main cuts did, for
every build: Pokémon Legends: Z-A (12 GB -> 3.94 GB; its cut and its function-matched rule
go, and with them the profile's patches.py), Paper Mario: The Thousand-Year Door (62 GB ->
4.06 GB; its other cuts and frame-step stubs stay) and Absolum (40 GB -> 2.5 GB with its
stdalloc_cap; its patches.py goes). Pokémon Scarlet keeps its cut: no game files here to
check it on.

Checked with Mac builds from this tree (Mac shaders for the current chain): each game's
route from a fresh save into play, headless, on the same build with and without the cut.
Z-A 380 s into Lumiose City and a walk (frame 11,100 both), Paper Mario 480 s into
Rogueport (28,500 both), Absolum 320 s into the first level, walking and striking (18,300
/ 18,600): the same [RED] kinds, alias region use and memory mapped, and screenshots of the
same scenes (only animation and fade timing differ). Z-A's 4 GB cut had already been
lowered to 3.94 GB by the cap since stage 1.
When a window has no explicit crop, use the recorded VI layer crop for its
queued present. Link's Awakening requests this crop for its 1280x720
handheld picture inside 1920x1080 textures. Preserve explicit window crops
and check the fallback layer rectangle against the presented texture.

Keep regression coverage through the real QueuePresent path and document
the general mechanism and the author's iPhone gameplay result.

Validation on f838995 plus this change: crop 88/88, NVN 159/159, full Metal
graphics suite and 419 Python tool tests pass (16 skipped). Restoring the
original present call fails exactly the new layer-crop queue assertion.

Five-game comparison: Wonder and ANIMAL WELL pass. BotW's Zora save is
missing; a documented Great Plateau route passes the fixed-camera direct
presentation control. Scarlet has a prebuild timing flag and an isolated
runtime tail that did not reproduce. TotK reaches no frame on either
revision, so it has no runtime coverage. All 94,717 shader entries match
with no new build or interface failures. Full results are recorded on seedds#499.
The target game's 11-minute iPhone run remains author-reported.

Co-authored-by: Gabs <40191213+GabZoFar@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Every encoder end goes through m4_end, which called gp_end for any encoder,
including the upload, diagnostics and present encoders that are never
profiled. gp_end closed whichever main command buffer encoder gp_begin had
opened last and wrote its end timestamp through the encoder it was given.
An upload flush inside a profiled draw pass sent the render encoder's
timestamp selector to the upload compute encoder, and the host aborted on
an unrecognized selector (Tears of the Kingdom, DM_NVN_GPUPROF=every:300,
one run in four). Where the kinds matched, the pass lost its own end stamp
and its GPU time was wrong.

gp_begin now records the encoder it stamped, and gp_end ignores any other,
so the open pass keeps its label until its own end. General: no profile
key or build gate; nothing changes with the profiler off.

t_gpuprof ends an upload encoder inside a profiled pass with fakes that,
like Metal 4's encoders, answer only their own timestamp selector: 3 checks
fail with the exception before the change, 0 of 31 after. Docs checks pass.
No regress.py run: its runs do not enable the profiler, so they do not
reach the change. Not rerun in the game.

Co-authored-by: Rob Young <210684206+QID-Rob@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Carry primitive offsets through recursive range splits and preserve each
instance's own numbering. Pass the base in the geometry-info ABI and add it only to
the translated guest PrimitiveID; capture and output indices remain local.
Rebuild the translator, chain stamp and profile shader libraries together.

Cover actual Maxwell PrimitiveID loads with 40 native GPU cases and a failing
old-translator control, plus 160 nested CPU splitter cases. Keep current main
and the earlier reviewed NieR integration together for the main update.

Co-authored-by: JioChoi <jiochoi7@gmail.com>
Co-authored-by: ThrecL <91959522+ThrecL@users.noreply.github.com>
…s#530)

Every --romfs separate build printed a red line saying the extraction
held a RomFS folder and the build carried no image identity, with
recovery steps that did not apply. say_push_romfs asked rfs.source()'s
romfs.Path whether it was a directory, and romfs.Path shows an image's
root as a folder by design, so a real RomFS.img always matched.

The check now asks the filesystem whether the image is a regular file,
the same test build_ipa.sh uses before it writes romfs.id, so the warning
means what it says. romfs.Path is unchanged: bench.py, regress.py and
pack_app.py use its is_dir() to mean "a usable RomFS", which an image is.
General: no profile key or build gate.

test_nsp_romfs now passes a real packed image through rfs.source(), as
main does (the old test used an empty file, which romfs.Path does not
treat as an image): it prints the issue's red line before the change and
passes after. 159 NSP and RomFS tool tests pass. No full NSP build run;
no regress.py run, since only a closing message of the script changes.

Co-authored-by: Ishtiaq Ul Haq Syed <25985759+ishtiaq156@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
A derived build (a version the profile was not brought up on) has none of
the registered builds' fixed-address cuts, only the function-matched ones,
so a region those cuts shrank now shows at pack time rather than as an
abort on the iPhone (GitHub issue seedds#375). The stamp names the rewrite it
measured, so other rewritten modules (a new cut) check again.

Tried on Pokémon Legends: Z-A 1.0.0 packed alone: its 12 GB
StandardAllocator region, which the host gives 3.94 GB. Smash 1.0.0 does
not reach the check (smash_arc.py's table format, a jump table in its
main): recorded as open in its note.
Preserve main's allocator cleanup and VI layer crop handling alongside the
NieR integration and generalized compute primitive-ID correction.

Recheck the merged NVN/presentation paths (169/88 native checks), the 40-case
compute-ID GPU replay, Mac host build, 178 iOS source compilation checks,
and 25 documentation/patch-table tests.

Co-authored-by: JioChoi <jiochoi7@gmail.com>
Co-authored-by: ThrecL <91959522+ThrecL@users.noreply.github.com>
Keep the GPU-profiler encoder ownership and RomFS warning fixes from main.
Their focused checks pass (31 native profiler checks, 14 Python tests), as
do the Mac host build and 178-source iOS compilation check after merging.

The compute primitive-ID follow-up compares 86,058 shaders against 2c31212:
NieR for mesh eligibility, Octopath II for its large compute split,
Xenoblade 2 for another geometry engine, Odyssey for shared NVN state,
and Kirby for geometry/upload integration. No shader or interface failures,
MSL or reflection changes; all variant differences are chain metadata.
The full Octopath first-attack gameplay comparison also passes. The other
four gameplay routes retain the earlier integration's validation.

Co-authored-by: JioChoi <jiochoi7@gmail.com>
Co-authored-by: ThrecL <91959522+ThrecL@users.noreply.github.com>
Preserve c00b81c while completing the reviewed geometry update to main.
The incremental NSP, RomFS and documentation tests pass after merging.
This packaging-only update does not change the host or shader chain.

Co-authored-by: JioChoi <jiochoi7@gmail.com>
Co-authored-by: ThrecL <91959522+ThrecL@users.noreply.github.com>
…dds#347, seedds#348)

In fullscreen the system shows the layer directly, and at frame latency 1 the display link calls back only one 60 Hz period (16 ms) before the target display time. On ProMotion built-in screens (an M4 Pro here, the reporter's M5 Max) it then shows some presents one 120 Hz refresh late and skips the link's next tick, though the callbacks run on time (16.2-16.6 ms ahead), the present takes about 0.1 ms and the misses do not follow GPU load. Frames that wait for the skipped tick start late, and the frame step read three in a row as load: 1 -> 2 -> 3 vsyncs on frames costing 0.1-0.6 vsyncs, 15 presents a second (TotK here, MK8DX in seedds#348).

link_latency asks for latency 2 (33 ms) while front_mac.m's front_fullscreen is set and 1 in a window; a new link takes the current one and logs it, and link_sync sets it again only when the window has entered or left fullscreen. DM_PRESENT_LATENCY=<n> still sets one latency for both. It is a workaround: why the system misses at 16 ms is open (docs/reference/frame-rate.md). One frame more input lag, in fullscreen only; iOS keeps latency 1.

Measured (TotK 1.4.3, 180 s fullscreen runs, about 9,600 presents each): latency 1, 68 / 100 presents a refresh late, 70 / 93 skipped ticks, one run with 4 cheap step-ups; latency 2, 17-19 late, no cheap step-ups outside an unrelated game stall; the final code started in fullscreen, 1 late present and 1 skipped tick in 3,724 frames. Reporter's M5 Max: 49 skipped ticks in 2,306 frames at latency 1, 4 in 5,328 at latency 2. A 120 Hz link (DM_PRESENT_LINK=2) avoided the cheap step-ups but showed 102-209 presents a refresh late.

Checked: t_present_rescue (6 new checks: window, enter, leave, a link created in fullscreen, DM_PRESENT_LATENCY in both; 49/49, 53/53 with --metal), t_link_ticks, t_present_alloc, t_present_crop, t_present_snap suspend/pacing/copy, test_docs; the Mac host builds. Not checked: entering and leaving fullscreen while a game runs (macOS refused scripted keystrokes); scripts/regress.py (another session's harness held the games, and this session's sandbox cannot signal the harness's process groups).
can_clone found its test file with os.walk, which yields nothing for a
file, so since the RomFS became one image (e09ff95) every Mac app pack
decided it could not clone: the free-space check asked for a full copy
of the game files (40.3 GB for Metroid Prime 4: Beyond on a volume with
26 GB free) although cp -c would have cloned them. A file source is now
tested as itself (seedds#518).

The RomFS probe also decided for the rewritten modules, the shader
libraries and the DLC, while the pipeline archive had its own. As cp -c
copies whatever it cannot clone, the probes decide only the free-space
check: each source now gets its own, and the check counts only what will
be copied (the modules were never counted).

Tests: a real-clonefile probe of an image, a folder and an empty folder,
and a pack whose RomFS clones while the rest is on another volume; both
fail on the old code. test_nsp_mac_app, test_bug_report,
test_nsp_incremental and test_docs pass. No real game packed.

Co-authored-by: Lilun Xu <173143200+lx7720@users.noreply.github.com>
PR seedds#397 correctly identifies instruction-like bytes in NRO metadata. Its
linear-scan cutoff still lets per-function analysis reenter the header when
code precedes the first unwind entry, failing with "jump table 44".

Keep the shared data-block exclusion already on main and extend the real
rewriter regression to cover this path. Document that both scans and function
analysis must exclude metadata. The new test fails against the original PR
and passes with the existing implementation; 108 related and documentation
tests pass. Production code is unchanged.

Refs seedds#397

Co-authored-by: v0lumehi <aljic.zijad@gmail.com>
The session used AVAudioSessionCategorySoloAmbient, which iOS silences
with the ring / silent switch: every game was mute whenever the phone
was set to silent, though the volume was up (seedds#155's commenter has to
toggle the switch). front_ios_audio_session now sets Playback, the
category for sound central to an app: the switch no longer mutes it,
other apps' audio is still interrupted, and the host still suspends its
output in the background (no background audio mode is declared). The
volume buttons still silence the game. General: the session is the
app's, so this holds for every game; the Mac has no audio session.

docs/reference/audio.md has the entry, with the symptom index
regenerated, seedds#155 linked and a line in Breath of the Wild's note. The
iOS compile check (178 files) and the documentation tests pass; not yet
heard on a device with the switch on silent.

Refs seedds#155 (seedds#414)

Co-authored-by: Max Davidson <davidsonmax@gmail.com>
The idle-release test assumed dispatch_after had delivered its callback after
sleeping for 1.1 seconds. A delayed callback on CI left the fake player's stop
count at zero even though the adapter stopped it correctly once delivered.

Advance a virtual monotonic clock and deliver scheduled callbacks on the real
serial queue. Cover the idle deadline explicitly and retain pulse renewal,
stale callbacks, failure recovery and routing checks.

Validated with the complete host unit suite, ASan/UBSan and TSan for the Apple
haptics test, 100 repeated adapter runs, and documentation/NRO tests. Mutation
checks catch missing, early and stale idle stops. No game runs are needed for
this test-only scheduling change.
Add opt-in quick/full validation records and show them in regress.py list,
without treating validation metadata as a profile input. Require successful
boot runs as well as PASS/CHANGED: an unchanged baseline crash is not proof
that the candidate booted. Derived build profiles cannot validate repository
profiles, and completed reports survive a validation metadata write failure.

Exercise a full ANIMAL WELL check --record on Apple M4 Pro / macOS 27.2:
46 shaders unchanged, 60 fps, no new RED lines and screenshots in tolerance.
Record the tested chain and document the result. Python tools (478 tests,
16 skipped), Metal graphics, docs and the lint ratchet pass.

Co-authored-by: v0lumehi <aljic.zijad@gmail.com>
…ale one

The tests' rule for game_profile.h had no prerequisites, so a header from an
older game.py was kept once it existed: after game.py added
DM_GAME_STDALLOC_CAP, t_heap_size failed on the missing define until the
header was deleted by hand (CI starts clean and never saw it). The rule now
runs on every make (FORCE) and defers to the host Makefile's game_profile
target, as the host build does; game.py writes the header only when its
content changes, so an unchanged header rebuilds no test. The libvpx rule's
FORCE_VPX is folded into the same FORCE target.

Closes seedds#466

Co-authored-by: v0lumehi <aljic.zijad@gmail.com>
Patch libraries can reference unchanged shader groups in base containers.
Resolve chunk IDs across the ordered container set and extract only the
winning library, including unnumbered and lowercase patch suffixes.

Check encryption when selected data is read: refuse group-only encrypted
containers without a key, while leaving fully replaced encrypted libraries
unread. Add ten synthetic archive-resolution tests and document the fix.

FINAL FANTASY RESONANCE's two decoded archives remain byte-identical:
74,244 entries and 73,496 distinct shader keys. Focused tests, Python tool
tests, GPU tests and lint/type checks passed during review. The full
regression harness remains incomplete (cold shader rebuild); gameplay
was not rerun.

Co-authored-by: Favour Okwara <favourprogrammer@gmail.com>
seedds and others added 21 commits October 28, 2026 15:33
When the regions made after a StandardAllocator region need more room than
the 256 MB the host's cap leaves them, the check now sets stdalloc_cap in
the provisional or derived profile instead of only naming the value: the
alias region less the regions before it, those after it and the same
256 MB for regions made later in play. A value set by hand is checked, not
replaced; one an earlier check set is worked out again.

Absolum 1.1.1: 2.5 GB and 2.72 GB both play its 320 s route to frame
18,600 with no red line; a provisional pack (with its game_env) sets
0xa2000000, 2.53 GB, between them.
…thread (seedds#471)

mtl_read_texels decided on its own which textures take their content from
guest storage, and that copy had drifted from tex_mtl_'s: it left out sampled
textures of a sparse virtual pool. Those went through tex_mtl, which uploaded
them on the caller's thread, and up_owner_check aborted (Tears of the Kingdom
on an iPad Pro M5, thread bake::ModuleMgr, a few seconds after the app came
back from the background). The storage path also read a sparse texture at its
virtual address, which faults where nothing is mapped.

One predicate, tex_from_storage, now serves both, and mtl_read_texels gathers
a sparse texture through its page table (nvn_sparse_copy, unmapped pages as 0)
before deswizzling. The up_owner_check abort stays. t_readback reads sparse
textures over mapped and unmapped pages on another thread and on the submit
thread.

Co-authored-by: Ink <347642667+Inksdx@users.noreply.github.com>
A provisional split of Pokémon Legends: Arceus 1.0.2 counted all 678 GFPAKs
under bin/archive as unreadable ("entry 0 has compression 3 (Oodle), which
this reader does not decode") and left their shaders out: every entry of
Arceus's GFPAKs is Oodle Kraken, where every entry of Sword's is LZ4.

gfpak_members now decodes compression 3 with the game's own decoder through
oodle(), as Trinity packs and PKZ archives are. profile_guess.py
build_sources counts the GFPAKs that hold an Oodle entry (oodle_gfpak), so
oodle_packs is set from the files and the NSP script provides
DM_OODLE_HOST / DM_OODLE_MODULES for the split. Nothing is keyed to a title
or build: any GFPAK with Oodle entries takes this path; LZ4-only games count
none and read as before.

Checked: on Arceus (reporter, Linux emulator of the game's decoder) the split
reads all 678 GFPAKs and finds 12,851 shaders, 69 more. On the Mac, a GFPAK
of 300 real Kraken streams from Pokémon Scarlet's packs (28 MB raw) decodes
through gfpak_members and the Mac host byte-identically with Scarlet's and
Z-A's OodleLZ_Decompress, and build_sources sets oodle_packs for it but not
for an LZ4-only GFPAK. dm/gfx/tests/test_gfpak.py covers each compression.
Arceus itself was not run on the Mac.

Co-authored-by: Aman Singh <40149189+amansingh909@users.noreply.github.com>
Persona 4 Golden 1.0.2 (010062B01525C000), SDK 14.3.0, on NVN with CRIWARE:
26 shaders, 8 vertex and 18 pixel, all BNSH entries under sysdat.cpk, and no
shader pairs and no compute shaders.

On the Mac it runs from boot through the opening movie, the title screen, a new
game and the in-game notices into the first Velvet Room scene: 60 fps (frame
time p50 16.66 / p95 17.01 ms), 243 render passes a frame in the busiest part of
the route, and no [RED] line over a 9,900-frame run. Resident memory 1370 MB
with 1672 MB of Metal allocations.

The RomFS is 12 files: eleven CPKs at the root plus data_cri/sound, which the
packer extracts as a 9.7 GB image that the host mounts directly. main carries
471 MB of bss and declares no system resource, so the profile keeps
sys_resource 0x0 and the 3.5 GB layout. The 26 shaders are grouped by their
CPK (shader_groups /sysdat.cpk/=sysdat) rather than left in "other".

pso_manifest.tsv holds 44 recorded lines (21 shaders, 23 render pipelines) from
an empty save over the smoke route.

dm/gfx/tests on this Mac: every binary passes except t_pso_bundle, which fails
the same 10 of its 52 checks on main with the same profile, so the failures come
from this machine's Metal 32023.883 on an M5, not from this profile.

The touch layout is untested and no IPA was built.

Co-authored-by: Ian Franklin <ian.franklin@outlook.com>
…nd survives their removal (seedds#476)

exFAT and FAT have no extended attributes, so macOS keeps a file's (a
download's quarantine flag) in an AppleDouble `._<name>` beside it and
deletes it with the file. Two things went wrong on such a drive:

- scan_nsps listed `._Update [<title ID>800].nsp`, 4 KB of metadata whose
  name carries the title ID, as a second update; an untagged update then
  read as two and was left out. Names starting with `._` are skipped.
- Deleting `x` takes `._x` with it, so shutil.rmtree's later unlink of the
  listed `._x` raised FileNotFoundError, which before Python 3.13 ended the
  removal and left a half-deleted folder. The script's rmtree passes ENOENT
  through an onexc hook (Python 3.12 is the floor) and raises every other
  error.

Nothing is keyed to a game. On top of the PR: the pre-3.12 onerror branch
is dropped, the test's stand-in unlink joins the partner's directory (it
was correct only for the dir_fd walk), and docs/reference/packing.md has
the entry, linked from docs/development.md and the symptom index.

Checked: on an exFAT disk image (hdiutil -fs ExFAT) main's scan_nsps listed
the `._` copy as an update and deleting `a` removed `._a`; the patched scan
finds one update and rmtree removes a tree holding 55 `._` files.
test_nsp_external_drive.py passes on Python 3.14 and 3.15, all 148
test_nsp_* and test_docs.py pass, and the pinned lint ratchet reports 0 new
findings. Python 3.12 was not available here; the author ran the tests on
3.12, where the rmtree and scan tests fail without the change.

Co-authored-by: v0lumehi <aljic.zijad@gmail.com>
title_of falls back to the [title ID] in a file's name when the file holds
no ticket, so scan_nsps took any file with a game's extension and tag for
one of its packages: an AppleDouble `._` copy on an exFAT drive (seedds#476), a
failed download saved under the game's name, a file cut off before its
header. One such file beside an untagged update made it one of two and left
it out.

is_package checks what nx_extract's OpenNsp opens: an XCI / XCZ by the card
header's HEAD at 0x100, else a PFS0 (NSP, NSZ). scan_nsps skips any other
file with a line saying why; `._` files are still skipped without one.
Nothing is keyed to a game.

Checked: dm/tools/tests/test_nsp_scan.py (an XCI base without a ticket, a
tagged HTML page and a truncated XCI beside a real update, a quiet `._`
skip) fails without the change and passes with it; all 150 test_nsp_*,
test_docs.py and test_bug_report.py pass; the pinned lint ratchet reports 0
new findings. is_package accepts all 339 packages on the T7 drive (336 NSPs,
3 XCIs); no NSZ / XCZ there, those are covered by the tests only.
dm/tools/docs_patches.py lists, per game, the rewriter's patches of its builds
(CUTS, OBS, FRAME_STEP sites, MATCH_CUTS, CAPABILITIES), the host rows keyed by
its build IDs, the SDK cuts by sdk build, translator rules keyed by one shader,
and the profile keys that switch host behaviour. Each patches.py gets a
GENERALIZE note on whether a general rule could replace it. test_docs.py checks
the page is current.
docs_patches.py walked dm/host with rglob, so the git-ignored build output in
dm/host/gen (game_profile.h's build check) entered the page wherever the host had
been built: the committed page carried a TOTK row a clean checkout cannot produce,
and test_docs.py failed on any machine with a different build. It now lists the
host files with git ls-files, as env_switches.py does, and the page is regenerated.

The 4 findings lint-python reported on 68fbe84 are fixed: load() checks the spec
and its loader before using them (as regress.py does), and page() drops the unused
by_id.
…ript

68fbe84 reached main with 4 new ruff/pyright findings: the guide's pre-PR list
never named lint_python.py, and the system Python has neither tool, so the check
stops at a [RED] line unless it runs from a venv with lint.txt. The list now
names it, where the venv goes, and that a generated page must not read
git-ignored files (CI compares it on a clean checkout).
…e path components (PR seedds#513)

On Horizon a FileHandle is a pointer to the SDK's FileAccessor, so its low bits are clear,
and Metroid Prime 4: Beyond's file device masks them off (handle & ~7) before ReadFile. The
host's RomFS image handle kept the entry index in its low bits, so entry 7 was read as
entry 0: a 10-byte read of another file, then a null pointer at boot. The guest now gets
the host's handle shifted left by 4 (FH_OUT), and each call that takes one shifts it back
(FH_IN); the image flag moved from bit 62 to bit 58, so the guest still sees it at bit 62.

Horizon's file system accessor replaces every backslash in a path with a slash before it
normalizes the path (FileSystemAccessor::SetUpPath), on every mount but the PC host's;
Metroid Prime 4 names its files rom:\StartupUI.pak. Each guest path resolver (img_path,
host_path_ex, set_guest_name, dir_snapshot) converts them first, so overlay folders, ".."
and guest names see the same path. PR seedds#513 taught two of the parsers instead.

t_fs: 10 new checks (masked handle on an image entry, backslash paths on a folder with an
overlay, an image, an image with an overlay, directory listings, a save's create, rename and
guest name): all fail before, all pass now. t_fs_commit_list decoded a handle as an fd and
hung with any shift; it now uses FH_IN. All host tests and tsan-threads pass. Reference entry
in docs/reference/services.md.

regress.py check on 5 games (Mac, M4 Pro, shared with three other harness runs):
ANIMAL WELL PASS; Pokemon Scarlet reached the end at 29.9 fps as before (its "new" red lines
are shader libraries missing in both runs); Tears of the Kingdom and Breath of the Wild did
not run (the baseline's shader build stopped); Super Mario Bros. Wonder frame p99
16.9 -> 47.6 ms under that load, unexplained, to re-run on an idle machine.

Co-authored-by: Lilun Xu <173143200+lx7720@users.noreply.github.com>
The tools read NSO and NRO modules through ten copies of the same loader,
and each trusted the header: a 256-byte file could make it allocate 8 GiB,
and a segment shorter than its size shrank the image (python-lz4 returns
fewer bytes without an error, and the slice assignment then shifted the
later segments), with no error.

dm/tools/nso.py is now the one loader (load, read, cli_read; nso.Error is a
ValueError), and patch_nso, nso_code, insn_census, nso2elf, loader_census,
nso_syms, shader_split, uepak, gl_shaders and profile_guess use it. It
accepts what Horizon accepts and checks it the same way, from Atmosphere's
loader (ldr_process_creation.cpp) and nn::ro (ValidateNro): NSO offsets on
pages, text at 0, ro and data in order without overlap; an uncompressed
segment read at its size; a compressed one decompressing to exactly its
size; a segment's SHA-256 checked when its flag is set (every retail module:
93 of 93 surveyed, 279 of 279 hashes matching), so damaged bytes of the right
size are caught too; an NRO in whole pages, contiguous to its size. Beyond
Horizon, so a small file cannot claim gigabytes, a compressed segment is no
more than LZ4 produces from its stored bytes (255 per byte) and the image,
gaps included, no more than that of the file. A zstd main and a compressed
NRO are an error naming the format. bss is never allocated (nso.py's own
output gets it as a sparse tail).

Each tool's command line, and the NSP script for the modules it reads
itself, stops on a damaged module with a [RED] line. nso.py joins the
rewrite and shader-stage cache keys, regress.py's rewriter files and
build_src.sha256.

Checked: 121 real modules (93 NSOs of 26 games, 28 Portal NROs) load into
images byte-identical to the old loader's, none refused; patch_nso on
Metroid Dread's, Wonder's and Monster Hunter Rise's mains and a Portal NRO
differs from before only in build_src.sha256. The issue's huge-segment.nso
is refused with nothing allocated; its short-segment.nso now loads its full
image, as Horizon would. test_nso.py covers each rule; 71 of 71 tool and gfx
test files pass; the lint ratchet has no new finding. regress.py check and
dm/ios/tests were not run.
Consolidate seedds#126, seedds#289 and seedds#304 for title 0100B8A026F14000 and main
b7f965b9d7227db8e29f04e84ea7331b. Keep one profile with build-gated
allocator and frame-rate registrations, the complete shader map, merged
manual facts and recorded prewarm seeds. Preserve each contributor's
Mac/iPhone testing and its limitations in the game note.

Normalize update 1.0.1 to v65536 and use the registered rhi.SyncInterval
write rather than a display-interval override. Keep independent translator
work separate from the profile.

Validation: rebased Python tools (509 tests, 16 skipped), 78 focused
profile/docs tests, FPS build-gate/override/startup tests and complete
shader-key verification. The initial base also passed host unit and Metal
graphics tests, the regional Mac host build and 178 iOS source compiles.
Full Mac/iOS shader validation and the ANIMAL WELL regression control are
queued behind the shared compile window; regional gameplay was not rerun.

Co-authored-by: huangzhuangchao <95277145+huangzhuangchao@users.noreply.github.com>
Co-authored-by: willreg <47594112+willreg@users.noreply.github.com>
Co-authored-by: 4202217 <208370207+4202217@users.noreply.github.com>
…ds#487)

An Intel Python under Rosetta, a shader translator without a .NET runtime of
its slice, a missing iOS SDK or a missing glslang each stopped the build late,
the translator ones as an empty `xlate_source` at the shader step.

- check_native_python stops under Rosetta (sysctl.proc_translated): the
  universal translator it starts runs as x86_64 and finds no x86_64 .NET
  (reproduced with arch -x86_64: exit 131, empty stdout).
- check_prereqs starts the translator once with --source-sha (check_translator),
  asks for glslangValidator, and for an iPhone build checks the iOS SDK before
  the Metal check (check_ios_sdk; the Command Line Tools case named apart).
- own_compilers.py does not ask a Mac-only shader build (DMSH_TARGETS=mac) for
  the iOS Metal compiler, so a Mac build needs no iOS platform.
- Open: dmsh_chain.py tools_now still drops the translator's exit code and
  stderr; fixing it changes the chain version (docs/reference/packing.md).

Tests: test_nsp_env_checks.py (new), test_own_compilers.py, test_nsp_mac,
test_nsp_progress, test_nsp_dotnet, test_nsp_incremental, test_nsp_romfs,
test_xcode_workflow, test_docs; lint_python.py 0 new. Raging Loop
--platform mac through the script: green. regress.py check --quick:
ANIMAL WELL PASS (46/46 shaders, 0 changed MSL, 59.7 -> 59.6 fps, 0 new RED).

Co-authored-by: v0lumehi <aljic.zijad@gmail.com>
Add the game profile, shader inventory, title smoke route, Mac gameplay
screenshot and port notes. Decode loose Monolith HCPS, XBC1, DRSM and
compressed effect containers through the existing recursive reader.

Retire cached descriptor views when their texture is finalized, while
preserving copied NVN metadata and fence-safe late command replay. This
removes the streamed texture retention found during the opening battle.

Validation: full Metal graphics suite, Python graphics/tool/docs checks,
15,129 Mac and iOS shaders, all 10,681 Mac pipeline pairs, brief gameplay
through the opening boss and Colony 9 combat, and a CRC-verified unsigned
13.2 GiB IPA. Five-game regression details are in the game note; Xenoblade
2's timed-route screenshots and footprint compare different scenes and
remain inconclusive. No iPhone or save/reload validation is claimed.

Co-authored-by: seedds <seedds@gmail.com>
… address space (GitHub issue seedds#483)

Shaking the overalls bush in the opening quest wrote to 0x78 at
main+0x2c534a8: a UI node context (Moria::Services::UI::Shared::Nodes::
Context, main+0x2c4a228) reserves 576 KB and 256 KB of address space,
never checks the reservation, and pushed its first entry onto a NULL
array. Measured with a 64 GB alias region and DM_REGIONS, the opening
quest's regions end at +4.515 GB, past the profile's 4.375 GB alias
region; on the profile's layout only 110 of 147 contexts got their range.

The largest region besides Snowdrop's 3 GB is a 1.06 GB pool of
4,194,304 272-byte slots built by the memory system's startup
(main+0x94364). Nothing allocates from it: only that startup and its
teardown name the pool or the default-pool slot it fills, and nothing
was ever committed in it. A build-gated cut (+0x94354) gives it 65,536
slots (17 MB). The regions then end at +3.455 GB, every context gets its
range, and the overalls route picks them up without a crash (Mac,
headless, past frame 5100). The save-loading crash in the issue is
likely the same cause but was not tested (no such save); docked coin
colours are listed as open in the game note.

Tests: test_docs and test_patches (26 tests) pass.
DM_NVN_CAPTURE=<frame>:<file> writes one frame of an NVN game to a file:
the memory pools and the regions records point to, the objects and
registries, textures whose content lives only in Metal, each submission's
command segments, the present, and the live run's pool block hashes and
presented pixels. Pointers are stored as region offsets and object
indices (cap_rec_fields), so dm/gfx/tests/nvn_replay.m replays the frame
in a process of its own, without the game, and compares the result with
the live run: memory hashes, the presented frame's SHA-256 and, with the
new LPIX chunk, its pixels. --repeat n runs n fresh replays; --max-pixels n
accepts a frame that differs from the live one in at most n pixels and
prints the count, the largest byte difference and the box.

tex_fit made a compatible view of a view taken from the view cache; Metal's
shader validation reports such a view as not resident (BotW: a 64x64 cube
read through a 2D-array view). The view is now made from the root texture,
with the level and slice offsets added up (t_tex_fit_root fails before and
passes after; rendering.md "Compatible views made from the texture under a
view").

Open (BotW note): fresh replays of one BotW outdoors frame differ from the
live frame and from each other in 3 to 124 pixels of colour targets 1 and 3
of the G-buffer pass, with segmented submission on or off; textures, depth
and per-draw dumps are identical. The cause is not found.

t_capture 77/77, t_tex_fit_root 9/9, t_capture_live 2/2, test_docs,
test_env_switches, docs_symptoms --check, iOS compile check (179 files).
regress.py check --runs 2 (impact: BotW, TotK, Scarlet; plus ANIMAL WELL,
Wonder): ANIMAL WELL and Wonder pass; no shader, MSL or interface change in
any game. The flags on the other three are the test Mac's, not the change's:
one Scarlet candidate run fell to 14 fps after its window's display link
stalled for 1 s twice (an occluded window, frame-rate.md; GPU 3-7 ms a
frame); BotW's outdoors route misses frame 2200 at 20 fps on both sides,
with screenshot differences below the baseline's own; TotK's new red lines
are missing-library names that differ from run to run, as in the baseline,
and its p99 rose in one of two runs.

Co-authored-by: v0lumehi <aljic.zijad@gmail.com>
…edds#307)

Every new graphics test edited the same lines: the run: line in
dm/gfx/tests/Makefile, its recipe and two lines in dm/gfx/tests/.gitignore,
so any two branches that each added a test conflicted there.

make run now runs every t_*.c / t_*.m but those NOT_RUN names, each with
its reason (helpers linked into other tests, fixtures other tests build,
t_gpuprof from CI's CPU subset). It builds them first, in parallel with -j,
then runs them one at a time through check_t_<name>: ./t_<name> by default,
with the environment a RUN_ENV beside the build rule gives it, or a
check_ recipe of its own for a test that runs several ways. The Python
tests become check_test_dmsh_pull and check_test_tbdr_budget. .gitignore
ignores t_* without an extension and every .dSYM, plus nvn_replay, the one
tool built there. t_dirty_slots and t_dirty_race now depend on the profile
header dm.h includes; a parallel build from a clean dm/host/gen could
compile them before it existed.

dm/tools/tests/test_gfx_tests_makefile.py (no compiler or GPU) checks that
every test source has a build rule, NOT_RUN names existing sources, no
hand-kept run: list comes back, every binary the Makefile compiles and its
.dSYM are ignored, and no tracked file is.

Rebased on d9c2d5e: t_capture, t_capture_live and t_tex_fit_root (seedds#473)
keep their check_ recipes without run: lines; nvn_replay gets its
.gitignore line and the ignore check covers every compiled binary, not only
t_* (it fails without that line).

make -n -B run executes the same 104 test commands as before (sorted
sets); make -C dm/gfx/tests GAME=010015100B514000 -j8 run from a clean
dm/host/gen passes (1:47); test_gfx_tests_makefile, test_lint_python,
test_docs. No runtime code changes, so no regress.py run.

Co-authored-by: v0lumehi <aljic.zijad@gmail.com>
…nd iOS (seedds#541)

macOS and iOS take both buttons as system gestures: Home on a Pro Controller opens the Games app (macOS) or the Games overlay (iOS) over the game, and Capture takes a screenshot on iOS. The host gives them no function, so both front ends now set their preferredSystemGestureState to disabled on every controller they attach: front_mac.m in the merged mode and per player, DmGamepad.m for iOS's single player.

Co-authored-by: Peter Kmet <55005408+peterkmet@users.noreply.github.com>
…f fixed 50 ms (seedds#445)

SwiftUI reports the grouped Form's scroll geometry on a later pass, an estimate
first and the measured rows a few ms after it. On a slow runner the fixed 50 ms
wait could read the estimate (CI: height 1282 = 660 + the estimate), so the
resize to that height did not fit. Each step now runs the loop until the report
meets the step's expectation and has held for 0.2 s, at most 5 s; the checks are
as strict as before (a 16 pt misfit still fails all four). Each measurement also
runs in its own autorelease pool, so earlier hosting views no longer stay alive
and lay out their Forms again on every change of window.advanced.
Conflicts: README.md keeps the fork's; front_ios.m keeps the data help screen; main.m
keeps both the split-build data roots and upstream's romfs.id check; dmsh_chain.h is
recomputed for the merged tree (same sha256 recipe as dmsh_chain.py version()).
… version

Upstream's committed dmsh_xlate.dll is an arm64 PE (machine 0xaa64), which dotnet on
Windows x64 refuses to load (the assembly architecture is not compatible with the
current process architecture). Rebuilt from the in-repo source with dotnet build
(machine 0x14c, as before the sync); Ryujinx DLLs stay upstream's. The lock's
dmsh_xlate.dll line and DMSH_CHAIN (4e91e9103b44) follow.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@zenvyzenshiro
zenvyzenshiro merged commit 0383905 into main Oct 28, 2026
2 of 6 checks passed
KillingStalking pushed a commit to KillingStalking/NXNativeWindows that referenced this pull request Nov 13, 2026
The walk from the load back to the bounds check noticed copies and masks but no
other write: cmp w18,zenvyzenshiro#4; b.hi; add w18,w18,#1; ldrsw x18,[x4,x18,lsl zenvyzenshiro#2] read
entries 0 to 4 while the code uses 1 to 5, so one case's target was missing with
no sign of it. A site with a write of the index after its check, or a call with
a caller-saved index, is now refused, as jump_adr_offsets and half_count already
refuse a write. A refusal goes on to the other recognizers and ends in the jump
table assertion. Writes before the check do not count, which keeps the sites
check_before_copy takes.

A write that keeps the value, such as sxtw x0,w0, is refused too; no module on
the drive has one.

Checked: test_jump_copies.py (the new cases fail on the previous rewriter). Of the
345 unique modules on the drive, old and new recognition are the same on 281, and
60 more mains were scanned on the previous code with no site this refuses. That
includes Smash 13.0.5, EA SPORTS FC 27 and Well Dweller. Four could not be checked:
New Super Mario Bros. U Deluxe's main, sdk and rtld fail alike (no profile), and
Smash 1.0.0's main stops at a table the game's note already lists. No regress.py
run: nothing changes in the modules checked.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants