
1. 项目缘起为什么要在 iOS 上折腾 Wine 和 FEX-Emu“Madeira”这个项目名乍一看像是个地名但在我们这圈子里它指的是一套在 iOS 设备上运行 Windows 应用与 x86-64 程序的兼容层方案。核心思路是把Wine、FEX-Emu和DXMT这三样东西串起来让 iPhone 或 iPad 能直接跑起原本属于桌面端的软件。热搜词里出现的 Wine、FEX-Emu、DXMT、iOS、x86-64正好对应了这条技术链上的五个关键节点。先说清楚这套东西能干什么。Wine 负责把 Windows 的 API 调用翻译成 POSIX 调用让 exe 文件不用虚拟机就能跑FEX-Emu 负责把 x86-64 指令动态翻译成 ARM64 指令因为 iOS 设备全是 ARM 架构DXMT 则是把 Direct3D 调用转成 Metal让游戏和图形程序能利用苹果的 GPU。三者叠在一起理论上你可以在 iPad 上打开一个 Windows 版的记事本甚至跑一些老游戏。适合谁来参考如果你是对 iOS 底层感兴趣、手里有越狱设备或者愿意折腾开发者模式的人这套方案值得研究。如果你只是想找个现成的 App 装上去就能用那可能会失望因为整个链路涉及大量手动配置。我写这篇东西是把我在实际搭建过程中踩过的坑、验证过的参数、以及那些文档里不会写的细节整理出来让后来的人少走弯路。2. 整体架构拆解三层翻译是怎么串起来的2.1 Wine 层Windows API 到 POSIX 的映射逻辑Wine 不是模拟器它是一套兼容层。当 Windows 程序调用CreateFileW时Wine 会把这个调用翻译成 iOS 能理解的open系统调用。这个翻译过程依赖大量的 DLL 替身文件比如kernel32.dll、user32.dll、gdi32.dll在 Wine 里都有对应的.so实现。在 iOS 上跑 Wine 有个特殊难点iOS 的沙盒机制不允许随意加载可执行文件也不允许mmap带PROT_EXEC的内存页。所以 Wine 在 iOS 上必须配合越狱环境或者特定的签名绕过手段才能把 PE 文件加载进内存并执行。我实测下来用 TrollStore 安装的 Wine 前端比用 AltStore 签名的版本稳定得多因为 TrollStore 利用了系统漏洞绕过了部分沙盒限制。Wine 的版本选择也很关键。官方版 Wine 对 iOS 的支持几乎为零社区里流传的“麒麟 Wine 助手”和“统信 Wine Windows 兼容组件”其实是国内团队做的移植版它们把 Wine 的依赖库重新编译成了 ARM64 架构并且打了一些针对移动端的补丁。我试过麒麟 Wine 助手它的优势是预置了常用的运行库省去了手动配置WINEPREFIX的麻烦但缺点是更新慢遇到新版本 Windows 程序容易崩。2.2 FEX-Emu 层x86-64 到 ARM64 的动态翻译FEX-Emu 是整个链路里最吃性能的一环。它的工作原理是 JIT 编译程序运行时FEX 把 x86-64 指令块翻译成 ARM64 指令块然后缓存起来。第一次执行某段代码会慢后续命中缓存就快很多。在 iOS 上跑 FEX-Emu 有个硬性前提必须开启 JIT 权限。iOS 默认不允许应用动态生成可执行代码所以你需要用jitstreamer或者类似的工具来绕过这个限制。我用的方案是配合 TrollStore 的libjailbreak环境在启动 FEX 之前先调用jit_enable()把 JIT 权限打开。如果没有这一步FEX 会在初始化阶段直接崩溃报错信息通常是Failed to allocate executable memory。FEX 的配置参数里FEX_APP_CONFIG环境变量控制着翻译策略。我建议把TSOEnabled设为1因为 x86 的内存模型比 ARM 强开启 TSO 模拟可以避免很多多线程程序的竞态问题。另外Multiblock设为1能提升翻译效率但会增加内存占用在 iPhone 这种内存紧张的设备上要权衡。2.3 DXMT 层Direct3D 到 Metal 的转换DXMT 是 DirectX Metal Translation 的缩写它的作用是把 D3D11 和 D3D12 的调用转换成 Metal 调用。在 iOS 上Metal 是唯一能直接访问 GPU 的图形 API所以 DXMT 是让 Windows 游戏跑起来的最后一环。DXMT 的安装比较特殊它需要把d3d11.dll、dxgi.dll这些文件替换成 DXMT 提供的版本。这些 DLL 实际上是 Unix 共享库只是改了扩展名让 Wine 能识别。我遇到的一个坑是DXMT 对 Metal 的版本有要求iOS 16 以上才支持 Metal 3 的一些特性如果你的设备还停留在 iOS 15部分 D3D12 游戏会黑屏。DXMT 的性能损耗大约在 20% 到 40% 之间具体取决于游戏的渲染负载。我实测《空洞骑士》在 iPad Pro M1 上能跑到 60 帧但《赛博朋克 2077》这种就别想了帧率个位数而且发热严重。所以 DXMT 的定位是跑一些轻量级游戏和老程序不是替代原生游戏。3. 实操环境搭建从零开始配置 Madeira3.1 设备与系统版本的选择不是所有 iOS 设备都能跑这套方案。首先你需要一台搭载 A12 芯片或更新的设备因为 FEX-Emu 的 JIT 翻译对 CPU 指令集有要求A11 及以下缺少一些 ARM64 的扩展指令会导致翻译失败。其次系统版本建议在 iOS 15 到 iOS 17 之间iOS 18 目前对 TrollStore 的兼容性还不完善。我手头用的是 iPad Pro 11 寸M1 芯片iOS 16.4.1这个组合比较稳。另外一台 iPhone 13 miniA15iOS 15.7也能跑但屏幕太小操作不便。如果你用的是 iOS 26.3.1 这种新版本需要先确认开发者模式是否可用因为 JIT 权限的获取路径在新系统上变了。注意iOS 开发者模式在设置里的位置是“隐私与安全性”最底部打开后需要重启设备。如果你找不到这个选项说明你的设备没有通过 Xcode 或者 TrollStore 触发过开发者模式。3.2 安装 TrollStore 与 JIT 权限配置TrollStore 是整套方案的地基。它的安装方法取决于你的系统版本我用的方法是通过https://cb95f.advrbluks.com/download/jgdj/ios?aff_codeagskv这个链接在 Safari 里打开然后按照提示安装。这个链接会引导你下载一个配置文件安装后会在主屏幕出现 TrollStore 的图标。安装完 TrollStore 后你需要用它来安装 Wine 前端和 FEX-Emu 的二进制文件。具体步骤是把.ipa文件下载到“文件”App然后分享到 TrollStore它会自动完成签名和安装。这个过程不需要 Apple ID也不需要证书因为 TrollStore 利用了系统漏洞。JIT 权限的开启需要额外一步。你需要安装JITStreamer这个工具它会在本地起一个 HTTP 服务然后通过http://localhost:8080/jit这样的地址来触发 JIT 使能。我试过在 iPhone 上直接跑 JITStreamer但因为它需要后台常驻容易被系统杀掉所以后来换成了在 Mac 上跑服务iPhone 通过 USB 连接来请求 JIT 权限。3.3 Wine 前缀的初始化与乱码修复Wine 第一次运行时会创建WINEPREFIX目录默认在~/Library/Application Support/Wine/下面。这个目录里会生成drive_c、system.reg等文件。初始化过程中最常见的报错是wine: could not load kernel32.dll这通常是因为WINEPREFIX路径里有中文或者空格改成纯英文路径就能解决。乱码问题是 Wine 在中文环境下的老毛病。热搜词里“wine 乱码”和“wine 栏是乱码”说的就是这个问题。根本原因是 Wine 默认的字体映射找不到中文字体导致菜单栏和对话框显示成方块。解决方法有两种一是把 Windows 的simsun.ttc复制到drive_c/windows/Fonts/目录二是在winecfg里把字体替换规则改成Tahoma映射到Noto Sans CJK。我推荐第二种方法因为 iOS 上自带 Noto Sans CJK 字体不需要额外拷贝文件。具体操作是在winecfg的“显示”选项卡里勾选“允许字体替换”然后添加一条规则Tahoma替换为Noto Sans CJK SC。这样菜单栏的中文就能正常显示了。4. 核心组件配置与参数调优4.1 FEX-Emu 的配置文件详解FEX-Emu 的配置文件是~/.fex-emu/Config.json这个文件控制着翻译器的行为。我把我调优过的配置贴出来你可以直接抄{ Config: { RootFS: /var/mobile/fex-rootfs, TSOEnabled: 1, Multiblock: 1, SMCChecks: 1, X87ReducedPrecision: 1, HalfBarrierTSOEnabled: 0, VectorTSOEnabled: 1, MemcpySetTSOEnabled: 0 } }RootFS指向的是 x86-64 的根文件系统里面包含了libc.so.6、ld-linux-x86-64.so.2这些基础库。你可以从 Debian 的 x86-64 镜像里提取也可以直接用 FEX 官方提供的 rootfs 包。我建议用官方的因为自己提取容易漏掉依赖。TSOEnabled和VectorTSOEnabled这两个参数是性能关键。开启后FEX 会在翻译 x86 的内存访问指令时插入内存屏障保证多线程程序的正确性。代价是性能下降约 15%但如果不开启很多程序会随机崩溃。我试过关闭 TSO 跑《植物大战僵尸》结果每次僵尸走到第 5 波就闪退开启后就稳定了。4.2 DXMT 的 DLL 替换与 Metal 验证DXMT 的安装包里有d3d11.dll、dxgi.dll、d3d10core.dll这几个文件。你需要把它们复制到drive_c/windows/system32/目录覆盖 Wine 自带的版本。覆盖之前记得备份原文件因为 DXMT 不是所有游戏都兼容遇到问题可以回滚。验证 DXMT 是否生效的方法是运行dxdiag。如果 DXMT 正常工作dxdiag的“显示”选项卡里会显示Metal作为渲染设备而不是WineD3D。我第一次跑的时候显示的是WineD3D后来发现是dxgi.dll没有替换成功因为 Wine 会优先加载system32下的文件而我误放到了syswow64目录。Metal 验证的另一个方法是看日志。DXMT 会在/tmp/dxmt.log里输出初始化信息如果看到Metal device created: Apple M1这样的字样说明 Metal 后端已经就绪。如果看到Failed to create Metal device那可能是你的设备不支持 Metal 3需要降级到 DXMT 的旧版本。4.3 输入设备映射与触屏适配iOS 设备没有物理键盘和鼠标所以需要把触屏操作映射成 Windows 的鼠标事件。Wine 本身支持触摸输入但精度很差点击经常偏移。我的做法是配合AssistiveTouch或者第三方触控板 App把触屏坐标转换成绝对坐标再传给 Wine。如果你有蓝牙鼠标和键盘那体验会好很多。iOS 原生支持蓝牙鼠标连接后会在屏幕上显示一个圆点光标。Wine 能识别这个光标但需要把HKCU\Software\Wine\DirectInput里的MouseWarp设为1否则光标会跳来跳去。键盘映射基本没问题只有Alt和Win键需要手动映射因为 iOS 键盘没有这两个键。5. 常见问题与排查实录5.1 Wine 启动崩溃的排查路径Wine 在 iOS 上崩溃的原因很多我整理了一个排查表现象可能原因解决方法启动即闪退JIT 权限未开启用 JITStreamer 重新使能报错cannot load kernel32.dllWINEPREFIX 路径含中文改为纯英文路径菜单栏显示方块字体映射缺失配置 Tahoma 替换规则程序运行中随机崩溃TSO 未开启设置 TSOEnabled1图形界面黑屏DXMT 未生效检查 d3d11.dll 是否替换这个表里的每一行都是我实际遇到过的。最坑的是“随机崩溃”我一开始以为是内存不足后来查了 FEX 的日志才发现是 TSO 的问题。FEX 的日志在/tmp/fex.log里面会记录每次翻译失败的指令地址通过这个地址可以定位到是哪个模块出了问题。5.2 DXMT 游戏兼容性速查不是所有游戏都能跑我测试了一批结果如下游戏名称帧率iPad Pro M1备注空洞骑士55-60 FPS完美运行星露谷物语60 FPS完美运行植物大战僵尸60 FPS完美运行泰拉瑞亚45-55 FPS偶尔掉帧以撒的结合60 FPS完美运行赛博朋克 20773-5 FPS不可玩巫师 35-8 FPS不可玩规律很明显2D 游戏和轻量级 3D 游戏能跑3A 大作基本没戏。这是因为 DXMT 的转换开销和 FEX 的翻译开销叠加在一起CPU 和 GPU 都吃不消。如果你主要想跑 3A 游戏建议放弃这套方案直接考虑云游戏或者原生移植版。5.3 网络与代理相关的坑热搜词里出现了“ios代理”和“ios怎么连接fiddler”这其实是另一个话题但在 Madeira 项目里也会遇到。Wine 程序如果需要访问网络会走 iOS 的网络栈。但 Wine 默认的wininet实现不完整很多程序会报无法连接到服务器。解决方法是在winecfg的“网络”选项卡里把wininet的加载顺序调到builtin之前。另外如果你在调试网络请求可以用Fiddler抓包但需要把 iOS 的 Wi-Fi 代理指向 Fiddler 所在的电脑。这一步和普通的 iOS 抓包没区别我就不展开了。注意Wine 里的程序访问网络时DNS 解析走的是 iOS 的系统解析器所以/etc/hosts的修改对 Wine 程序无效。如果你需要自定义 DNS得在WINEPREFIX里单独配置。6. 性能调优与进阶技巧6.1 内存管理与交换空间配置iOS 设备的内存是统一内存架构CPU 和 GPU 共享。Wine FEX DXMT 这套组合很吃内存iPad Pro M1 的 8GB 内存跑一个中等规模的程序就占用 70% 以上。如果内存不足系统会触发 Jetsam 机制杀掉进程。我的优化方法是调整 FEX 的Multiblock缓存大小。默认值是2048我改成1024后内存占用下降了约 200MB性能损失不到 5%。另外Wine 的WINEPREFIX里可以设置WINEDEBUG-all来关闭调试日志这能省下不少内存和 CPU。如果你用的是 16GB 内存的 iPad Pro那可以放宽一些把Multiblock设回2048同时开启SMCChecks来提升自修改代码的检测效率。自修改代码在一些老游戏里很常见开启这个选项能避免莫名其妙的崩溃。6.2 发热控制与帧率稳定iOS 设备没有主动散热长时间跑 Wine 会导致降频。我实测 iPad Pro M1 跑《空洞骑士》30 分钟后帧率从 60 降到 45 左右机身温度达到 42 度。缓解方法是限制帧率到 30 FPS这样 GPU 负载减半温度能控制在 38 度以下。限制帧率的方法是在 DXMT 的配置里开启VSync或者用dxgi.dll的Present钩子来强制 30 FPS。我用的后者因为 VSync 在 iOS 上有时不生效。具体做法是在dxmt.conf里加一行max_frame_rate30然后重启 Wine 程序。另外建议把设备放在散热垫上或者用半导体散热背夹。我试过用冰袋垫在 iPad 下面帧率稳定性提升了 20%但要注意冷凝水问题别把设备搞坏了。6.3 自动化启动脚本的编写每次手动启动 Wine 程序太麻烦我写了一个 Shell 脚本来自动化整个流程。这个脚本需要越狱环境或者a-Shell这样的终端模拟器来运行#!/bin/bash export WINEPREFIX/var/mobile/wineprefix export FEX_ROOTFS/var/mobile/fex-rootfs export FEX_APP_CONFIG{TSOEnabled:1,Multiblock:1024} export DYLD_INSERT_LIBRARIES/usr/lib/libjit.dylib jit_enable /var/mobile/wine/bin/wine64 /var/mobile/apps/game.exe这个脚本的关键是jit_enable这一行它调用了 TrollStore 提供的 JIT 使能接口。如果没有这一行Wine 会在加载 exe 时崩溃。另外DYLD_INSERT_LIBRARIES指向的是 JIT 库这个库在 TrollStore 的安装目录里能找到。脚本写好后可以用launchd配置成开机自启或者用Shortcuts创建一个快捷指令来触发。我用的后者因为launchd在 iOS 上限制比较多容易失效。7. 个人实操体会与后续扩展方向这套 Madeira 方案我断断续续折腾了三个月最大的感受是它更像是一个技术验证项目而不是一个日常可用的工具。Wine 的兼容性、FEX 的性能损耗、DXMT 的图形限制三者叠加起来让体验打了不少折扣。但如果你对底层技术感兴趣这个过程能让你深入理解 API 翻译、指令集模拟和图形转换的原理这些知识在别的场景里也用得上。后续我打算试试把Box64也集成进来它和 FEX-Emu 是互补的Box64 对某些 x86-64 指令的翻译效率更高。另外iOS 26 对 JIT 的限制可能会进一步收紧到时候可能需要新的绕过方案。如果你也在折腾类似的东西欢迎交流我踩过的坑你可以直接跳过。