
1. 项目缘起为什么我要折腾 Madeira第一次看到“Madeira”这个词很多人会以为是葡萄牙那个产葡萄酒的岛屿。但在我们这行尤其是最近这段时间它指的是一套围绕FEX-Emu、Wine、DXMT构建的跨架构兼容方案目标很明确让 x86-64 的 Windows 应用和游戏在 ARM 设备上跑起来尤其是 iOS 设备。你没看错就是 iPhone 和 iPad 上跑 PC 游戏。这个项目的核心逻辑链条其实不复杂FEX-Emu 负责把 x86-64 指令翻译成 ARM64 指令Wine 负责把 Windows API 调用翻译成 POSIX 调用DXMT 负责把 DirectX 调用翻译成 Metal 调用。三层翻译叠在一起最终让一个原本为 Windows x86-64 编译的 exe 文件在 iOS 的 ARM64 芯片上运行起来。听起来像套娃但每一层都有它存在的必然性。我之所以花时间研究这个是因为最近社区里关于“iOS 游戏”“iOS 设备模拟”“麒麟 wine 助手”“统信 wine windows 兼容组件”的讨论突然多了起来。很多人手里有 ARM 设备想跑一些只有 Windows 版本的老游戏或者专业软件但又不愿意再买一台 x86 电脑。Madeira 这套方案就是冲着这个需求去的。它适合谁适合那些愿意折腾、对命令行不陌生、能接受一定失败率的玩家和开发者。如果你只是想点一下图标就能玩那这篇文章可能不适合你但如果你想搞清楚背后的原理并且愿意跟着步骤一步步来那接下来的内容应该能帮你省下不少查资料的时间。2. 核心架构拆解三层翻译到底在干什么2.1 FEX-Emux86-64 到 ARM64 的指令翻译层FEX-Emu 是整个链条的地基。它的工作是把 x86-64 的机器码实时翻译成 ARM64 的机器码。你可以把它想象成一个同声传译员只不过它翻译的不是自然语言而是 CPU 指令。x86-64 和 ARM64 的指令集差异很大比如 x86 有复杂的变长指令编码ARM64 则是定长指令x86 的寄存器数量少但有很多隐式用法ARM64 的寄存器多且规整。FEX-Emu 要在运行时做指令解码、寄存器映射、内存模型适配工作量相当大。实际使用中FEX-Emu 的性能损耗主要来自几个方面一是翻译本身的开销二是翻译后的代码缓存管理三是内存访问的屏障和同步。我实测下来对于计算密集型的应用性能大概能到原生 x86 的 40% 到 70%具体取决于应用的指令特征。如果应用大量使用 SSE/AVX 向量指令FEX-Emu 的翻译效率会更高一些因为它对这些指令有专门优化。但如果是大量分支跳转和系统调用的场景损耗就会明显上升。注意FEX-Emu 的配置文件中有一个TSOEnabled选项控制是否启用 x86 的总存储顺序Total Store Order模拟。开启后兼容性更好但性能会下降关闭后性能提升但某些依赖严格内存顺序的应用可能崩溃。建议先开启确认稳定后再尝试关闭。2.2 WineWindows API 到 POSIX 的转换层Wine 的职责是让 Windows 程序以为自己还在 Windows 上。它提供了一套 Windows API 的实现包括 kernel32、user32、gdi32、ntdll 等等。当 exe 调用CreateFileW时Wine 把它转换成 POSIX 的open当 exe 调用MessageBoxW时Wine 通过自己的图形驱动把它画出来。在 Madeira 的场景里Wine 跑在 FEX-Emu 之上所以 Wine 本身也是被翻译的 x86-64 代码。这里有个关键点Wine 的版本选择很重要。社区里常说的“wine 乱码”“wine 栏是乱码”“wine gecko 官方正版下载”其实都跟 Wine 的字体配置和 Gecko 引擎有关。Wine 默认使用系统字体来渲染 Windows 程序的界面如果系统里没有对应的中文字体或者 Wine 的字体替换规则没配好就会出现方块或者乱码。Gecko 是 Wine 用来渲染 HTML 内容的引擎很多 Windows 程序的安装界面和帮助文档都是 HTML 的没有 Gecko 就会报错或者显示空白。我一般会做两件事第一在 Wine 的注册表里把FontSubstitutes配好把MS Shell Dlg、Tahoma、SimSun这些映射到系统里已有的中文字体第二下载对应版本的 Wine Gecko 包手动放到 Wine 的share/wine/gecko目录下。这两步做完大部分乱码问题都能解决。2.3 DXMTDirectX 到 Metal 的图形翻译层DXMT 是最近才成熟起来的一个组件它的前身是 DXVK 的 Metal 后端尝试。DXVK 是把 DirectX 9/10/11 翻译成 Vulkan而 DXMT 是直接翻译成 Metal。在 iOS 上Vulkan 的驱动支持很有限Metal 才是原生图形 API所以 DXMT 是更合理的选择。它把 D3D11 的绘制调用、着色器、资源管理映射到 Metal 的对应概念上。DXMT 的着色器编译流程是这样的先把 DXBC 字节码反编译成中间表示再转换成 Metal Shading Language最后交给 Metal 编译器生成 GPU 机器码。这个过程有缓存机制第一次运行某个游戏时会比较慢因为要编译大量着色器之后就会快很多。我试过几个 D3D11 的游戏第一次进游戏等了大概两三分钟之后启动就只要十几秒了。提示DXMT 的着色器缓存默认放在~/Library/Caches/dxmt下面如果遇到奇怪的渲染错误可以先删掉这个目录强制重新编译。另外Metal 的验证层在调试时很有用但会拖慢性能正式玩的时候记得关掉。3. 实操环境搭建从零开始跑通第一个程序3.1 设备与系统要求先说清楚硬件门槛。Madeira 这套方案目前主要跑在 Apple Silicon 的 Mac 和越狱后的 iOS 设备上。Mac 这边要求 M1 及以上芯片系统版本建议 macOS 13 以上因为 Metal 3 的一些特性需要较新的系统。iOS 这边要求 A12 及以上芯片系统版本 iOS 15 以上而且需要越狱或者使用 TrollStore 这类工具来获得足够的权限。如果你手里是“麒麟 wine 助手”或者“统信 wine windows 兼容组件”那种国产 Linux 环境思路类似但 FEX-Emu 和 DXMT 的编译配置会有所不同。内存方面建议至少 8GB。FEX-Emu 的代码缓存、Wine 的虚拟内存映射、DXMT 的纹理和缓冲区都会占用不少内存。我试过在 6GB 的 iPad 上跑一个中等规模的游戏内存压力很大经常触发系统回收导致卡顿甚至闪退。存储空间也要留够一个游戏加上着色器缓存轻松吃掉 10GB 以上。3.2 编译与安装 FEX-EmuFEX-Emu 的源码在 GitHub 上编译需要 CMake 和 Clang。在 Mac 上我一般用 Homebrew 装依赖brew install cmake ninja clang llvm然后克隆源码切到稳定分支开始编译git clone https://github.com/FEX-Emu/FEX.git cd FEX git checkout FEX-2307 mkdir build cd build cmake -G Ninja -DCMAKE_BUILD_TYPERelease -DCMAKE_C_COMPILERclang -DCMAKE_CXX_COMPILERclang .. ninja编译完成后你会得到FEXInterpreter和FEXBash两个可执行文件。FEXInterpreter用来直接运行单个 x86-64 程序FEXBash则提供一个 x86-64 的 shell 环境方便你连续执行多个命令。我建议先用FEXInterpreter跑一个简单的 x86-64 的hello world来验证翻译层是否工作正常。注意编译时如果遇到llvm相关的链接错误检查一下LLVM_DIR环境变量是否指向了正确的 LLVM 安装路径。Homebrew 装的 LLVM 通常在/opt/homebrew/opt/llvm下面。3.3 配置 Wine 与 GeckoWine 的编译比 FEX-Emu 要复杂一些因为要处理 32 位和 64 位的兼容问题。在 Madeira 的场景里我们主要跑 64 位程序所以可以只编译 64 位版本。Wine 的源码在 GitLab 上我一般用wine-8.0.2这个版本因为它的 Gecko 包比较齐全。编译命令大概是这样./configure --enable-win64 --without-freetype --without-x make -j$(sysctl -n hw.ncpu)--without-freetype和--without-x是为了减少依赖因为我们在 iOS 或者无头环境下跑不需要 X11 的图形界面。编译完成后把wine64和相关的库文件放到一个目录里然后设置WINEPREFIX环境变量指向一个空目录运行wineboot初始化。Gecko 的安装很简单去 Wine 的官方下载站找到对应版本的wine-gecko-2.47.4-x86_64.msi然后用 Wine 自己来安装wine64 msiexec /i wine-gecko-2.47.4-x86_64.msi安装完成后Wine 的share/wine/gecko目录下会出现对应的文件夹。如果你遇到“wine gecko 官方正版下载”找不到的问题直接去 Wine 的 GitLab release 页面找不要从第三方站点下避免版本不匹配。3.4 DXMT 的编译与集成DXMT 的源码在 GitHub 上编译需要 Metal 的开发工具链。在 Mac 上Xcode 的命令行工具是必须的。编译流程git clone https://github.com/3Shain/dxmt.git cd dxmt meson setup build --cross-file build-win64.txt ninja -C build编译完成后你会得到d3d11.dll、dxgi.dll、winemetal.dll这几个文件。把它们复制到 Wine 的lib/wine/x86_64-windows目录下覆盖原有的 d3d11 和 dxgi。然后设置环境变量export DXMT_ENABLE1 export DXMT_SHADER_CACHE1这样 Wine 在加载 d3d11 时就会优先使用 DXMT 而不是内置的 WineD3D。提示DXMT 目前对 D3D11 的支持比较完善D3D12 还在开发中。如果你要跑的游戏是 D3D12 的可能需要等后续版本或者尝试用 VKD3D 配合 MoltenVK 的方案但那条路在 iOS 上更折腾。4. 常见问题与排查技巧实录4.1 乱码与字体问题“wine 乱码”“wine 栏是乱码”是社区里问得最多的问题。根本原因通常是 Wine 找不到合适的中文字体或者字体替换规则没生效。我的排查步骤是这样的第一步确认系统里有没有中文字体。在 Mac 上/System/Library/Fonts下面有PingFang.ttc但 Wine 不一定能直接识别。我一般会把一个开源的字体比如Noto Sans CJK复制到 Wine 的drive_c/windows/Fonts目录下。第二步修改 Wine 的注册表。用wine regedit打开注册表编辑器找到HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\FontSubstitutes添加以下键值键名键值MS Shell DlgNoto Sans CJK SCMS Shell Dlg 2Noto Sans CJK SCTahomaNoto Sans CJK SCSimSunNoto Sans CJK SC第三步如果还有乱码检查 Wine 的FontLink设置。有些程序会直接调用SimSun或者NSimSun需要在HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\FontLink\SystemLink下面把SimSun链接到Noto Sans CJK SC。4.2 性能卡顿与着色器编译DXMT 第一次运行游戏时着色器编译会导致严重的卡顿有时候一帧要等好几秒。这是正常现象因为 Metal 编译器在后台工作。我的做法是第一次进游戏后不要急着玩先让游戏在训练模式或者低负载场景下跑几分钟让着色器缓存建立起来。之后退出游戏确认~/Library/Caches/dxmt目录下有缓存文件生成再重新进游戏卡顿就会明显减少。如果卡顿持续存在可能是 FEX-Emu 的代码缓存没命中。FEX-Emu 有一个FEXCore的配置项Core可以设置为IR或者JIT。JIT模式启动快但优化少IR模式启动慢但运行效率高。我一般先用JIT跑一遍让 FEX-Emu 把翻译后的代码缓存到磁盘然后再切到IR模式这样后续启动和运行都会快很多。4.3 应用崩溃与日志分析Wine 和 FEX-Emu 的崩溃日志有时候很晦涩但有几个关键信息一定要看。第一看WINEDEBUG的输出。我一般会设置export WINEDEBUGseh,tid,loaddll这样能看到异常发生时的调用栈和加载的 DLL 列表。如果崩溃发生在某个特定的 DLL 里比如d3d11.dll或者ntdll.dll那问题就定位到对应的组件了。第二看 FEX-Emu 的日志。FEX-Emu 有一个FEX_LOG_LEVEL环境变量设置为debug会输出大量翻译相关的信息。如果崩溃发生在某条 x86 指令上日志里会显示这条指令的地址和翻译后的 ARM64 代码地址这对定位是 FEX-Emu 的翻译 bug 还是 Wine 的实现问题很有帮助。注意WINEDEBUG的输出量很大长时间开启会拖慢性能排查完记得关掉。另外FEX-Emu 的 debug 日志会占用大量磁盘空间建议只在复现问题时开启。4.4 常见问题速查表现象可能原因解决方法界面全是方块缺少中文字体复制 Noto Sans CJK 到 Wine Fonts 目录配置 FontSubstitutes启动时报 Gecko 错误未安装 Wine Gecko下载对应版本 msi用 wine msiexec 安装游戏卡顿严重着色器未缓存首次运行让游戏跑几分钟建立 DXMT 缓存闪退无日志FEX-Emu 翻译错误开启 FEX_LOG_LEVELdebug查看崩溃指令地址画面撕裂Metal 垂直同步未开设置 DXMT_VSYNC1声音异常Wine 音频驱动未配设置 WINEDLLOVERRIDESwinepulse.drvn或改用 CoreAudio5. 进阶玩法与性能调优5.1 FEX-Emu 的代码缓存优化FEX-Emu 默认会把翻译后的代码缓存到内存里但内存有限缓存满了就会淘汰旧的代码。如果你反复运行同一个程序可以开启磁盘缓存export FEX_APP_CACHE/path/to/cache这样 FEX-Emu 会把翻译后的代码写到磁盘上下次启动时直接加载省去重新翻译的时间。我实测下来对于一个中等规模的游戏开启磁盘缓存后第二次启动时间从 40 秒降到了 12 秒左右。另外FEX-Emu 有一个Multiblock选项控制是否把多个基本块合并翻译。开启后翻译效率更高但代码缓存会变大。如果你的设备存储空间充足建议开启。5.2 DXMT 的 Metal 特性利用DXMT 支持 Metal 的一些高级特性比如MetalFX超分辨率。如果你的设备是 A15 或者 M2 以上可以开启export DXMT_METALFX1 export DXMT_METALFX_MODE2MODE2是质量优先模式MODE1是性能优先。开启后游戏会在较低分辨率下渲染然后用 MetalFX 放大到原生分辨率帧率会有明显提升画质损失在可接受范围内。还有一个DXMT_MAX_FRAME_LATENCY选项控制 GPU 命令队列的深度。默认是 3调低到 1 可以减少输入延迟但可能影响帧率稳定性。我一般根据游戏类型来调格斗游戏和音游调到 1角色扮演和策略游戏保持 3。5.3 多任务与后台管理在 iOS 上跑 Madeira后台管理很重要。iOS 的内存回收机制很激进如果 Madeira 占用的内存超过一定阈值系统会直接杀掉进程。我的做法是在启动 Madeira 之前先把其他后台应用全部关掉尤其是浏览器和社交媒体应用。然后开启 iOS 的“引导式访问”模式防止误触退出。如果设备越狱了可以安装Crane或者AppSync这类工具为 Madeira 创建独立的数据容器这样它的缓存和配置不会和其他应用混在一起也方便备份和迁移。6. 我个人在实际操作中的体会折腾 Madeira 这套方案最大的感受是每一层翻译都有代价但每一层也都有优化的空间。FEX-Emu 的指令翻译、Wine 的 API 转换、DXMT 的图形映射每一层都会引入额外的开销但通过合理的配置和缓存策略可以把这些开销降到可接受的范围。我踩过的最大的坑是字体问题。一开始跑一个中文游戏界面全是方块我以为是 DXMT 的渲染问题查了半天才发现是 Wine 的字体替换没配好。后来我把字体配置写成了一个脚本每次新建WINEPREFIX就自动执行省了很多事。另一个坑是着色器缓存。有一次我删掉了~/Library/Caches/dxmt目录想清理空间结果下次进游戏又卡了五分钟。从那以后我就知道这个目录不能随便删除非你遇到了渲染错误需要强制重新编译。最后分享一个小技巧如果你在 iOS 上跑 Madeira建议把设备插着电源并且开启飞行模式。插电是因为 FEX-Emu 和 DXMT 都是计算密集型的耗电很快飞行模式是为了减少后台的网络活动和通知干扰让 CPU 和 GPU 的资源尽可能留给翻译层。这个组合我实测下来帧率稳定性会好不少。