☰
暗黑2 开源 25 年编年史:DevilutionX 打好地基,d2r-mobile 把最后一块拼图搬上手机
2026/10/20 7:48:15 网站建设 项目流程

暗黑2 开源 25 年编年史:DevilutionX 打好地基,d2r-mobile 把最后一块拼图搬上手机

【免费下载链接】d2r-mobileOpen-source iOS / macOS compatibility layer for Diablo II: Resurrected with the Reign of the Warlock expansion. Runs your own Switch copy natively on iPhone, iPad and Mac — full touch controls, 60 fps, new endgame modes. Bring your own game files. 开源的《暗黑破坏神 II:狱火重生》术士君临 iOS / macOS 兼容层项目地址: https://gitcode.com/gh_mirrors/d2/d2r-mobile

2026 年,距离《暗黑破坏神 II》发售已经过去了二十五年。这个让"刷宝""Build""符文之语"成为一代人母语的游戏,其开源生态走过了一条远比官方版本更新更跌宕的路线:先是 Devilution 用逆向工程把 1996 年的初代《暗黑破坏神》从汇编里"复活",再由 DevilutionX 打磨成覆盖 PC、Android、Switch、3DS 的跨平台引擎;接着是 d2s-editor 这类工具链把.d2s存档格式吃透;而 2025 年出现在公众视野的 d2r-mobile,则选择了截然不同的第三条路——不重写、不模拟,把 Switch 版《暗黑破坏神 II:狱火重生》的原版 aarch64 程序"原封不动"地搬到 iPhone、iPad 与 Apple 芯片 Mac 上原生执行。

这篇文章把这三条支线放进同一条编年史里,并以 d2r-mobile 的源码级工程实践为核心,拆解"二进制改写 + 宿主接管 + Metal 重放"这条不依赖模拟器的移植路线,以及它如何用一套可回归、可复现的验证体系,把"能跑"变成"能天天玩"。

阶段一:引擎重构,DevilutionX 打下的地基

暗黑系开源的第一块基石是逆向工程。初代《暗黑破坏神》在 1996 年发布后,社区花费十余年逆向其 x86 汇编,最终把整份游戏逻辑重写为可读的 C++ 源码,这就是 Devilution。DevilutionX 在其之上做了现代化改造:SDL 渲染、原生宽屏与高帧率、现代编译器工具链、存档与多语言支持,并借由"引擎开源、资源自备"的模式铺开跨平台覆盖。

社区情报中关于 DevilutionX 的讨论密度,本身就是它生态成熟度的证据。近一年来多篇中文部署指南反复出现同一套动作:准备DIABDAT.MPQ、devilutionx.mpq等核心资源文件,然后在 Windows、Linux、Android、任天堂 3DS、Switch、PS Vita 上一路装下去,再调分辨率、帧率、纹理过滤、中文显示与多人联机。这些文章的技术门槛几乎为零——说明这个项目已经过了"能不能跑"的阶段,进入了"怎么配置更顺手"的生态成熟期。

DevilutionX 为后续项目留下两份最重要的遗产。其一是方法论:引擎层完全可以独立于商业内容而存在,玩家只需提供自己合法拥有的游戏文件;其二是生态纪律:开源侧只交付程序,内容侧永远划清边界。这两条原则,在二十多年后 d2r-mobile 的仓库里被以更严格的形式继承下来——README 开篇就写明"不含、也不分发任何游戏内容",仓库甚至内置了一道工具,在每次导出时用真实游戏文件对整棵树逐字节扫描,扫到疑似内容就不放行。

阶段二:工具链,把存档与格式吃透

在引擎重构之外,暗黑2 开源生态的第二块拼图是工具链。以 d2s-editor 为代表的存档编辑器,把.d2s二进制格式解析、MPQ 数据读取、CRC32 校验、Base64 物品编码做成了一套可复用的开源能力:角色属性、任务进度、传送点、物品装备全部可视化编辑,覆盖经典版与重制版。这些工具的价值在于把"游戏数据格式"从黑盒变成公共知识——后来的任何项目想要在存档层面做功能(比如 d2r-mobile 的 iCloud 存档同步、共享仓库预置),都站在这份知识之上。

工具链的繁荣是暗黑2 开源生态区别于多数怀旧项目的特征:它不只"再造一个能玩的引擎",而是把游戏相关的每一层——引擎、格式、数据、渲染——逐步开源化。到这一步,暗黑2 的开源版图里只剩一块公认的空白:移动端。官方从未发布过 D2R 的手机版,而手机恰恰是当下玩家基数最大的屏幕。

