NX2Apple README

The project's README from github.com/LNPANDY/NX2Apple. As one markdown file: README.md.

NX2Apple

Play your own Nintendo Switch games on an iPhone or iPad. Point a macOS app at your own game dumps and it runs the whole pipeline: unpack, rewrite the game's code for Apple silicon, translate its shaders to Metal, and pack a slim IPA plus a sharded RomFS archive that a phone can actually install — then tells you the three steps to get it onto the device.

NS Port Builder

The project is based on nxnative — the NSO rewriter, the Horizon host, the NVN-on-Metal layer and the per-game profiles are upstream work, and this fork follows upstream automatically. What it adds:

English | 简体中文 · Formerly nxnative-romfs-archive (the old links redirect here).


The problem

A RomFS like Tears of the Kingdom's is 304,243 files / 15.9 GiB. Two things break:

Packing the tree into ~100 shard files solves both: the package stays small, and the game files move to the phone as one folder of large files.

The fix

A ROMFSARC archive: one index file plus N shard files.

romfs.idx        28 MB   header, name pool, file table, directory table
romfs.00 ... .125        126 shards, 200 MiB each, source bytes verbatim

Measured on Tears of the Kingdom (v1.0.0 + 1.4.3):

Plain RomFS ROMFSARC
Files 304,243 127
Largest directory 75,941 entries 126 shards, none over 200 MiB
IPA with the game inside 16.6 GB (fails to install) —
Slim IPA (the default, no RomFS inside) — 743 MB, installs
Copy to the phone hours one folder of 127 files

Path lookup is a binary search over the sorted file table; a directory listing walks one contiguous child range. The index is mmaped, shards are opened lazily, and reads go straight to pread on the shard, so the page cache still works.

The GUI front end

frontend/ is a macOS app that does steps 2–5 below with a window instead of a terminal, for people who would rather not read a build log: four inputs picked separately (base game, update, DLC, prod.keys), a dependency panel that detects and installs what is missing, the script's real stage list with live percentages, and the archive shards produced at the end.

界面截图(点开) NS Port Builder
cd frontend && swift run          # or double-click frontend/NS Port Builder.app

The interface is bilingual: it follows the device (Chinese for any Chinese variant, English for everything else) and the 中文 / EN button in the title bar switches at any time.

It runs the same scripts the command line does, so everything below still applies; see frontend/README.md. The prebuilt app, and the release notes in English and Chinese (including which games are verified), are on the releases page and in docs/RELEASE_NOTES.md.

Staying current with upstream

A scheduled GitHub Actions workflow (.github/workflows/sync-upstream.yml, daily 21:30 UTC, also runnable by hand) follows seedds/nxnative: it re-applies this fork's features onto upstream's tree through scripts/sync_upstream.py, verifies the result (Python 3.9 parse of every script, bash -n, the archive reader test, a macOS host build) and pushes — or opens an issue with the log when a patch no longer fits, pushing nothing. The repo is a fresh repository rather than a true fork, so GitHub's own "Sync fork" does not apply here; the last synced upstream commit lives in .upstream-ref.

Using it

1. Build the app (as upstream)

The archive feature does not change the normal build. Follow upstream's README to install Xcode, Homebrew tools and the Python packages, then pack an IPA.

git clone https://github.com/<you>/NX2Apple.git
cd NX2Apple
brew install xcodegen zstd spirv-cross
brew install --cask dotnet-sdk
python3 -m pip install lz4 capstone fonttools backports.zstd

2. What you need on disk

Three things, none of which ship with the repository:

What Where it comes from Used by
prod.keys dumped from your own console (see below) the extractor, to decrypt your game
The game's NSPs dumped from your own cartridge or eShop purchases the extractor
Xcode + iOS SDK the App Store the app build

prod.keys is the console's key set — a text file of name = hex value lines (~230 entries: master_key_xx, titlekek_xx, header_key, key_area_key_application_xx, …). Every Switch homebrew tool reads this format. It is not tied to a specific game: one prod.keys decrypts any of your games. The only requirement is that the file contains keys for the key generation (firmware era) the game was packaged with — a current dump covers every retail game.

Where to put it is up to you; you pass the path with -k:

# any layout works, e.g.
~/keys/prod.keys
# or keep it beside the game's NSPs
~/Games/Hades2/prod.keys
python3 scripts/build_ipa_from_nsp.py ~/Games/Hades2 -k ~/keys/prod.keys

How keys are dumped is documented across the Switch homebrew community (Lockpick_RCM from a hacked console is the usual route); this repository does not cover that step and ships none of it. Two practical notes from real dumps:

