
- Title ID
- 0100A83022A22000
- Tested build
- 1.1.2
- Nintendo SDK
- 18.3.2
- Engine
- FNA / .NET NativeAOT (FNA3D's NVN driver)
- Graphics API
- NVN
Build this game
Keep [0100A83022A22000] in your file names, put the base game, update and DLC in one folder and run the script. Follow the build guide, then the sideloading guide. Needs an iPhone 15 Pro or newer, or an M1 iPad or later.
Technical notes
The project's own record of bringing this game up: what its profile needs and why, what was found and what is still open.
Update 1.1.2 (v458752) over the base game (v0), SDK 18.3.2. FNA (XNA 4 reimplemented in C#) compiled with .NET NativeAOT (NativeAOT-NX; its own GC, regions enabled), SDL3, FNA3D's NVN driver (FNA3D_Driver_NVN.cpp), Wwise. Modules main, subsdk2 (the movie module, for FNA3D's video player: H.264 logos) and sdk; NVIDIA's Vulkan and OpenGL drivers (subsdk0, subsdk1) are not loaded. Epic Online Services comes as an NRO the host does not load; the game plays offline without it. Playable on an iPhone.
Status: brought up on 1.1.2 on the Mac: the logo movie, the licence agreement, the main menu, the campfire character select, the WOF_Intro intro movie and into the first level (the story's intro blurbs past, no error to frame 17,400 of a 300 s scripted route), at 60 fps with real sound and saves; about 2.5 GB resident (2531 MB at frame 14,700).
What it needs
| Setting | Where | Why |
|---|---|---|
| malloc's address space 40 GB → 2.5 GB | the host's StandardAllocator cap (hle_svc.c), every build, with game.tsv's stdalloc_cap 0xa0000000 |
malloc's StandardAllocator takes 40 GB of address space; 2.19 GB is used into the first level, and the GC's 1.5 GB region comes after it; see Memory |
game_env DOTNET_GCRegionRange=60000000 |
game.tsv, hle_os.c |
the .NET GC's region range 1.5 GB instead of 5 × its hard limit (3.75 GB) |
Node area cut 1 GB → 62 MB (sdk+0x13be4c / +0x13bee0) and 2 MB alignment for regions of 4 MB and more, as SDK 20 (sdk+0x13c034, +0x13c03c, +0x13c040) |
dm/tools/sdk_patches.py SDK-wide rules, sdk build f118ae1c… |
the 1 GB alignment lost up to 1 GB before each of the two large regions |
Engage's 5 GB block: mem_reserve 0x140000000, mem_alias 0x118000000, mem_stack 0x10000000, app_pool 0xCD500000 |
game.tsv |
256 MB in front + 4.375 GB alias region + 256 MB stack region + 128 MB heap region (never used) |
No pad_layout key: the iPhone gamepad starts in the 2D layout (README).
Memory
- The NPDM declares 16 MB of system resource (virtual address memory), and
mainnever callsSetMemoryHeapSize: everything lives in address regions of the alias region.profile_guess.pygives every game with an NPDM system resource the 5 GB block, and this game came out right with it (Unity 6 takes its memory with no StandardAllocator and no heap). - Resolved:
nn::diagabort atsdk+0x12dad0innninitStartup, called frommain+0x756310(1.1.1:+0x7562d0). That is malloc'sStandardAllocator::Initialize(nullptr, 0xa00000000): 40 GB of address space (main+0x28c084; 1.1.1main+0x28bff4).nninitStartupis atmain+0x1b54d0in both builds. - How the needs were measured: one boot on the Mac with a 64 GB alias region (
mem_reserve 0x1030000000,mem_alias 0x1008000000), probingVammManager::AllocateAddressRegion's success path (sdk+0x13bf8c: x21 size, x22 address) withDM_SVC=1's map log. The regions, from boot into the first level:- malloc's: 2.19 GB used, 1.75 GB of it FNA3D's GPU heap, allocated whole at
FNA3D_CreateDevice(main+0xc03660→main+0x563370); its blocks reach 332 MB from the region's start and 1.82 GB from its end, and no more after the title. The cut gives it 2.5 GB, 300 MB to spare. - the .NET GC's region range: 5 × its hard limit (768 MB = 75% of the 1 GB its
GetPhysicalMemoryLimit,main+0x28c08cin 1.1.2,main+0x28bffcin 1.1.1, reports), 3.75 GB. The GC readsDOTNET_GCRegionRangethroughgetenvfirst; the host answersgetenvfrom the profile keygame_env(hle_os.c; every other name goes to the SDK's owngetenv, an empty environment), which sets 1.5 GB. The GC then uses its range from both ends, 756 MB from the start and 748 MB from the end (718 MB mapped into the first level), where it used only the first 1.14 GB of 3.75. - a few small regions (bookkeeping, thread-local blocks), under 5 MB.
- malloc's: 2.19 GB used, 1.75 GB of it FNA3D's GPU heap, allocated whole at
- Resolved: the regions did not fit even then. SDK 18's
AllocateAddressRegion(sdk+0x13bfd0) aligns a region by its size: 64 KB under 2 MB, 2 MB under 4 MB, 4 MB under 32 MB, 32 MB under 1 GB, else 1 GB in absolute address (+0x13c03c), and places it first-fit after the regions already there. The two large regions, with a 4 KB one between them, lost up to 1 GB each to alignment, depending on wheremmapput the guest block (measured with a 64 GB alias region: 0.6 GB before malloc's at that run's alias base, 1 GB before the GC's). A cut on that build aligns them to 32 MB as well (what the SDK gives regions from 32 MB up; nothing in the SDK or the game reads the region's alignment), beside the node-area cut. Thesdk's AddressRegionManager is at the offsets of Super Mario Galaxy's SDK 18.3.5 build. - Current: the host's StandardAllocator cap with
stdalloc_cap 0xa0000000, for every build; themaincut atmain+0x28bff4and the profile'spatches.pyare gone (StandardAllocators over 12 to 62 GB of address space). 1.1.2 has the samesdkbuild; the contributor tested it on an iPhone 15 Pro Max with a 2.5 GBmaincut, which the cap now gives the SDK; a 1.1.2 gameplay run with only the cap is open (no 1.1.2 files on this Mac). Checked on 1.1.1 with the route into the first level, walking and striking there (320 s, frame 18,600), headless from a fresh save with the same keys, against the same build with the cut: the same frames reached,[RED]kinds and alias region use, screenshots of the same scenes (only animation and fade timing differ). Before, withmainrewritten without its cut (another build ID): the default cap gave malloc 4.06 GB and the GC's region then had no room (nn::diagabort atsdk+0x13a684); with the key malloc got 2.5 GB, the GC its 1.5 GB at alias +2.59 GB, and the game ran 75 s headless to frame 3,900. The NSP script's region check first computed0xae000000(2.72 GB) from a 64 GB boot, leaving 64 MB for regions made later; that value plays too: the same route on 1.1.1 withmainuncut, one host built with each value from the same source (2026-10-28, 320 s headless): both reach frame 18,600 with no[RED]line, the same screenshots and 2.73 GB mapped; with 2.72 GB the GC's region is at alias +2.81 GB and the regions reach +4.31 GB of the 4.38 GB alias region (+4.09 GB with 2.5 GB), so it leaves less room for regions made later in play. The check now leaves the host's 256 MB for those and sets the value in a provisional or derived profile: a provisional pack of 1.1.1 (2026-10-28) set0xa2000000(2.53 GB), between the two, once its profile had this one'sgame_env(without it the GC's region is 3.75 GB and the check reports thatmalloc's region has no room). This profile keeps 2.5 GB, the size the iPhone run had. (The earlier runs used an extraction made from the NSPs in the work folder; the NSP script's own was pruned then.) - Current: the regions end 4.13 GB into the 4.375 GB alias region of Engage's 5 GB block. The mapped total peaks at 2.9 GB, under the hardware pool.
Rendering
The effects (.xnb) are XNA's D3D9 effects (MOJOSHADER is linked in and parses them for the parameters). Every binding decodes with the usual layout.
- Resolved: the whole picture upside down (the title logo and "Press Any Button" mirrored top to bottom). The game keeps NVN's lower-left window origin (handled as for Link's Awakening, 01006BB00C6F0000) and, twice a frame, sets viewport 0's swizzle with
nvnCommandBufferSetViewportSwizzlesto (x, −y, z, w), which negates clip-space y: how a renderer with OpenGL heritage flips its picture. The host had it as a red-reporting stub. It is recorded now (OP_VP_SWIZZLE) and applied at the draw as a viewport flipped about its centre, the same mapping. Facing is decided after the swizzle on the GPU as in Metal, so the winding stays. Other swizzles (axis exchanges, negated x / z / w) give a red line and are drawn unswizzled. - Resolved: the logo movie as a strip of small grey copies at the top of the screen (FNA3D's
nx_video_player.cpp: type 1, output 3, mode 2). The texture held NV12 bytes: in RGBA8, 405 rows of 1920 pixels are exactly a 1920×1080 NV12 frame, rows 0–270 the luma at video black (16) and rows 270–405 the chroma (128). This game's MP4 demuxer hands the parameter sets toConfigure(csd-0/csd-1), which made the H.264 decoder there without switching it to RGBA, and the first packet then found it made. Both paths now go throughmd_avc_open(hle_movie.c).
Files and shaders
- The shaders the GPU runs are 163 GLSLC outputs in
rom:/shaders.nx.bin(a u32 count, then per entry a u32 hash, a u32 size and one whole output), whichshader_split.pyreads as a loose file no other reader takes: 162 unique shaders, 83 pixel and 79 vertex, every vertex shader paired with the single-stage cover. Groupsfnaanddata(shader_groups). romfs_marker:data/textures.
Code
- Resolved:
RuntimeError: 788 ldr x18, #0xd1eb0inpatch_nso.py. The text segment starts with 1.7 MB of data (main+0x0–+0x1b5000, high-entropy, no FDE covers it, and noadrpor relocation points into it): 62% of its words do not decode, and 6,858 of the rest read as instructions that usex18, plus three "svc" words with numbers such as#0x91e7. The linear sweep rewrote them all.dm/tools/nso_code.pyfinds such data blocks (a stretch of at least 4 KB between the.eh_frameFDE ranges in which more than a quarter of the words do not decode); the sweep skips them (DATA_BLOCKS, logged asdata blocks left alone), and so doesinsn_census.py, whose counts then match the rewriter's again (11,744x18sites in thismain). Code without unwind information is still swept: it decodes (zlib atmain+0x75261c, libunwind's register restore atmain+0x28dbd4). - Padding halfword jump tables only to their largest entry (
half_count) changed one to six tables in thismain, as in a few other games' (Halfword jump table landing pads sized to the largest entry).
Services
- Resolved: SIGSEGV in
vi::CreateLayer(Layer**, Display*, int, int)(sdk+0x3f9bc4, from SDL3's Switch video driver atmain+0x239304): the overload with the layer's size; the host had only the two-argument one. It returns the static layer (hle_vi.c). The survey's import table had listed every vi call as unhooked:nso_syms.py host_hooksread onlydm/host, and the vi hooks are indm/gfx(it readsdm/gfxanddm/gfx/nvnnow). - AudioIn (SDL3's audio driver lists the input devices when its audio starts): the host has no microphone.
ListAudioInsreports none, both opens fail, and the calls that need an open AudioIn answer without touching the game's object (hle_audio.c). - Epic Online Services in an NRO (
rom:/nro/EOSSDK-Switch-Shipping.nro, loaded withro::LoadModule;mainimports theEOS_*functions). Before the host loaded NROs, the imports stayedUNIMPLstubs andEOS_Platform_Createhanded back a garbage handle; the game checks it ([EOS] Failed to create platform) and plays offline. Current: the host never loads an online SDK: an NRO that exportsEOS_Platform_Createis left out of the pack (dm/tools/nro_collect.pyONLINE_SDKS, arefusedrow innro.tsv) andro::LoadModulerefuses it with one log line (refused …: an online SDK (Epic Online Services), not loaded). Offline, local saves. - The modules:
mainimports EGL (SDL3's GL support, the same 13 EGL functions as Digimon Story Cyber Sleuth) andvkGetInstanceProcAddr(its Vulkan support), but FNA3D renders with NVN and names no GL function. NVIDIA's OpenGL and Vulkan drivers program the GPU through NvRm, which the host does not have, so they are never loaded (profile_guess.pyGPU_DRIVERS); the EGL imports stay unbound stubs that nothing calls.
Mac routes
- A fresh save reaches the first level at about frame 9,000 with
DM_KEYS="f1500:A,f1900:A,f2300:A,f2700:A,f3000:A,f3300:A,f3600:A,f4000:A,f4400:A,f4800:A,f5400:A*30/150"(any button, accept the licence, New game, the character select, then A through the intro and the first dialogue);LRightwalks, Y strikes and X uses the skill.
Found on this game
General host changes first needed here; each applies to every game:
- Data blocks at the start of the text left alone (
nso_code.py,DATA_BLOCKS): Data blocks at the start of the text. getenvanswered from the profile (game_env,hle_os.c) and the 32 MB alignment cut for large regions on SDK 18, later taken by Paper Mario: The Thousand-Year Door's and Celeste's SDK 15.3.2 build: .NET NativeAOT malloc and the GC region range, 1 GB alignment of regions of 1 GB and more.vi::CreateLayerwith a size, AudioIn absent, online SDK NROs refused (ONLINE_SDKS),nso_syms.py host_hooksreadingdm/gfx, NVIDIA's GPU drivers never loaded (GPU_DRIVERS):vi::CreateLayerwith the layer's size, No audio input devices, Online SDKs in NROs are never loaded, Listing a game's unhooked imports before they are met, GPU driver modules that are never loaded.- H.264 decoders made at
Configureswitched to RGBA (md_avc_open): Parameter sets inConfiguremade the H.264 decoder without RGBA. - Viewport swizzles (
OP_VP_SWIZZLE): Viewport swizzles applied as a flipped viewport. - GLSLC outputs in loose RomFS files: GLSLC outputs in main and in loose files.
- The host's StandardAllocator cap and the
stdalloc_capkey (hle_svc.c; the region check's advice): StandardAllocators over 12 to 62 GB of address space.
Open
- Shader counts differ: development.md says 162 GLSLC outputs in
shaders.nx.bin; 04 anddm/games/README.mdsay 163 outputs (162 unique). - Loading online SDKs: always load, refuse them (as now), or decide per profile is an open question for the maintainer (Online SDKs in NROs are never loaded).
These notes are republished from the nxnative repository under its MIT license (license and attribution). The original, which may be newer, is the game's notes on GitHub. This site hosts no game files.