阶段三:移动端落地,d2r-mobile 的技术路线

不是模拟器,而是"原版程序在 iPhone 上原生执行"

d2r-mobile 对自己的定位非常明确:不是模拟器,不是云游戏,也不是串流。它把 Switch 版《暗黑破坏神 II:狱火重生》的main、sdk、subsdk0三个 NSO 可执行模块,经离线改写后直接在 Apple 设备上执行。

这条路线成立的关键在于一个硬件事实:iPhone 与 Switch 使用同一类 ARM 处理器。游戏本体是 aarch64 代码,绝大多数指令可以在苹果内核下原样运行,需要处理的只有少数依赖 Switch 专属系统环境的部分。问题被精确收敛成四类指令:

原指令为何不能原样执行改写为
x18寄存器读写苹果内核会改动该寄存器线程槽(per-thread 存储)的读写
tpidr_el0/tpidrro_el0读写游戏用它定位线程局部存储与 Horizon 线程区对应的线程槽
svc #n(系统调用)苹果平台上没有 Horizon 内核跳进宿主门(host gate),进入宿主 C 代码
cntpct_el0(系统计数器)Horizon 计时器在苹果平台无对应物走宿主门,由宿主以 19.2 MHz 计数器应答

这套改写逻辑全部实现在 dm/tools/patch_nso.py:它把 NSO 解压成内存映像,用 Capstone 反汇编扫描,只触碰上述几类指令;同时维护按构建号(build ID)分组的"剪口"(CUTS)表,每条剪口记录原始字节与改写字节,改写前逐条断言原字节一致,任何不匹配直接中止——绝不悄悄产出一个坏包。

剪口表里藏着大量"只有真机才懂"的工程细节。比如游戏显存分配器预算原本是 260 MB,在全面屏高分辨率下内部分辨率超过约 345 万像素(2736×1260 通过、2864×1240 崩溃)时渲染目标放不下,剪口把它改成 512 MB;比如启动路径被改写成直达主菜单,跳过开场 Logo 与"按 A 开始"页;比如"联网剪口"取自社区既有的离线修复补丁 LFS Mod Offline Fix,把"User Last Online"时间戳语义反转,使游戏恒走离线分支。

宿主:在苹果平台上重造一个"迷你 Horizon"

改写后的程序需要一套运行环境,这就是宿主(host)。dm/host/loader.c 扮演 Horizon 的 rtld 角色:映射三个模块的映像、分配线程槽、按gates.tsv登记宿主门、做跨模块重定位、设置段权限——代码段一旦装入即置为只读+可执行,此后装载器只写数据段。

内核层之下,dm/host/hle_svc.c 实现了 Horizon 的系统调用子集:预留一整块 5 GB 的客体地址空间,按 Horizon 的分区方式切出 alias、stack、heap。这里有一个非常"真机"的取舍——宿主把应用内存池报成 Switch 的 3285 MB 再加上 512 MB,因为游戏自己固定划走约 3.08 GB 后,主堆余量进局只剩 100–180 MB,换图时地板只有三四十 MB,原版在申请失败时多处不判空直接写,真机实测堆余量 138 MB 时发生过崩溃;多给的 512 MB 一比一变成主堆余量(实测 138 → 649 MB),而 iOS 上不碰的堆页不计入常驻内存。

系统服务层则由 dm/host/hle.h 里的HLE/HLE_FORCE两张宏表接管:HLE填补模块没有定义的导入,HLE_FORCE强制接管——即使 SDK 模块自带定义,所有引用也一律解析到宿主实现,用于 fs、oe、aoc、pctl 等nn::服务客户端。每条接管都必须写明理由,而表中存在却没有任何模块引用的条目会在装载时报红中止,"清单写错要响亮"。没有实现的导入则接到记名桩:调用时打印一行UNIMPL <函数名>并返回 0。联机、好友、商店这类在线功能统一应答"不可用",游戏自己走进离线分支。

图形层:NVN 逐函数接管,着色器离线翻译成 Metal

Switch 版游戏用 NVN(Nintendo 的 Vulkan 系原生图形接口)绘制。d2r-mobile 的思路是:不让 SDK 里任何一条 NVN 驱动指令执行。游戏主程序只导入一个取地址入口nvnBootstrapLoader,宿主强制接管它,之后游戏取的 523 个函数名(dm/gfx/nvn/names.txt)全部落到宿主侧的跳板。

接口层 dm/gfx/nvn/hle_nvn.c 把游戏的 NVN 调用录成命令,回放层 dm/gfx/nvn/nvn_mtl.m 在提交时把这些命令流回放成 Metal 调用:内存池建成 GPU 地址等于 CPU 地址的零拷贝MTLBuffer,管线状态按哈希缓存,启动时按预热清单在后台预建着色器与管线。

最硬核的部分是着色器。游戏的着色器是 Tegra X1 的 GPU 机器码(Maxwell 指令集),翻译链完全离线完成:

Switch 原版着色器包 → dm/gfx/tools/dmsh_xlate(用 Ryujinx 的翻译库出 SPIR-V,C# 工具) → spirv-cross(SPIR-V 转 Metal Shading Language) → xcrun metal / metallib(编成 Metal 着色器库)

每条链路由 dm/gfx/tools/dmsh_build.py 驱动。每个着色器以控制段(0x880 字节)的 sha256 前 16 位为键,运行时游戏把控制段交给nvnProgramSetShaders,回放层算出同一个键去命中库。为了让"翻译结果可追溯",整条链引入一个版本号 V——由涉及的所有源文件内容、翻译选项与工具指纹一起哈希得出,宿主编译时从 dm/gfx/nvn/dmsh_chain.h 取 V,只从b-<V>/目录加载并逐个核对,改链上任何一个文件或换了工具都要重新出库。配套的 dm/gfx/nvn/shader_facts.tsv 提供机器码里读不出的"事实"(贴图维度类型、图像格式、计算着色器线程组大小),由翻译器按键查询。

值得注意的是 dm/third_party/ryujinx 里的 Ryujinx 着色器翻译库以第三方代码的形式被直接复用——d2r-mobile 的图形层与任天堂 Switch 模拟器生态在着色器翻译这一环是共享底座的。

iOS 的硬约束:代码必须被签名、必须被内嵌

iOS 有一个任何桌面移植都不会遇到的硬约束:不允许运行时写入可执行内存。游戏代码因此不能像 Mac 宿主那样在启动时装载,而必须在构建时编进 App 的可执行文件。这带来了一套繁琐但严谨的构建链路:

  • dm/ios/tools/gen_embed.py 把改写后的模块映像生成内嵌汇编Gen/embed.S,按原相对布局放进可执行文件段;
  • 宿主的main.c以-Dmain=dm_main编入,由 App 在自有线程上调用;
  • 装载器在 iOS 上只取这些段的地址并核对布局,不拷贝代码、不改页权限。

由此倒逼出一个设计:改写后的补丁代码不能直接引用线程槽号或门地址(那会被写入运行时可写内存),而是通过一个只读的"键页"间接取数。这是 dm/README.zh-CN.md 里特别强调的架构取舍——所有线程槽编号都从键页读出,键页本身作为签名代码段的一部分编译进 App。

为手指重写一套交互,而不是贴个手柄上去

"为手指重写"是 d2r-mobile 与多数移植作品拉开差距的地方。设计原则只有一条:沿用原版界面语言,靠手势而不是往屏幕上加按钮。

其核心是一个不依赖 UIKit 的纯 C 状态机 dm/ios/Sources/dm_touch_controls.c(dm_tc_*系列函数):哪根手指属于摇杆、技能钮、腰带格还是界面,点按 / 双击 / 长按 / 拖放各算什么,最终输出该按下的手柄键与摇杆值。因为不依赖 UIKit,它可以在没有设备的 CI 环境里做单元测试。

这套状态机按游戏当前所处界面分三种情况驱动,且切换"不靠猜"——由游戏自己发出的界面消息驱动:

  • 前端页面(主菜单、选角、建角):不显示摇杆与技能钮,触点变成对游戏界面的鼠标点击;
  • 局内、没开面板:左侧摇杆、右侧技能钮、底栏按钮变成手柄键与摇杆;点在世界里(怪、物品名、NPC、箱子)由宿主解析目标;
  • 局内、开着面板或对话:手指直接点界面,物品格单击看说明、双击使用、长按、拖放,命中什么由宿主按游戏自己的面板树回答。

面板本身全部是游戏原版面板——外壳通过一份自带的"覆盖层 RomFS"(DM_ROMFS_OVERLAY)把原版手柄键位提示挪出屏幕、加关闭按钮、把手柄说法换成触摸说法。仓库只存"改了什么",不存原版内容:布局用结构补丁,字符串表按键名替换并用原文校验值核对版本。

性能:不是"能跑",是"能天天玩"

README 给出了 iPhone 17 Pro Max 上的实测数据:局内 60 帧稳定、热机不掉帧;App 启动 0.15 秒、到主菜单约 1 秒、主菜单进局约 1 秒;换幕时最长的一帧约 0.1 秒;整机功耗 60 帧约 3.8 W(营地 50% 亮度,满电近 5 小时),省电模式 30 帧约 1.85 W。安装包只有一百多 MB,约 5.6 GB 的游戏资源单独放进手机,升级 App 不动存档和资源。

性能来源在架构上可解释:程序原生执行,没有一条指令经过模拟;图形直走 Metal;着色器全部离线翻译、预先编译,进新场景无需现编。

承接与超越:DevilutionX 的地基,d2r-mobile 的收尾

把 d2r-mobile 放回 DevilutionX 的坐标系里,它的"承接"清晰可见,它的"超越"也值得单独说明。

承接的是理念:引擎与内容彻底分离("Bring your own game files",docs/GAME_FILES.zh-CN.md);跨平台是首要目标而非附加项——宿主、图形层、前端三层的分工天然为多平台留了接口,Apple 芯片 Mac 原生版与 iPhone/iPad 共用同一份游戏程序和同一个宿主,键盘、鼠标、手柄全部可用。

超越的是路径:DevilutionX 的路线是"重写引擎",d2r-mobile 的路线是"二进制改写 + 宿主接管"。前者适用于没有可用原版程序的老游戏(暗黑1 从未有官方现代版本),后者适用于有官方原版程序但缺平台的现代作品(D2R 有 Switch 版)。两种路线的适用边界被这个项目用实践划清楚了——同处一室验证了怀旧开源生态已经走到"按场景选路线"的成熟阶段。

超越的还有工程纪律。稳定不是靠运气,dm/verify/README.zh-CN.md 把验证体系写成项目的一部分:regress.sh用三样工具核对"改写没有改变程序行为"——structck.py做结构核对(不执行代码,逐字核对改写输出)、equiv.py用 Unicorn 对每一处改写以多组随机状态执行"原指令"与"补丁块"并比较全部寄存器、内存写入与结束地址,还带变异自检(故意改坏一批补丁块,必须全部被判定不等价);accept.sh则把改写等价回归、逐拍存档比对、画面闸门、单元测试、触摸用例串成一条完整验收链。真值数据用打补丁的 Ryujinx 模拟器跑原版游戏录制,"宿主结果与它比"。

怀旧游戏开源的新范式:内容边界、合规与共建

d2r-mobile 对怀旧开源最大的示范意义,在于它把"合规"做成了工程的一部分,而非法律声明。

docs/DERIVED.zh-CN.md 用整整一页讲清楚仓库里"有什么、没有什么、为什么":没有游戏程序、美术、音频、文字、数据表、着色器;有的是"关于游戏的事实记录"——着色器接口描述(shader_map.tsv、shader_facts.tsv)、图形接口函数表与程序内地址(约 800 处main+0x…标注)、改写规则、验证基线。没有这些记录,玩家用自己的游戏文件也做不出能跑的包;有了这些记录,仓库本身又不构成侵权内容。这一页的自我解剖,比任何免责声明都有说服力。

在此基础上,项目的协作模式是开放而自证式的:免费 Apple ID 即可从源码构建并安装;没有 Mac 也能走"壳 + 做包工具"路线(docs/NO_XCODE.zh-CN.md);老机型有配套的贴图包方案(docs/LEGACY_DEVICES.zh-CN.md);44 台内测设备完成了普通、噩梦、地狱三个难度的多角色通关;每条改动必须过回归闸门;CONTRIBUTING.zh-CN.md 明确欢迎把适配的游戏版本提交回仓库。

最后值得记录的是它的可迁移性叙事:README 在结尾把这条路线指向了安卓——"安卓手机用的是同一类处理器,游戏程序同样可以原生执行,这条路完全走得通:要换的只是宿主和图形层对接的那一侧"。并且已经有人拿同一份宿主去试别的 Switch 游戏了。DevilutionX 把"游戏可以在现代平台上重获新生"这件事变成了普遍可能,而 d2r-mobile 证明了另一件事:当原版程序与目标平台共享同一套指令集时,最忠实的移植不是重写,是"什么也别动"。这或许才是这份 25 年编年史最终沉淀下来的方法论——开源生态对经典游戏的守护,已经从"再造一个它",进化到了"原样运行它"。

【免费下载链接】d2r-mobileOpen-source iOS / macOS compatibility layer for Diablo II: Resurrected with the Reign of the Warlock expansion. Runs your own Switch copy natively on iPhone, iPad and Mac — full touch controls, 60 fps, new endgame modes. Bring your own game files. 开源的《暗黑破坏神 II:狱火重生》术士君临 iOS / macOS 兼容层项目地址: https://gitcode.com/gh_mirrors/d2/d2r-mobile

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询