Skip to content
NX Native

Super Mario Odyssey

Nintendo EPD0100000000010000

Plays on the iPhone.

Super Mario Odyssey with nxnative
Title ID
0100000000010000
Tested build
1.4.1
Nintendo SDK
7.3.2
Engine
Nintendo EPD (sead / agl, NintendoWare)
Graphics API
NVN

Build this game

Keep [0100000000010000] 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.4.1 (v393216) over the base game, SDK 7.3.2. Nintendo EPD's sead / agl engine with NintendoWare (G3d / Ui2d / Vfx), on NVN. Modules main, subsdk0 (the movie module) and sdk; DRAGON QUEST XI S 1.0.4 runs the same sdk build. Plays on the iPhone. The README lists its IPA at about 6 GB.

What it needs

Setting Where Why
pad_layout full game.tsv the 3D gamepad layout
4 GB layout: mem_reserve 0x100000000, mem_alias 0x10000000, mem_stack 0x10000000, app_pool 0xCD500000 game.tsv from the NSP script's provisional profile
Node area cut and 2 MB alignment of large regions (SDK 20's) dm/tools/sdk_patches.py SDK-wide rules, sdk build fcf93a41… DRAGON QUEST XI S's build, cut by the SDK-wide rules; Odyssey has no virtual address memory and never allocates an address region

No patches.py. op_mode stays docked: Odyssey only takes its motion moves in its default docked mode.

Input

  • Resolved: buttons and menus worked, the sticks did nothing in play (its first hint, "Look Around", waited forever). The game reads all 16 entries of the controller history (GetNpadStates) and ignores the returned count; the host filled only the newest. It polls every controller slot each frame, which moves the shared sampling number on by about 16, so the stick samples it combined were mostly empty, while buttons and the stick's direction bits (which menus use) still came through. The host now fills every requested entry with the current state under consecutive sampling numbers.
  • Resolved: motion moves never happened; shaking did nothing (cap throws by shaking). The host handed out six-axis handles but reported no sensor; it now reports one per connected controller. The game reads the six-axis history the same way (16 entries per sensor, the count ignored, main+0x93fd84). Its motion analyzer (main+0x5d5590) turns the newest acceleration, seen against the controller's pose, into a swing value per hand and sets the swing flags above 0.2. A still controller gives 0, and the host's shake (±3 G side to side) gives up to 1.5, so a Shake sets the left, right and any-swing flags for its length. To find the swing detector, probe the accelerometer value it reads (DM_PROBE=main:626248@x8+10 in 1.4.1). On the iPhone, shake the phone (a sharp flick); on the Mac, Space.

Rendering

  • Resolved: hard-edged dark boxes behind the HUD's layout panes (the coin counters' solid white cards, a dark box behind the "Look Around" hint; layout captures, as in Breath of the Wild). The game creates each capture with its per-format default mapping (an R8 capture reads (R, 0, 0, 1)), rebuilds it on the same storage with the capture's own mapping, (1, 1, 1, R), and replaces the registered descriptor only if nvnTextureCompare reports a difference (main+0x982320). The host compared everything but the mapping, so the captures kept alpha 1. Fixed by the general nvnTextureCompare compares the channel mapping. The profile's earlier workaround, alpha_from_red (all 26 layout pixel shaders, main's BNSH at 0x132c000, read such targets as (R, 0, 0, R)), is gone with its host code. Checked on the Mac from the "Look Around" hint with a probe host on the comparison: 40 rebuilds in the first 11,700 frames change only the mapping, R8 captures (640×80, 400×120, 390×80, 378×80, 300×40, 250×50, 128×128, 64×64, 48×57) from (R, 0, 0, 1) to (1, 1, 1, R) and a 320×180 RGBA one from (R, G, B, 1) to (R, G, B, A). Ignoring the mapping again brings back a dark box behind the hint (about 12,800 pixels from frame 4,200 on); with the old list the workaround converted the 378×80 capture and the frames matched the fixed host's; with the fix, the list converted nothing and changed nothing (the frames 4,200 to 6,600 equal to the pixel but for the hint video's corner). The comparison and the views run the same on the iPhone (no platform branch), so the rebuilt captures leave no (R, 0, 0, 1) target for the list to find there either.
  • Resolved: the Cap Kingdom fog never parted around Mario (reported on an iPhone; fix checked on the Mac). The fog's shading reads velocity fields that a small fluid simulation updates every frame: two 256×256 and two 128×128 RG16F targets, with a two-pass blur and a blended composite. Its only input is a point-sprite splat per mover, drawn with alPointSpriteShader: one vertex per mover, its size in the vertex data (Mario's main splat about 18 px, others 3–5 px) and a soft disc in the fragment shader (clamp(1 - 4r², 0, 1) × velocity). The vertex shader writes gl_PointSize = min(size, c0[0x610]), and the host had nothing at that offset of the driver segment, so every splat had size 0 and the fields stayed exactly zero (raw dumps with DM_NVN_TEXDUMP + DM_NVN_SNAPRAW). The host now writes the largest point size, 2047, at c0 + 0x610 for the graphics stages. Writing the SetPointSize value instead (always 1 here) left one-pixel splats whose disc weight came out 0 (the fields held -0.0). After the fix the fields hold velocities up to ±44 and the fog clears around Mario.
  • Resolved: the Cap Kingdom ground showed 32×32 blocks of a stale image on every fourth frame (Mac). Presents 4k were brighter and blocky, 4k+1 and 4k+2 clean, 4k+3 partly blocked, repeating to the decimal across runs; only with the ground's material (b6384a85afd3ef94) drawn. In some frames the game first draws a depth pre-pass (alRenderDepthOnly) for objects its occlusion queries report visible (the queries rotate through three slots, hence the cycle), then the G-buffer with other vertex shaders and depth test less-or-equal, without clearing the colour targets. Metal compiled the two vertex shaders' identical position math to slightly different depths, the G-buffer fragments failed the test and kept the previous content. Every Metal compile now uses -fpreserve-invariance. Ruled out on the way: the TBDR optimizations, same-pass feedback, the present path; evaluating the game's conditional rendering on the CPU from earlier frames' results made it worse (the condition belongs to the same frame on the GPU), so the host still ignores the condition here and draws (no render_cond).
  • Resolved: the Cascade Kingdom's waterfall mist drew hard-edged white slabs and triangles (GitHub issue #558, Mac). The mist itself, a half-resolution Vfx pass (an 800×450 RGBA16F layer, depth-tested against a 2×2-minimum depth), was soft; the edges came from its composite (alProgramShader, pixel 934386ce027e72be), a depth-aware upsample that blends the layer onto the scene. It draws 9 vertices from a vertex stream bound with 36 bytes (one full-screen triangle); the host read the 6 vertices past it from the pool's next bytes, and the triangle they made blended the mist in a second time. Fixed by the general Vertex fetches past a stream's bound size. Found from a Cascade Kingdom save, with per-pass snapshots (DM_NVN_TEXSNAP + DM_NVN_SNAPRAW), a numpy model of the composite and the draw's vertex dump (DM_NVN_PASSDUMP_ALL, DM_NVN_CBDUMP_KEY=40cc99352976d0ff_b). Ruled out on the way: soft-particle depth (the 628 Vfx shaders that sample the R16F linear depth read correct values), the 2×2 depth reduction, the light volume (alPrepassLight) and the water material.
  • Resolved: 16×16 magenta blocks scattered over the scene (iPhone 17 Pro, iOS 27.0.1, 1.4.1; seedds/nxnative#544). agl's post-effect program (5345f3231650f0b7, agl_technique_pfx.sharcb BAHS@0xa7038 in SystemData/AglResource.szs, the same program as 3D World's 66a71a3081b91b9a and Captain Toad's 54f58a7cab608766 in another build) samples slot 0 from the 1280×720 RGB10A2 image it renders into as attachment 0: the same-pass feedback read. The report's iPhone runs found it: with DM_NVN_FB_SNAP=all the blocks were gone and every snapshot the log sampled (frames 616 to 2,186) named this shader, and with it alone they stayed gone. It draws so only in handheld mode, the iPhone's: docked (the Mac's default) the game renders at 1600×900 and its post-effect reads another target, so the Mac never showed the blocks. The host now snapshots such draws without a per-game list; on the Mac with DM_OPMODE=0 (handheld) it snapshots this shader, and no other, from frame 617 on. Not yet checked on the iPhone. ba12a528caca7398 (agl_common.sharcb) renders level 1 of a texture while its view covers levels 0–1 (a mip-chain pass) and is left as it is.
  • Its three geometry shaders (a thunder effect) do not translate.

Movies

  • MP4 / H.264: the videos in its tutorial hint boxes and the kingdom travel videos, 41 of 44 High or Main profile with B-frames. The host decodes them with VideoToolbox; the SDK's demuxer passes the parameter sets as the avcc record (the MP4 avcC box), and pictures leave in presentation order through a small queue.

Services

  • Resolved: modern Chinese language codes selected Japanese. The general Chinese language alias comparison fix makes raw DM_LANG=zh-Hans and zh-Hant select Simplified and Traditional Chinese. Checked on the Mac with separate fresh test saves, through the startup mode-selection menus; no new gameplay or iPhone test.
  • Start-up calls no game before it made, each answered by the host: the aborting account::OpenPreselectedUser and GetLastOpenedUser (a null read), news delivery (bcat: the delivery storage does not mount, which the game treats as "nothing delivered"), HOME-button blocking, the parental 3D-mode setting, vi::SetLayerScalingMode, time::ToPosixTimeFromUtc, the debug pad, and the SDK 7 names of two audio-device calls (audio::AcquireAudioDeviceSwitchNotification aborted). Each aborted with no message in the SDK's IPC; frames resolved with sdis.py --sym.
  • Command memory: it hands out many 512-byte command memory grants before recording; with no recording to reclaim them by, 256 KB host segments each reached the cap entering the Wooded Kingdom, so an empty-pool grant of at most 4 KB outside a recording gets a 4 KB segment (hostseg_size). The GetCommandMemoryUsed count from the last AddCommandMemory (found on Three Houses) left it unchanged.
  • Shared fonts: SDK 7, so the address is the plain TTF. Focus handling mode 3 (no focus notifications; the system suspends the game out of focus).
  • Its SDK 7 driver's GPU code range (1.5–1.14, nvnDeviceGetInteger 0x2E–0x31) was measured here; the host now reports newer versions for newer SDKs.

Audio

  • It is silent for its first minute with either audio renderer update lock: the old SDK 16 offset and the one update_lock now reads off the SDK's own Update (MutexType at +0x1d8 on SDK 6.4–8.2) (The renderer's update lock).

Files and shaders

  • Resolved in the shader builder (#276): BAHS pipeline interfaces. The local 1.4.1 libraries have 290 pairs with missing vertex outputs, involving 29 vertex shaders. All are among the split's 12,124 BAHS pairs; the profile's 20,740 other pairs have no missing outputs. The shared fix makes padding read both tables; see Pixel shader inputs the vertex shader does not write. The report's Max Unstruct ID 758 ... exceeds supported limit lines did not fail the build: see the next item. Targeted validation: the affected vertex sources compile for Mac and iOS, and all 290 pairs build into g13s and g16s Metal archives with both depth entry points (Metal 32023.921). Full Mac rebuild: 27,984 of 27,984 shaders successful, no binding or pulling-library failures, no regressions, and no interface mismatches across all 32,864 pairs. The Mac host also rebuilt with the regenerated uniform tables. No new gameplay or iPhone run.
  • Resolved in the shader builder (#276): the 25-minute pipeline. Max Unstruct ID 758 is a note from Apple's GPU compiler, which then lowers the function a slower way; left to finish, metal-tt exits 0 with the pipeline in the archive. The report's exit 1 came from the BAHS link errors above. The function is the float body of alRenderMaterialUber (fragment 007d7e411ef14ebd, BNSH variation 0, paired with vertex 3c3435a62e05975b): alone on applegpu_g13s it took 24.6 minutes and 17.3 MB of code, which is also what held the reporter's startup prebuild at 7,008 of 32,864. With the front end at -O1 (Front end at -O1) it takes 31 s and 1.3 MB; the note remains, at 642. Full Mac app pack with that chain (Metal 32023.921): 27,984 of 27,984 shaders in 32:50, the g18g archive in 10:50 with no note, and the reporter's g13s archive of 32,864 bases in 15.6 minutes (exit 0, 912 MB). From save_new1, frames 4,200 to 6,600 (every 300) against an -O2 build of the same commit: six identical or within one pixel, frame 5,700 differs by the same 4,191 pixels between two -O2 runs too, and frames 4,800 and 6,300 by 1,140 and 2,059 grass pixels of at most 3 of 255. GPU time per frame over the same frames (one in 150, three runs each, alternating order): medians 5.05, 4.79 and 4.76 ms at -O1 (the 5.05 ms run was the first of the series), 4.76, 4.75 and 4.83 ms at -O2. No iPhone run.
  • Resolved (#294): the prebuild that stopped at 6,000 or 7,000. With the -O2 libraries, the same base (pair 5641 of the prebuild's 20,740) cannot be built at run time: on an M6 with 16 GB, MTLCompilerService aborted four times in llvm::report_bad_alloc_error and Metal returned an error after 33.7 minutes. Its compile held the pipeline archive's lock, so the prebuild and every draw stopped behind it. The host now writes the archive in flush periods that no compile waits for, and the prepare step no longer waits for a compile that does not return: A compile that does not return stalls every flush and draw. With the -O1 front end the base builds in 11.4 s. On an M6 with the -O2 library the prebuild now goes on past that pair: 18,739 of the other 20,739 bases were written within 7 minutes.
  • 27,984 shaders: agl sharcb and bfsha in ShaderData, particles in EffectData; groups per RomFS folder in game.tsv.
  • The spin-wait rewrite leaves its modules byte for byte as before the lock and store rules.

Mac routes

  • A fresh save wakes Mario in the Cap Kingdom with DM_KEYS="f1000:A,f8200:RLeft~6,f9200:LUp~4": the "Look Around" hint waits for the right stick held about 6 s one way (left then right cancels out). Play starts at about frame 9,900, and the game saves there.
  • From that save, f1000:A,f1300:A resumes into play at about frame 1,500, and f1600:LUp~7 walks straight across the fog to the hills by the bridge, where Cappy flees from Mario.
  • DM_KEYS takes RLeft / RUp / RRight / RDown for the right stick and Shake for the controller.
  • smoke.tsv (for scripts/regress.py) starts from a save made at the ship's "Look Around" hint, kept as saves/look_around in the NSP folder: f1000:A,f1300:A resume there, and Mario stands on the deck from about 40 s.

Found on this game

General host and tool changes first needed here; each applies to every game:

Open

  • The three thunder geometry shaders do not translate.
  • The silent first minute of sound.
  • development.md calls SDK 7.3.2 "the oldest so far"; Mario Kart 8 Deluxe (SDK 4.4.0) is older now.
  • game.tsv says values marked "Mario's" are not measured yet, but none is marked.

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.

More Nintendo EPD games