d2r-mobile Runs Diablo II on iPhone Without Emulating a Single Instruction
The Switch and your phone already speak the same language. Everything else (the OS, the graphics API, the 30-second save timer) is what had to change.
- The Switch and the iPhone both run ARM64, so d2r-mobile executes the game's shipped binary directly and patches only a handful of instructions rather than emulating a CPU.
- The real porting work is the platform: reimplementing Horizon OS calls and rebuilding Nintendo's NVN graphics API on top of Metal, shaders included.
- Lowering the save throttle from 30 seconds to 3 is the kind of mobile-specific change no general emulator would ever make, and it reveals who the project is actually built for.
- It is not a general emulator and it is not a free copy of Diablo II; users supply their own Switch game files.
It's Not an Emulator. The CPU Was Never the Problem.
A Switch game is running on an iPhone, and it is not being emulated. That claim sounds like marketing. It is actually an architectural fact about two chips that happen to share a language.
The Nintendo Switch is built on ARM Cortex cores. Every iPhone and Apple-silicon Mac is built on ARM64 cores. Same instruction set. The bytes of Diablo II: Resurrected's compiled program are already valid ARM64 code. An emulator's entire job, translating one instruction set into another, is simply unnecessary here.
So d2r-mobile does something narrower. It loads the game's shipped NSO modules, overwrites a small set of instructions, and reimplements everything around the program: the operating system calls, the graphics API, the input layer. The game runs natively. The platform is what gets translated.
That reframing is the whole story. A compatibility layer can be dramatically smaller and faster than an emulator when the instruction set matches. The cost is that it works for one game, on one hardware family, and the two are welded together forever.
An open-source iOS / macOS compatibility layer for Diablo II: Resurrected with the Reign of the Warlock expansion.
Cut Points and Gates: Surgery on a Shipped Binary
If you don't rewrite the instructions, what do you rewrite? A scalpel's worth.
An offline rewriter walks the game's binary and replaces selected instructions with branches into a gate mechanism. Function entries that need host intervention get replaced with a two-instruction cut point: svc #n; ret. When the game's program counter reaches a cut point, control leaves the binary and enters the host runtime, which decides what to do and hands control back.
Hundreds of instructions patched out of millions. The binary between the cut points is untouched.
The project takes patching safety seriously. Before it writes anything, it checks that the original value at a known offset matches what it expects. If the build ID doesn't match, it refuses to patch.
int32_t *p = (int32_t *)(m->base + 0x20cefa8);
if (*p != 30) { LOG("...构建号不符?),不改", *p); return; }
That guard is the difference between a working port and a corrupted binary. The same pattern appears at main+0x20a0074, where a sentinel mismatch triggers a hard die() instead of a quiet skip.
Rebuilding the Operating System the Game Expects
The game doesn't just compute. It calls into an operating system. It opens files through nn::fs, reads input through nn::hid, checks the clock through nn::oe, queries add-on content through nn::aoc, and asks about parental controls through nn::pctl. None of those exist on iOS.
So the project implements them. High-level emulation means writing a shim for every function the game calls into the OS. When the game hits a cut point that routes to nn::fs::OpenFile, the host runtime intercepts it and calls the equivalent Apple platform function instead. The game never knows the difference.
Networking is cut entirely. It returns a generic failure code. The online features of Diablo II: Resurrected simply don't exist in this build, and the project doesn't pretend otherwise.
The takeaway is that HLE is not clever. It is exhaustive. Every function the game calls needs a host-side answer, and the list is long.
NVN on Metal: Translating the Graphics API
The graphics path is where the real weight of the port lives. Nintendo's NVN graphics API is reimplemented on top of Apple's Metal.
Shaders are translated offline, not at runtime. A tool called dmsh_xlate converts NVN binary shader form into Metal Shading Language. A locked build chain (dmsh_chain.lock) keeps the translation reproducible across machines. A set of analysis scripts studies the tile-based deferred rendering behavior that Apple GPUs use and Tegra GPUs don't.
- The NVN API surface, reimplemented in
dm/gfx/nvn/ - The Metal backend in
dm/gfx/metal/mtl_backend.m - Texture format handling and Tegra-style deswizzling in
dm/gfx/tex/ - Offline shader translation and TBDR analysis in
dm/gfx/tools/
This is not a shim. It is a graphics backend, written against a console API, targeting a mobile GPU architecture the original game was never built for.
The 30-Second Save That Would Have Killed Your Character
Here is the detail that reveals the entire mindset. The game throttles non-forced saves to once every 30 seconds per container. On a console, that's fine. You're not going anywhere.
On a phone, iOS can kill a suspended app at any moment. A player creates a new character, backgrounds the app to answer a message, and the app is gone. The character might never have reached disk.
The host lowers the throttle to 3 seconds and exposes it as DM_SAVE_THROTTLE. That's it. One behavioral change, ten times more frequent saves, a small battery cost, and a character that survives an interrupted session.
No general emulator would make this change. It only makes sense if you understand that the target platform is a phone that can vanish your session without warning.
What It Isn't
Three honest caveats, stated plainly because the project states them plainly.
- It is not a general Switch emulator. It targets one game, one build, and one hardware family.
- It does not ship game files. Users must own the Switch release and provide the files themselves.
- It is not an official Blizzard product, and its legal footing is unusual.
A source of game content. It contains none and distributes none.
| Project | Approach | CPU handling | GPU handling | Scope | Needs game files? |
|---|---|---|---|---|---|
| d2r-mobile | Compatibility layer | None (native ARM64) | NVN reimplemented on Metal | One game | Yes (Switch release) |
| Ryujinx / Sudachi | General Switch emulator | Emulated | Emulated | Many Switch titles | Yes |
| Winlator / Mobox | Windows-on-Android stack | Emulated (Box64 / Wine) | Translated (DXVK, etc.) | Many Windows apps | Yes |
| DevilutionX | Engine reimplementation | Native | Native | Original Diablo | Yes (original assets) |
| Official D2R | First-party port | Native | Native | D2R | No |
Read the CPU handling column. d2r-mobile is the only row that says none. That single cell is the whole article: when the instruction set already matches, you don't need to translate the program, you need to translate the world around it.





