seedds f5646efc47 Uniform updates that cover part of what a draw reads no longer end the render pass; clears with no draw ride on the next pass
Outdoors in Breath of the Wild (the user's Great Plateau save, Mac M4 Pro) a frame had about 386
render passes and loaded and stored about 900 MB of attachments, which an iPhone at 60 fps can
hardly move (about 55 GB/s). 220 of those passes ended only because the game updates the first
64 bytes of a 64 KB vertex uniform block before each draw group (nvnCommandBufferUpdateUniformBuffer
inside the pass) while its shaders read up to 544 bytes of it. A deferred update that covers only
part of what a draw reads used to end the pass.

- Partly covered blocks (nvn_mtl.m ubp_get): the shadow starts from memory and takes every inline
  update queued in the submission, newest last, incrementally per range. It is refused, and the
  pass ends as before, when something else may have written the range by the time the pass runs:
  a copy, fill or feedback write queued in the submission, a storage buffer a shader may write or
  a texture->buffer copy over it (sbw, by 4 KB page), or a GPU write or counter report of an
  earlier submission still running (gw). A counter report of the same submission does not count:
  the host writes reports on the CPU after the whole submission completes (Fire Emblem Engage
  writes its reports into a uniform block's page). DM_NVN_UB_PARTIAL=0 turns it off;
  DM_NVN_UBP_CHECK=1 lands the updates, waits for the GPU and compares every shadow with memory.
- cb_extent.tsv (new tool dm/gfx/tools/cb_extent.py, c1_guard.py's interval arithmetic over the
  translated MSL): per shader, how far into each uniform slot it can read, and the storage slots
  it never writes. Like c1_full.tsv, the host loads it only for its chain version; build_ipa.sh
  copies it, the NSP script writes it for provisional profiles, and the chain procedure
  regenerates it. Without it bindings are copied whole (up to 64 KB). Ten profiles have one.
- Route B's reflection never says whether a storage buffer is written, so every one read as
  read-only and its writes went unnoticed: a storage buffer now counts as written unless
  SPIRV-Cross typed it const (nvn_refl.m sbuf_rw) or cb_extent.tsv proves the slot unwritten.
- Lazy clears (lc_*): a set of targets that gets only clears before the game sets other targets
  no longer opens a pass just to store them; the next pass that attaches the same texture level
  and layer through the same view clears it as its load action, and the rest land as before
  before anything else could see them. Breath of the Wild's four clear-only passes before the
  G-buffer fold into its first pass. Off in TBDR mode; DM_NVN_LAZY_CLEAR=0 turns it off.
- The residency set line counts shadows from memory, refusals by reason, and lazy clears.

On the Mac, old (DM_NVN_UB_PARTIAL=0 DM_NVN_LAZY_CLEAR=0) and new side by side on each game's
route; attachment loads + stores per frame:
- Fire Emblem Engage battles: about 1,850 -> 360 MB, 88 -> 38 passes.
- Tears of the Kingdom: about 1,100 -> 700 MB, 124 -> 116 passes.
- Breath of the Wild outdoors: 386 -> about 158 passes, GPU p50 about 9.3 -> 6.5 ms, submit
  5.7 -> 4.2-4.9 ms; where the old host fell to 30 or 20 fps under load, the new held 60.
- Link's Awakening, 13 Sentinels: about 15% less. Super Mario 3D World: 5% less (lazy clears).
- Odyssey, Three Houses: 2-8% less. Mario Wonder, Metroid Dread: unchanged.

No new red lines in any of them. The shadow check found every shadow equal to the GPU's memory
(BotW 346,000, Engage 284,000, TOTK 86,000), and t_nvn passes 68/68. Screenshots differ only by
timing. Tried and dropped: DM_NVN_TBDR=1 (it turns the deferred updates off), DM_NVN_BARRIER=game
(no gain), and same-target continuation outside TBDR mode (BotW's decals end the pass anyway).

What still splits BotW's big passes: the GPU particles' compute dispatches (about 190 MB a frame)
and decals that sample the G-buffer. Mac shader libraries were built for the current chain of
TOTK, Engage, Wonder, Dread, Odyssey, Link's Awakening, Three Houses and 13 Sentinels (c1_full.tsv
headers already matched). The IPA ZeldaBreathOfTheWild-2026-10-21-passes.ipa (an earlier state of
these changes plus the uncommitted frame-step probes) awaits an iPhone test.
2026-10-21 12:57:48 +08:00

nxnative

Play your own Nintendo Switch games natively on iPhone and iPad. Bring your game (its NSP files) and your prod.keys; one script turns them into an IPA that you sideload.

It is not an emulator: the game's own code runs directly on the Apple chip, with Switch graphics rebuilt on Metal. Nothing from any game is in this repository, and the project is not affiliated with Nintendo or any game publisher.

Acknowledgements

This project is built on other people's work:

  • d2r-mobile (MIT) developed this native-execution approach, for Diablo II: Resurrected. nxnative (first called smbw-mobile) started as a copy of its generic parts: the NSO rewriter, the host that stands in for Horizon, the NVN-on-Metal layer and the iOS app shell. Fixes go both ways.
  • Ryujinx (MIT): its shader translator (Ryujinx.Graphics.Shader, Ryujinx.Common and Spv.Generator 1.3.2, in dm/third_party/ryujinx with a few fixes listed in its README) turns the games' Maxwell shaders into SPIR-V. Its emulation of the kernel and system services is also a reference when the host's behaviour is in doubt.
  • Atmosphère: its kernel, mesosphère, is the reference for Horizon's synchronization semantics (address arbiter, condition variables).
  • yuzu: Maxwell instruction encodings for the shader reference tool, and a cross-check for kernel behaviour.
  • LibHac: reads NSPs and layers updates over the base game in dm/tools/nx_extract. hactool did the first extractions.
  • SPIRV-Cross: SPIR-V to Metal Shading Language.
  • deko3d (zlib): its texture layout size algorithm is ported in dm/gfx/nvn/nvn_tex.c.
  • libvpx (VP9 movies), stb_vorbis (movie sound), the Vulkan headers and NVIDIA FLIP ship in dm/third_party with their licenses (list).
  • Capstone, lz4, fontTools and XcodeGen are used by the build.

Games

All of these are tested on an iPhone and playable:

Game IPA Notes
Super Mario Bros. Wonder ~4 GB
Fire Emblem Engage ~15 GB Upgraded to 60 fps (30 fps on Switch); the upgrade needs update 2.0.0, it does not work with other versions
Metroid Dread ~5 GB
13 Sentinels: Aegis Rim ~5 GB
Super Mario 3D World + Bowser's Fury ~3 GB
The Legend of Zelda: Breath of the Wild ~16 GB Upgraded to 60 fps (30 fps on Switch); the upgrade needs update 1.9.0, it does not work with other versions
The Legend of Zelda: Link's Awakening ~6 GB
Super Mario Odyssey ~6 GB
Hades ~4 GB
Hyrule Warriors: Age of Calamity ~15 GB Upgraded to 60 fps (30 fps on Switch); the upgrade needs update 1.3.0, it does not work with other versions
Fire Emblem: Three Houses ~13 GB Upgraded to 60 fps (30 fps on Switch); the upgrade needs update 1.2.0, it does not work with other versions
Super Mario Galaxy ~3 GB
The Legend of Zelda: Tears of the Kingdom ~17 GB Upgraded to 60 fps (30 fps on Switch); the upgrade needs update 1.4.3, it does not work with other versions
Monster Hunter Rise ~22 GB Upgraded to 60 fps (30 fps on Switch); the upgrade needs update 16.0.2, it does not work with other versions
Ace Attorney Investigations Collection ~7 GB
Hades II ~4 GB
Unicorn Overlord ~4 GB
Rhythm Heaven Groove ~3 GB
Octopath Traveler 0 ~4 GB Upgraded to 60 fps (30 fps on Switch); the upgrade needs update 1.0.5, it does not work with other versions
Pokémon Legends: Z-A ~6 GB
Cuphead ~5 GB
Super Mario Bros. Wonder
Super Mario Bros. Wonder
Fire Emblem Engage
Fire Emblem Engage
Metroid Dread
Metroid Dread
13 Sentinels: Aegis Rim
13 Sentinels: Aegis Rim
Super Mario 3D World + Bowser's Fury
Super Mario 3D World + Bowser's Fury
The Legend of Zelda: Breath of the Wild
The Legend of Zelda: Breath of the Wild
The Legend of Zelda: Link's Awakening
The Legend of Zelda: Link's Awakening
Super Mario Odyssey
Super Mario Odyssey
Hades
Hades
Hyrule Warriors: Age of Calamity
Hyrule Warriors: Age of Calamity
Fire Emblem: Three Houses
Fire Emblem: Three Houses
Super Mario Galaxy
Super Mario Galaxy
The Legend of Zelda: Tears of the Kingdom
The Legend of Zelda: Tears of the Kingdom
Monster Hunter Rise
Monster Hunter Rise
Ace Attorney Investigations Collection
Ace Attorney Investigations Collection
Hades II
Hades II
Unicorn Overlord
Unicorn Overlord
Rhythm Heaven Groove
Rhythm Heaven Groove
Octopath Traveler 0
Octopath Traveler 0
Pokémon Legends: Z-A
Pokémon Legends: Z-A
Cuphead
Cuphead

Needs iOS / iPadOS 26 or later on an iPhone 15 Pro or newer (A17 Pro chip or later) or an iPad with an M1 chip or later: the games' textures are BC-compressed, and older iPhones can't read that format. Other games aren't supported yet. For them the script writes a survey and still tries its best to pack an IPA, guessing the game's settings; such an IPA may not start.

Pack an IPA

1. Get nxnative and its tools (once, on a Mac with Xcode and Homebrew):

git clone https://github.com/seedds/nxnative.git && cd nxnative
brew install xcodegen zstd spirv-cross
brew install --cask dotnet-sdk
python3 -m pip install lz4 capstone fonttools

2. Put one game's NSPs in a folder, base game (an NSP, or an XCI cartridge dump) and update (and any DLC):

~/Games/MarioWonder/
  Super Mario Bros. Wonder [010015100B514000][v0].nsp
  Super Mario Bros. Wonder [010015100B514800][v327680].nsp

3. Run the script:

python3 scripts/build_ipa_from_nsp.py ~/Games/MarioWonder -k ~/keys/prod.keys

The IPA appears in the same folder (here ~/Games/MarioWonder/SMBWMobile.ipa). The first build takes 30–60 minutes, later ones about 10. You need free disk space of about twice the NSPs' size plus 10 GB. Options such as -o (output path) and --app-name are listed by -h.

4. Install the IPA with your sideloading tool (AltStore, Sideloadly, LiveContainer, …). The app gets the game's name, icon and version (the update's version, for example 1.2.1 for Mario Wonder; the base game's when there is no update).

Play

Touch controls. Tap 2D / 3D at the top to switch layouts; the app remembers your choice. Wonder, Engage, Dread, 13 Sentinels and Link's Awakening start in 2D, the other games in 3D. In 3D, the right stick (the camera in most games) is not drawn: swipe on the right third of the screen, starting anywhere off the buttons. In Super Mario Galaxy, other touches outside the controls move the pointer. A connected game controller hides the touch controls and keeps every button.

  • Game language is English by default. For Chinese, add the line DM_LANG=zh-Hans to Documents/env.txt.
  • Saves and logs are in the Files app, under the app's name: save/ and log.txt. When something goes wrong, send log.txt (the previous run is log.prev.txt).
  • Short stutters the first time a new scene appears are normal: the app compiles graphics for it once and remembers them for the next launch.
  • Reinstalling: some sideloading tools give a reinstalled app a new data folder. Copy save/ and pso_user.tsv over from the old one to keep your progress.
  • Settings without rebuilding: put lines like DM_OPMODE=0 (handheld mode: lower resolution, higher frame rate) in Documents/env.txt. DM_FPS_TARGET=30 keeps Breath of the Wild, Tears of the Kingdom and Octopath Traveler 0 at their original 30 fps, which saves battery. DM_SHOW_FPS=1 shows the frame rate at the top left (--show-fps when packing turns it on from the start).

Known issues

  • No amiibo and no online features.
  • Fire Emblem Engage: after switching away from the app and back, some sounds may stay silent until you restart it.
  • Fire Emblem: Three Houses: small square patches on the ground where decals land.
  • Super Mario Galaxy: effects drawn as points or lines come out one pixel wide.

For developers

How it works, the script's options, running games on the Mac, adding a game, and per-game notes: docs/development.md. In-depth bring-up and debugging notes: docs/agents.

License

MIT (see LICENSE). Third-party code keeps its own license; see dm/third_party.

S
Description
Run supported Nintendo Switch games natively on Mac, iPhone & iPad. One-command setup, complete Mac apps, lightweight iOS builds, Xbox/PS4/PS5 controllers and editable touch controls. Built on seedds/nxnative. Bring your own games and keys.
Readme MIT
108 MiB
0 Stars 1 Watchers 0 Forks
Languages
Objective-C 31.3%
C 28.4%
Python 28.4%
Swift 5.7%
Assembly 2.6%
Other 3.5%