backports.zstd is needed for Tears of the Kingdom's zstd dictionaries; other games do not use it.

3. Pack the RomFS into an archive

python3 dm/tools/romfs_arc.py pack <RomFS dir> <output base> --shard-mib 200

<RomFS dir> is the directory nx_extract wrote (the one holding Pack/, Banc/, Sound/, …). The tool writes <base>.idx and <base>.00, <base>.01, … Files are grouped by top-level directory and cut so no shard passes --shard-mib, so one shard holds one kind of content.

Check the result before copying anything:

python3 dm/tools/romfs_arc.py verify <base> <RomFS dir>   # byte-compares every file
python3 dm/tools/romfs_arc.py list   <base> [path]        # list one directory

4. Build the IPA

The main build script packs a slim IPA by default (no RomFS inside; the app reads the game files from Documents/romfs), so step 1 already produced it. To re-pack just the package later:

GAME=<title id> W=<work dir> PATCHED=<patched dir> \
  ROMFS=<RomFS dir> MSL_IOS=<shader dir> \
  dm/ios/tools/build_ipa.sh <output.ipa>

--full-romfs (or DM_FULL_ROMFS=1) packs the RomFS into the package instead — upstream's behaviour, only sensible for small games.

5. Copy the archive to the phone

Put the whole set in the app's Documents/romfs/, keeping the base name:

Documents/romfs/romfs.idx
Documents/romfs/romfs.00
...
Documents/romfs/romfs.125

The app looks for romfs.idx first (dm/ios/Sources/main.m, romfs_locate). A plain directory tree is still accepted when there is no index, so nothing changes for existing games.

The index may sit at any of these places; the reader tries them in order:

<DM_ROMFS>.idx
<DM_ROMFS>/romfs.idx
<DM_ROMFS>/romfs/romfs.idx

Playing

Touch controls. Two layouts (tap the button at the top centre to switch): 2D (left stick, A/B/X/Y, shoulders, − / +) and 3D (adds a D-pad, a right stick and L3 / R3). With no controller connected, touches outside the controls go to the game's touch screen.

Physical controllers (GameController framework: Xbox, PlayStation, Switch Pro and MFi pads all work) hide the touch controls entirely while connected and bring them back when disconnected; meanwhile every touch goes to the game's touch screen. Buttons map by position, as on a Switch controller: bottom = B, right = A, left = Y, top = X, and the D-pad and right stick are kept.

Settings without rebuilding: Documents/env.txt, one KEY=VALUE per line. Common ones:

DM_LANG=zh-Hans        # Chinese (en-US by default)
DM_FPS_TARGET=30       # cap the frame rate (saves battery; the profile's fps_target by default)
DM_OPMODE=0            # handheld mode: 720p render instead of the docked 1080p

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.

Using it for another game

The archive format has nothing game-specific in it: the packer walks whatever directory you give it and the reader looks paths up in that tree. To switch games:

  1. Extract the new game's NSPs with nx_extract as usual (upstream's script does this).
  2. Pack that game's RomFS with romfs_arc.py.
  3. Rebuild the IPA with the new GAME= profile (the slim package is the default).
  4. Copy the new archive into Documents/romfs/.

Three things to watch for:

Games from upstream's tested list (4–6 GB) need roughly 20–30 shards at 200 MiB, well inside the limit, and their memory footprint is far below the per-app budget that stops Tears of the Kingdom.

What changed against upstream

Five files added, four modified. The fs layer keeps its normal path handling: when an archive is open, host_path_ex hands out a synthetic path (@romfsarc:<guest path>) for entries on the archive mount, and OpenFile / GetEntryType / OpenDirectory / the directory calls route those to the archive. File handles for archive entries are tagged with bit 63 so they never collide with real fds, and ReadFile clamps to the file size (a shard is a flat container, so an unclamped read past the end would return the next file's bytes).

File Change
dm/host/romfs_arc.h new — archive format and reader interface
dm/host/romfs_arc.c new — mmap index, binary-search lookup, lazy shard fds
dm/tools/romfs_arc.py new — pack / verify / list
dm/host/tests/t_romfs_arc.c new — walks a whole archive, checks every file
docs/ROMFS_ARCHIVE.md new — format and design notes
dm/host/hle_fs.c mount and route archive-backed paths
dm/host/Makefile build romfs_arc.c
dm/ios/Sources/main.m romfs_locate accepts romfs.idx
dm/ios/tools/build_ipa.sh slim by default (DM_FULL_ROMFS=1 = upstream full package)
scripts/build_ipa_from_nsp.py --work-dir (build folder away from the NSPs); derives DOTNET_ROOT (a framework-dependent nx_extract will not start from a plain shell without it)
frontend/ new — macOS GUI front end (see The GUI front end)

The mount is recognised by its directory, not its name: titles differ (rom for most games, content for Tears of the Kingdom).

Games

48 game profiles ship with this repository. A profile is what makes a build possible — the game's build IDs, memory layout, shader tables and per-game switches — and this fork adds the RomFS archive layer on top, so a large game can be installed at all.

The update column is the version the build actually checks: the update NSP's display version must match the profile's registered build (the base game's own version does not matter — the update layers over it). "无(v0)" = the base game alone is enough.

Game Title ID Update required Status
Super Mario Odyssey 0100000000010000 1.4.1 (v393216) Playable on an iPhone (upstream)
Ace Attorney Investigations Collection 010005501E68C000 1.0.0.2 (v131072) Profile present; not verified on a phone
Hollow Knight: Silksong 010013C00E930000 1.0.30000 (v655360) Profile present; not verified on a phone
Digimon Story Cyber Sleuth: Complete Edition 010014E00DB56000 1.0.3 (v196608) Profile present; not verified on a phone
Super Mario Bros. Wonder 010015100B514000 1.2.1 (v327680) Playable on an iPhone (upstream)
Mario Kart 8 Deluxe 0100152000022000 4.0.0 (v1441792) Playable on an iPhone (upstream)
Raging Loop 010020400E1C2000 无(v0) Profile present; not verified on a phone
ANIMAL WELL 010020D01AD24000 1.0.10 (v655360) Playable on an iPhone (upstream)
Super Mario 3D World + Bowser's Fury 010028600EBDA000 1.2.2 (v262144) Playable on an iPhone (upstream)
Hyrule Warriors: Age of Calamity 01002B00111A2000 1.3.0 (v327680) Playable on an iPhone (upstream)
Celeste 01002B30028F6000 1.4.1.1 (v589824) Profile present; not verified on a phone
The Great Ace Attorney Chronicles 010036E00FB20000 无(v0) Playable on an iPhone (upstream)
FINAL FANTASY TACTICS - The Ivalice Chronicles 010038B015560000 1.4.0 (v393216) Profile present; not verified on a phone
13 Sentinels: Aegis Rim 01003FC01670C000 1.10 (v65536) Playable on an iPhone (upstream)
DARK SOULS: REMASTERED 01004AB00A260000 1.0.3 (v196608) Profile present; not verified on a phone
Monster Train 2 010051701FB46000 1.1.1 (v131072) Playable on an iPhone (upstream)
OCTOPATH TRAVELER 0 01005270232F2000 1.0.5 (v327680) Profile present; not verified on a phone
Hades 0100535012974000 1.0.38233 (v589824) Playable on an iPhone (upstream)
Fire Emblem: Three Houses 010055D009F78000 1.2.0 (v327680) Playable on an iPhone (upstream)
FINAL FANTASY RESONANCE 0100606026F12000 1.0.1 (v65536) Playable on an iPhone (upstream)
Big Brain Academy: Brain vs. Brain 0100620012D6E000 1.2.0 (v131072) Profile present; not verified on a phone
Unicorn Overlord 010069401ADB8000 1.05 (v262144) Profile present; not verified on a phone
The Legend of Zelda: Link's Awakening 01006BB00C6F0000 1.1.0 (v131072) Playable on an iPhone (upstream)
DRAGON QUEST XI S: Echoes of an Elusive Age – Definitive Edition 01006C300E9F0000 1.0.4 (v262144) Profile present; not verified on a phone
Animal Crossing: New Horizons 01006F8002326000 3.0.3 (v2228224) Profile present; not verified on a phone
AI: THE SOMNIUM FILES - nirvanA Initiative 0100713016CCC000 1.0.2 (v131072) Playable on an iPhone (upstream)
The Legend of Zelda: Breath of the Wild 01007EF00011E000 1.9.0 (v1114112) Playable on an iPhone (upstream)
AI: THE SOMNIUM FILES 010089B00D09C000 1.0.1 (v65536) Playable on an iPhone (upstream)
STEINS;GATE RE:BOOT 01008BC024FDE000 1.0.3 (v131072) Profile present; not verified on a phone
Metroid Dread 010093801237C000 2.1.0 (v327680) Playable on an iPhone (upstream)
Super Mario Galaxy 010099C022B96000 1.3.1 (v327680) Playable on an iPhone (upstream)
Captain Toad: Treasure Tracker 01009BF0072D4000 1.4.2 (v393216) Profile present; not verified on a phone
Hades II 0100A00019DE0000 无(v0) Verified here — builds, installs and runs on an iPhone; 33-file archive
Cuphead 0100A5C00D162000 1.3.7 (v655360) Profile present; not verified on a phone
Fire Emblem Engage 0100A6301214E000 2.0.0 (v327680) Playable on an iPhone (upstream)
Absolum 0100A83022A22000 1.1.1 (v393216) Profile present; not verified on a phone
DRAGON QUEST VII Reimagined 0100A9D01C446000 1.2.0 (v131072) Profile present; not verified on a phone
Monster Hunter Rise 0100B04011742000 16.0.2 (v2228224) Profile present; not verified on a phone
Catherine: Full Body 0100BF00112C0000 1.0.1a (v131072) Profile present; not verified on a phone
Balatro 0100CD801CE5E000 1.1.3 (v851968) Playable on an iPhone (upstream)
Rhythm Heaven Groove 0100D9F01D474000 无(v0) Profile present; not verified on a phone
Luigi's Mansion 3 0100DCA0064A6000 1.4.0 (v327680) Playable on an iPhone (upstream)
Patrick's Parabox 0100E44016C22000 1.0.1 (v65536) Profile present; not verified on a phone
Paper Mario: The Thousand-Year Door 0100ECD018EBE000 1.0.1 (v65536) Profile present; not verified on a phone
The Legend of Zelda: Tears of the Kingdom 0100F2C0115B6000 1.4.3 (v720896) Verified here — builds, installs and runs on an iPhone; 304,243 files into 127 archive shards, byte-verified
Pokémon Legends: Z-A 0100F43008C44000 2.0.2 (v393216) Profile present; not verified on a phone
Brain Memory Training Academy 0100F5D01DA3A000 无(v0) Profile present; not verified on a phone
Super Mario Galaxy 2 0100FD8022DAA000 1.4.0 (v393216) Profile present; not verified on a phone

Verified on

The jetsam budget is the limit on big titles, and it can be raised. Without com.apple.developer.kernel.increased-memory-limit an iPhone gives this app ~3,375 MB (measured on a 12 GB device: Tears of the Kingdom was killed entering the world map at 3,342 MB resident with 33 MB of headroom; lowering the render scale to 0.5 did not change it, so the limit is the guest heap, not the GPU). The app requests the entitlement and build_ipa.sh embeds it in a placeholder ad-hoc signature — an unsigned binary has no signature for a sideloader to read entitlements from, so without that step the request would be lost. AltStore Classic 2.2+, Impactor and Xcode with a Personal Team keep it; SideStore with a free Apple ID needs a one-time App ID fix; Sideloadly does not. See dm/ios/ENTITLEMENTS.md for the steps and how to tell from Documents/log.txt (resident + headroom: ~3,375 MB = not in effect, 6–8 GB = it is).

Limitations

Credits

All of the emulation work is other people's. This repository adds the archive subsystem on top:

dm/third_party ships other projects with their licenses (list).

What this project is. A build tool for games you already own. It reads dumps you made of your own cartridges and purchases, on your own machine, and produces a package for your own device. Nothing in this repository, in the app, or in any release contains or distributes Nintendo's code, art, text, data, shaders, firmware, keys or any other copyrighted material.

What it cannot do. The pipeline has no source of games or keys: it requires the user's own prod.keys and their own NSP/XCI dumps, and it talks to no server. It does not bypass, strip or redistribute any protection — LibHac opens the user's own dumps with the user's own keys, the same operation a console performs on a cartridge.

Trademarks. Not affiliated with, sponsored by or endorsed by Nintendo. "Nintendo Switch" and game titles are trademarks of Nintendo Co., Ltd.; all other trademarks belong to their respective owners.

Your responsibility. Laws on making a personal backup copy differ by country; some places do not allow it at all. You are responsible for knowing and following the law where you live, and for never distributing game files, keys or the packages built from them — the project's outputs are for personal use on your own hardware only. If you enjoy a game, buy it.

Takedowns. If you believe anything in this repository infringes your rights, open an issue or contact the maintainer directly; valid claims will be actioned promptly, up to removing content or the repository.

No warranty. The software is provided "as is" under the licenses in LICENSE (this fork adds to upstream's; see the credits) — without warranty of any kind, express or implied. Use it at your own risk.

NX2Apple (LNPANDY/NX2Apple)

ipados
Searching Database…

Play your own Switch games on iPhone/iPad — one-click macOS app, no terminal. | 把 Switch 游戏装进 iPhone:图形界面一键构建,不用命令行。Bring your own game.

Creator
LNPANDY
Size
129.5 MB
Stars
462
Age
5d
Commits · 90 Days