Skip to content

Xenoblade Chronicles 2, Nintendo Switch 2 Edition: plays on the Mac through its first battle - #1258

Open
capibawa wants to merge 8 commits into
seedds:mainfrom
capibawa:xc2-ns2-profile
Open

capibawa wants to merge 8 commits into
seedds:mainfrom
capibawa:xc2-ns2-profile

Conversation

@capibawa

@capibawa capibawa commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

What and why

Adds Xenoblade Chronicles 2, Nintendo Switch 2 Edition (0400E95004038001: program 1 of update 1114112, 3.0.1, SDK 21.4.4, NVN2) with its profile, note, games-table row and gallery screenshot. Its note says what the profile needs and why: dm/games/0400E95004038001/README.md.

Stacked: this branch holds the five commits of the PRs below, then one of its own. Review the commit Xenoblade Chronicles 2, Nintendo Switch 2 Edition (…; the two commits after it are fixes that also went to #1253 and #1254. This PR needs a rebase as the others land.

That commit also turns those PRs' plain-text "Found on" mentions of this game into links to its note: they could not link a note that was not there yet and still pass test_docs.py.

Checked

  • Tests: dm/tools/tests/test_docs.py, with the tests listed in each PR above run on the rebased stack. The full make -C dm/gfx/tests run and make -C dm/host/tests run suites were not run; only the tests for the changed code.
  • Games run: this game on the Mac, headless from a new save with DM_CTL input: title, content screen, menus, New Game, the opening (about 2 minutes, then skipped) and the first battle against King Crustip, at 60 fps (frame time p50 16.6 ms) with no crash and no [RED] line. These runs predate the rebase on current main.
  • Sound: checked by ear on the Mac app (details in SDK 21's hardware Opus decoder manager and OutputStream #1254).
  • Not checked: winning the battle (Rex had not drawn his weapon when testing stopped), a save or anything after it; Torna; a windowed run of the final host. No IPA: the 121 GB of address regions need a cut for the iPhone, so the gallery says Mac only.
  • Devices and OS: Apple M3 Pro, macOS 27.0.1.
  • scripts/regress.py check --quick: Donkey Kong Bananza, Fire Emblem Fortune's Weave and Hello Kitty Party Land PASS (0 newly failing, 0 changed MSL; no routes, so no boot runs). This game reports ERROR only because its profile is not in the baseline tree. No smoke.tsv route is added here.

Docs

  • The finding is in its game note and its docs/reference/<subsystem>.md entries, and python3 dm/tools/docs_symptoms.py was run (the symptom index)
  • No game files, keys or data extracted from a game in the change or the attachments (the tables are shader keys and offsets, as in other profiles; one gameplay screenshot)

🤖 Generated with Claude Code

capibawa and others added 6 commits November 4, 2026 15:04
…a Switch 2 Edition's program 1 pack: the NSP script builds from the Switch 2 program by default

A dumper can write a game's update and DLC with no base/ folder (an update's files are whole), each version split into program/<n>/{exefs,romfs}, and a DLC under <n>/base/ or <n>/updates/<version>/, each optionally under program/0/. pack_dump.py read none of these, and for a dump of Xenoblade Chronicles 2 Nintendo Switch 2 Edition (update 1114112) the scan stopped with "program ID 0400E95004038001 is not a base title ID": the update's program 1 is the Switch 2 game, which names a program of its own.

pack_dump.py now finds the base game optional beside an update, the program folders, the DLC layouts (the newest DLC update wins), and takes the title ID from program 0's NPDM, else from the DLC, else from --title-id. A Switch 2 Edition's update packs program 1 by default (--program N picks another); each program is packed with its own RomFS, as Horizon mounts it. build_ipa_from_nsp.py passes --program for a dump folder and prefers a profile for the packed program's own ID.

Checked: test_nsp_dump.py (updates only, a DLC update winning, the Switch 2 Edition program ID, program/ folders, packing program 0 of several, refusals) and test_docs.py; the Xenoblade Chronicles 2 dump packs program 1 (13.1 GB) and its seven DLC.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…me decodes and plays its sound instead of aborting at hwopus and auddmg:u

Xenoblade Chronicles 2's Switch 2 program (SDK 21.4.4) stopped at boot with abort 0xe15 from nn::codec: SDK 21 decodes Opus through a manager (GetHardwareOpusDecoderManagerWorkBufferSize, Initialize/Finalize, decoders made from it, packets enqueued, an Execute that decodes everything waiting, PCM dequeued, counts and a system event), which the host did not have. Past it, abort 0xa99 from auddmg:u: SDK 21 plays through an OutputStream on a virtual output device (GetDefaultVirtualOutputDevice, OpenOutputStream with float planar or PCM16 frames, Start/Stop/Close/Purge, state, free frame count, Write, gain, statistics), which the host did not have either.

hle_codec.c now runs the manager on the SDK's software decoder: an enqueued packet takes an I/O buffer, Execute decodes all waiting packets with DecodeOpusInterleaved (silence and a red line on a failure) and signals the manager's event, Dequeue hands back the 20 ms frame; the existing Finalize/Reset hooks also release a manager decoder. hle_audio.c runs each OutputStream on the host's AudioOut: a write converts the frames to one 16-bit interleaved block, the free frame count follows what was played, the release event fires; 48 kHz only.

Checked: t_output_stream (new: the block layout and clamping, 100 ms played in real time with release events, a purge after a stop, the states, a 44.1 kHz open refused) and t_audio; Xenoblade Chronicles 2's Switch 2 program boots past both, through its menus, opening and first battle on the Mac (headless, silent sink: the sound itself has not been heard). Docs: two entries in docs/reference/audio.md, symptom index regenerated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ts NDSR upscaler pack: Xenoblade Chronicles 2's Switch 2 program has its 3D frame in the window

On Xenoblade Chronicles 2's Switch 2 program (SDK 21.4.4, NVN2) the split stopped at main's standalone SLCT with "HCPS: truncated control": its HCPS programs hold NVN2 control sections (0xd0 bytes, their own length at +0x30, the code's at +0x44), which hcps_shaders stepped through by the Switch 1's lengths. With that fixed, nine shaders the game uses were still "not in the shader map", among them three compute shaders, and the dispatch that scales the frame into the window was dropped (`the dispatched program has no compute shader`): the game's upscaler is agl's NDSR network in /monolib/shader/ndsr.pck, an XBC1 entry of bf2.ard, and monolith_shaders decoded the entry but looked only for HCPS and GLSLC outputs in it.

hcps_shaders steps through NVN2 controls by their own lengths (nvn2_ctl); monolith_shaders hands an entry that starts with NDSR to ndsr_shaders (this pack: header +4 0x500, records from +0x10, 40 shaders of which 3 compute). The game's split goes from 22,533 shaders to 22,561 and the nine keys are all in it.

Checked: test_monolith.py's new test_nvn2_controls and test_ndsr_pack_in_an_archive_entry (it fails without the change), every test that imports the split, test_docs.py; the game's NDSR dispatch runs on the Mac. Not a chain tool (DMSH_CHAIN unchanged); other games' tables change only where a Monolith archive holds an NDSR pack. Docs: an entry in docs/reference/shaders.md, symptom index regenerated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e optimal size and the output evaluates DLSS instead of skipping it

Xenoblade Chronicles 2's Switch 2 program renders its scene at 1024x576 into a 1536x864 DLSS network (method 0) and its own upscaler scales that 2x into the window; on the Mac every 3D frame reached the window black. It never called nvnCommandBufferEvaluateDlss: its evaluation (main+0x8ee0e0) returns unless the input lies between the bounds nvnDlssNetworkBuilderGetOptimalSettings gave, and the host answered one size per method (min = max = optimal = 768x432), so the upscaler read an output nothing wrote.

GetOptimalSettings now reports the optimal input from the method as before and a range from a third of the output (DLSS's ultra performance) up to the output (DLAA); MetalFX's temporal scaler takes any scale in it and dlss_eval already keys a scaler by its input size. The same code shows the argument order: optimal, then the smallest, then the largest input (the game stores the fourth and fifth as its lower bound), not NGX's order with the largest first, which the comment said.

Checked: check_t_dlss_mfx with a new 1024x576 → 1536x864 evaluation, the encoding thread off and on (13 checks each); Xenoblade Chronicles 2's Switch 2 program evaluates DLSS every frame and its title, opening and first battle reach the window. Fire Emblem Fortune's Weave renders at its optimal size, inside the range: its opening looks the same before and after. Docs: docs/reference/rendering.md (NVN2's DLSS on MetalFX: Fix, Input range, Found on).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s made without flag 0x4 keep the Tegra X1's, so Xenoblade Chronicles 2's grading tables and font read right

On Xenoblade Chronicles 2's Switch 2 program every 3D frame came out green and dark red and its text in pairs of rows shifted 16 texels aside. The frame's colour grading reads a 16x16x16 LUT blended from the game's own RGBA8 3D LUTs; dumped from guest memory, their bytes decode to smooth ramps in the Tegra X1's order inside a GOB (mean neighbour difference 16.6) and to a weave in the Switch 2's (31.1), which the host applied to every texture of a Switch 2 game since Donkey Kong Bananza (the block height and depth were right). The R8 font atlas (4264x4364) shows the same exchange of offset bits 5 and 6.

Across the three Switch 2 games looked at, the texture's flags tell them apart: Bananza's and Fire Emblem Fortune's Weave's textures from their files (BC, ASTC) are all made with flag 0x4 (NVN's IMAGE bit) and need the Switch 2's order; Xenoblade Chronicles 2's are made with flags 0 and need the X1's. The memory pools do not (flags 0x64 in all three). nvn_gob_order(d) = a Switch 2 game and flag 0x4 now picks the order for the CPU copy, WriteTexels, sparse tiles and both Metal untiling kernels; nvn_bl_offset keeps the global for callers without a texture. What flag 0x4 means to NVN2 is open (a BC texture is no image), and so is whether a texture without it is ever in the Switch 2's order.

Checked: t_tex (5,347 checks, with new ones that a Switch 2 game's texture with flag 0x4 decodes in the Switch 2's order, one without in the X1's, and a Switch game's always in the X1's), t_deswz_gpu (38 cases, the Switch 2 runs with flag 0x4), t_sparse_layout (21). Bananza's and Fire Emblem Fortune's Weave's openings look the same with the old and the new host; Xenoblade Chronicles 2's title, menus, opening and first battle show right colours and clean text. Docs: docs/reference/nvn-objects.md (The Switch 2's order inside a GOB: Fix, Which textures, Check, Found on).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…update 1114112 program 1, 3.0.1, SDK 21.4.4, NVN2) plays on the Mac through its first battle, from a dump

The Switch 2 program of Xenoblade Chronicles 2's Nintendo Switch 2 Edition gets a profile of its own: the Switch 1 profile's modules, memory and shader tables crashed it in nninitStartup. It needs two 60 GB address regions (nninitStartup and nnMain both run the allocator's setup, main+0xbddb40 → main+0xb78ed0), so mem_alias 0x2000000000 and mem_reserve 0x2028000000 (Mac only), and mem_map_limit for the setup's probe that maps 1 MB pages until a map fails; its display version is 3.0.1, the one it compares with DLC 98's dlcver.bin (with another it showed the add-on content version error, which the host cannot draw). Its own RomFS is a 5-byte dummy.bin: the game data is DLC 98's bf2.arh/bf2.ard, registered under rom:/ after rom:/bf2.arh is not found.

With the dump packing, SDK 21 audio, Monolith shader split, DLSS input range and per-texture GOB order changes before this commit, it boots, shows its title and plays, headless from a new save: the content screen, menus, New Game, the opening (skipped partway) and the first battle against King Crustip, at 60 fps (frame time p50 16.6 ms) with no crash and no [RED] line. Sound is not heard yet (headless), nothing past the first battle was tried, and the iPhone needs a cut for the 121 GB of address regions.

The reference entries those changes added now link this note; the games table and the README gallery list it (Switch 2, Mac only, from a dump), and the Switch 1 game's note points here. Checked: test_docs.py; scripts/regress.py check --quick on Donkey Kong Bananza, Fire Emblem Fortune's Weave and Hello Kitty Party Land: PASS (0 newly failing, 0 changed MSL; no routes, so no boot runs); this game ERROR only because its profile is new.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added host The Horizon host: SVCs, services, threads, memory (dm/host) nvn NVN on Metal and its tests (dm/gfx/nvn, dm/gfx/tests) textures Texture layouts and formats (dm/gfx/tex) packing Building an IPA or a Mac app: the NSP script, extraction, the shader split, the packers tools Tools and scripts (dm/tools, scripts) docs Documentation game: 0100E95004038000 Xenoblade Chronicles 2 game: 0400E95004038001 A game profile new game Adds a game profile that main does not have yet labels Nov 4, 2026
capibawa and others added 2 commits November 4, 2026 15:12
… does: lint-python's new finding on this change

pyright: Operator "/" not supported for "None" at is_dump, since program_dir returns None when an index names a program the folder lacks. is_dump passes no index, so it never crashed; the guard is the walrus the updates scan already uses. Checked with CI's pinned tools (dm/tools/lint_python.py --against origin/main: this finding gone; dm/games/settings.py:115 remains, red on main since 4e0333a and not from this change) and test_nsp_dump.py.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…its pitch, without a jump every block

Xenoblade Chronicles 2's Switch 2 program sounded an octave low with crackling (voices very deep), heard on the Mac app. WriteOutputStream takes interleaved frames (L, R, L, R), and the host read them as one run per channel, misled by the game's code that zeroes an unfilled tail (main+0xad1684) stepping by a period per channel: each 10 ms block's first half played as left and its second half as right, each at half speed, with a jump at every block. t_output_stream asserted the same wrong layout.

The block now keeps the order it is given (float scaled and clamped to 16 bits, PCM16 copied). The PCM16 path has no game behind it; it takes the same layout by reasoning, not measurement.

Checked on the same 4-minute route with DM_AUDIO_DUMP: energy above 20 kHz from 16-39 % to 0.00 %, left channel lag-1 autocorrelation from 0.18-0.64 to 0.99+, jumps at block edges from 1.42 times those elsewhere to 1.00, and through the real AudioUnit (muted) 0 underruns in 48,000 device callbacks. By ear on the rebuilt Mac app: sounds right. t_output_stream now checks the interleaved order. Docs: docs/reference/audio.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

docs Documentation game: 0100E95004038000 Xenoblade Chronicles 2 game: 0400E95004038001 A game profile host The Horizon host: SVCs, services, threads, memory (dm/host) new game Adds a game profile that main does not have yet nvn NVN on Metal and its tests (dm/gfx/nvn, dm/gfx/tests) packing Building an IPA or a Mac app: the NSP script, extraction, the shader split, the packers textures Texture layouts and formats (dm/gfx/tex) tools Tools and scripts (dm/tools, scripts)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant