在 iPhone 上跑 Windows 游戏:Madeira 把三层模拟层压成一个进程
一个非越狱的 iPhone,跑起了 ULTRAKILL 和 Thumper——这两个都是 x86-64 Windows 上的 PC 游戏。不是串流,不是云游戏,是本机运行。Madeira 这个项目做到了一件理论上不应该可能的事情。
一个非越狱的 iPhone,跑起了 ULTRAKILL 和 Thumper——这两个都是 x86-64 Windows 上的 PC 游戏。不是串流,不是云游戏,是本机运行。Madeira 这个项目做到了一件理论上不应该可能的事情。
它的技术栈听起来像是一个"不可能三角":FEX-Emu 做 x86-64 → ARM64 的 CPU 指令翻译,Wine(ARM64EC 版本)做 Windows API 兼容层,DXMT 把 D3D11 图形调用转成 iOS 上的 Metal。三层翻译,每一层都有自己的复杂度,叠在一起更是指数级增长。
但 Madeira 真正的创新不在某一层,而在它怎么把这三层压成一个 Mach 进程。
wineserver 不是进程,是线程
这是 Madeira 最反直觉的设计决策。
在所有现存的 Wine 实现里——无论 Linux、macOS 还是 Android——wineserver 都是一个独立的进程。它是 Wine 的"内核":管理窗口、处理 IPC、协调 wineserver32 和 wineserver64 之间的通信。每个 Windows 进程通过 Unix domain socket 和 wineserver 通信,wineserver 再分发消息。
这个架构在桌面系统上没问题,因为桌面系统有完整的进程隔离、信号处理、fork/exec 语义。但 iOS 不一样。iOS 有严格的进程限制:一个 app 只能有一个进程(debugger attach 例外),不能 fork,不能 exec,不能创建新的 Mach 任务。如果 wineserver 是独立进程,它在 iOS 上根本跑不起来。
Madeira 的解决方案:把 wineserver 从独立进程改成一个线程。所有 Wine 内部的 IPC 从"进程间通信"变成"线程间通信",不走 socket,走共享内存和锁。
这不是简单的"把进程改成线程"。wineserver 的代码假设自己是独立进程——它有自己的信号处理、自己的 PID、自己的 /proc 条目。把这些假设全部剥离,让它在另一个进程的地址空间里作为线程运行,需要重写 wineserver 的启动流程、IPC 层、信号处理。Madeira 的 Wine fork 里有一个 LICENSE-MADEIRA.md,说明这些修改是 GPL-3.0-or-later 授权的——这是实质性的代码改动,不是配置调整。
ARM64EC:Wine 的 ARM64 适配用了微软的 ABI
Madeira 用的 Wine 不是普通的 Wine ARM64 版本,是 ARM64EC 版本。EC 是 "Emulation Compatible" 的缩写,这是微软为 Windows on ARM 设计的一种 ABI。
ARM64EC 的核心特点:x86-64 代码和 ARM64 代码可以在同一个进程里互调。你不需要在进程边界做架构切换,函数调用直接跨架构。这意味着 FEX-Emu 翻译出来的 ARM64 代码可以直接调用 Wine 的 ARM64EC 函数,不需要一个中间的 thunk 层。
微软设计 ARM64EC 是为了让 Windows on ARM 能渐进迁移——老 x86 DLL 和新 ARM64 DLL 可以混在一个进程里。Madeira 借用了这个 ABI,让 Wine 的 Windows API 实现可以直接被 FEX 翻译出来的 ARM64 代码调用,省了一层架构转换。
这是一个"借用别人的标准"的聪明决策。Madeira 不需要自己设计一个跨架构 ABI,微软已经设计好了,而且有文档、有工具链支持。代价是 Madeira 的 Wine fork 和上游 Wine 的差异更大——上游 Wine 没有 ARM64EC 支持,因为 ARM64EC 是 Windows on ARM 特有的。
DXMT:D3D11 → Metal 的直通管道
图形翻译是三层里最难的。CPU 指令翻译(FEX)是确定性的——每条 x86 指令对应一条或几条 ARM64 指令。API 翻译(Wine)是工程量的——每个 Windows API 对应一个 Unix 实现。但图形 API 翻译是语义层的——D3D11 和 Metal 的概念模型不完全对应,有些 D3D11 的操作在 Metal 里没有直接等价物。
DXMT 是这个领域已有的开源项目,做 D3D11 → Metal 翻译。Madeira 用了 DXMT 作为子模块,但 DXMT 本身是为 macOS 设计的,iOS 上的 Metal 有额外限制(比如某些着色器模型不支持、某些纹理格式不可用)。Madeira 的 DXMT fork 包含了 iOS 特定的适配。
三层翻译栈的完整路径是这样的:
Windows 游戏代码 (x86-64)
↓ FEX-Emu 翻译
ARM64 机器码
↓ 调用 Wine ARM64EC 的 Windows API 实现
Wine 内部处理 (作为线程运行)
↓ 调用 DXMT 的 D3D11 → Metal 翻译
Metal 图形调用
↓ iOS GPU 驱动
屏幕上的像素
所有这些发生在一个 Mach 进程里。没有进程间通信的开销,没有跨进程的内存拷贝。这是"模拟层压缩"——把传统上分属三个进程的三个翻译层,压成一个进程里的三个模块。
JIT 和 iOS 的限制:StikDebug 的角色
iOS 不允许 JIT。这是 Apple 的硬性限制——app 不能分配可写且可执行的内存页。这意味着 FEX-Emu 的 JIT 编译器(它需要在运行时生成 ARM64 机器码并执行)在 iOS 上不能正常工作。
Madeira 的解决方案是 StikDebug——一个利用 iOS debugger 接口实现 JIT 的工具。iOS 允许 debugger attach 到进程上,debugger 有特殊权限可以设置内存页为可执行。StikDebug 通过 task_set_exception_ports 等 Mach API,让 FEX-Emu 能在 iOS 上做 JIT。
这也是为什么 Madeira 不能上架 App Store——Apple 不会允许一个用 debugger 接口绕过 JIT 限制的 app 分发给普通用户。它只能通过 sideload 安装,而且需要每周重新签名(免费 Apple ID 的 provisioning profile 7 天过期)。
许可证的故事:LGPL §3 的实际应用
Madeira 的许可证结构值得单独说。它不是简单的 MIT 或 GPL,而是一个"混合许可证重组"的案例。
上游 Wine 是 LGPL-2.1-or-later。LGPL 允许你以动态链接的方式使用库而不开源你的代码,但如果你静态链接或修改了库本身,你需要开源你的修改。Madeira 的 Wine fork 选择了 LGPL §3 的路径:把修改后的 Wine 重新授权为 GPL-3.0-or-later。
LGPL §3 允许这样做:你可以把 LGPL 代码重新授权为 GPL,但之后这个修改版本就受 GPL 约束。Madeira 的作者 Will Faust 选择了这条路,意味着任何使用 Madeira 的 Wine fork 的项目都必须开源。
FEX-Emu 和 DXMT 上游是 MIT 许可,但 Madeira 对它们的修改是 GPL-3.0-or-later。这意味着 Madeira 的 fork 和上游不能直接合并——上游是 MIT,fork 的修改是 GPL,混在一起会有许可证冲突。README 里明确说:"do not submit AI-generated changes from this fork upstream"——因为 FEX-Emu 的贡献政策禁止 AI 生成的代码。
这个许可证结构反映了一个现实:当你把多个开源项目组合成一个新东西时,许可证会"向上收敛"——最严格的许可证会主导整个项目。MIT + LGPL + 0BSD 的组合,最终产物是 GPL-3.0。
研究项目,不是产品
README 里反复强调:"This is a research project, not a product." Thumper 和 ULTRAKILL 可玩,Marvel Cosmic Invasion 能进游戏但会崩,其他游戏帧率很低。每个游戏都有自己的怪癖,需要单独适配。
这不是一个"装上就能跑所有 Windows 游戏"的方案。它是一个"证明这条路走得通"的概念验证。走通的路是:x86-64 Windows 游戏 → ARM64 iOS,三层翻译压成一个进程,用 debugger 接口绕过 JIT 限制。这条路的技术可行性被验证了,工程成熟度还差得远。
Madeira 的意义在于它展示了"模拟层压缩"的极限。传统上,模拟器是"一个进程模拟一个系统"——QEMU 模拟 CPU,Wine 模拟 Windows API,DXVK 模拟 D3D。每层独立,进程间通信。Madeira 证明:如果你愿意重写每一层的进程模型,你可以把所有模拟层压成一个进程里的模块,消除进程间通信的开销。
这个思路不只适用于 iOS。在任何进程创建成本高、进程间通信受限的平台上——比如 WebAssembly(一个 wasm 模块不能 fork)、某些嵌入式系统(没有 MMU)——"模拟层压缩"都是一种可行的架构选择。
我的看法
Madeira 让我想起 2000 年代的 Wine 早期。那时候 Wine 也是一个研究项目,每个能跑的 Windows 应用都是新闻,跑起来也都有各种问题。20 年后,Wine 成了 Steam Deck 的底层(Proton),Valve 用它让 Linux 跑 Windows 游戏。
Madeira 走的是同一条路,但起点更高——它站在 FEX、Wine、DXMT 三个成熟项目的肩膀上,解决的是"怎么把它们压到 iOS 的进程模型里"这个工程问题。这个问题的解法(wineserver 作为线程、ARM64EC ABI、debugger JIT)是实打实的技术贡献,不是配置调整。
至于"iPhone 上跑 Windows 游戏"这个应用场景本身——它更多是一个技术展示,不是一个实用方案。iPhone 的屏幕太小、触控不适合 PC 游戏、电量消耗惊人。但 Madeira 验证的技术——单进程多层模拟——会在其他地方找到应用。也许是 Apple Vision Pro 上跑桌面应用,也许是云端游戏服务器上用单进程模拟降低延迟,也许是某个还没出现的场景。
技术展示的价值不在于它解决了什么问题,在于它证明了什么是可能的。Madeira 证明了:三层翻译可以压成一个进程,JIT 限制可以用 debugger 接口绕过,LGPL 可以通过 §3 重组为 GPL。这三件事中的每一件,都有独立的价值。
项目地址:https://github.com/willfaust/Madeira 许可证:GPL-3.0-or-later(Wine fork),MIT + GPL-3.0(FEX/DXMT fork) 状态:研究项目,非产品。Thumper 和 ULTRAKILL 可玩,其他游戏帧率低 要求:非越狱 iPhone(A15+)、Apple ID(免费即可)、StikDebug 用于 JIT