在 Android 手机上运行 Windows 游戏的完整方案。通过 Box64 CPU 模拟 + Proton 11 兼容层 + DXVK/wined3d 软件渲染 + PulseAudio AAudio 音频输出,实现在没有硬件 GPU 加速的 Android 设备上运行 DirectX 9/11 游戏。
Windows 游戏无法直接在 Android 上运行,因为存在四层鸿沟:
Android (ARM64/Linux/Bionic) ←→ Windows (x86_64/Win32/NT)
本项目通过 6 层翻译/兼容层,逐层跨越这些鸿沟:
┌─────────────────────────────────────────────────────────────┐
│ Windows 游戏 (.exe) │
│ DirectX 9/11 + Win32 API 调用 │
└──────────┬──────────────────────┬───────────────────────────┘
│ │
图形 API 调用 系统 API 调用
│ │
┌──────────▼──────────┐ ┌────────▼──────────────────────────┐
│ 第1层: DXVK/wined3d │ │ 第2层: Proton 11 (Wine) │
│ DirectX → Vulkan/ │ │ Win32 API → POSIX API │
│ DirectX → OpenGL │ │ 注册表、文件系统、进程管理等 │
└──────────┬──────────┘ └────────┬──────────────────────────┘
│ │
Vulkan/OpenGL 调用 x86_64 机器码
│ │
┌──────────▼──────────┐ ┌────────▼──────────────────────────┐
│ 第3层: Mesa 软件 │ │ 第4层: Box64 │
│ 渲染器 │ │ x86_64 → ARM64 动态重编译 │
│ lavapipe (Vulkan) │ │ 系统库 wrapper 翻译 │
│ llvmpipe (OpenGL) │ │ WoW64 支持 32位+64位 │
└──────────┬──────────┘ └────────┬──────────────────────────┘
│ │
像素数据 ARM64 机器码
│ │
┌──────────▼──────────┐ ┌────────▼──────────────────────────┐
│ 第5层: X11/VNC │ │ 第6层: Termux (Bionic libc) │
│ TigerVNC 远程显示 │ │ Android 用户空间 Linux 环境 │
│ 端口 5901 │ │ aarch64 原生运行 │
└──────────────────────┘ └──────────────────────────────────┘
Windows 游戏使用 DirectX 图形 API,Linux 没有 DirectX。需要将 DirectX 调用翻译为 Linux 支持的图形 API:
DX11+ 游戏 → 使用 DXVK:
游戏调用 D3D11 API
→ DXVK 的 d3d11.dll (native, 替代 Wine 内置版本)
→ 翻译为 Vulkan API 调用
→ lavapipe 执行 Vulkan 渲染 (CPU 软件实现)
→ 像素数据写入 X11 窗口
DX9 游戏 → 使用 wined3d:
游戏调用 D3D9 API
→ wined3d (Wine 内置翻译层)
→ 翻译为 OpenGL API 调用
→ llvmpipe 执行 OpenGL 渲染 (CPU 软件实现)
→ 像素数据写入 X11 窗口
为什么 DX11 不用 wined3d?因为 wined3d 的 D3D11 实现不完整,很多游戏无法运行。DXVK 是成熟的 D3D9/10/11 → Vulkan 翻译层,兼容性好得多。
为什么 DX9 不用 DXVK 的 D3D9 组件 (DXVK-native)?因为在 lavapipe 上 DXVK 的 D3D9 实现不如 wined3d + llvmpipe 稳定。
Windows 游戏调用 Win32 API(CreateWindow、CreateFile、RegOpenKey 等),Linux 没有这些 API。Wine 提供了完整的 Win32 → POSIX 翻译:
游戏调用 CreateFileW("C:\\game\\data.dat", ...)
→ Wine 翻译为 openat(AT_FDCWD, "/home/user/.wine/drive_c/game/data.dat", ...)
→ 返回文件描述符,游戏认为自己在 Windows 上
为什么用 Proton 而不是普通 Wine?
- Proton 是 Valve 基于 Wine 的增强版本,包含额外的兼容性补丁
- Proton 的 WoW64 (Windows on Windows 64) 支持更好,可以同时运行 32 位和 64 位程序
- 本项目使用 Proton 11.0-1 x86_64 Bionic 版本(专为 Android Bionic libc 编译)
Wineprefix(Windows 虚拟环境):
- Wine 在 Linux 上模拟一个完整的 Windows 文件系统
p11prefix/drive_d/windows/system32/— 包含 808+ DLL 文件p11prefix/drive_d/windows/Fonts/— 包含中文字体p11prefix/drive_d/windows/mono/— 包含 .NET 运行时p11prefix/user.reg— 注册表,包含 DLL 覆盖规则和字体替代
关键细节 — drive_c → drive_d 符号链接:
Proton 使用 drive_d 作为 Windows 系统目录,但 Wine 内部通过 C:\ 路径操作,映射到 drive_c。如果没有 drive_c → drive_d 符号链接,Wineboot 初始化时所有 DLL 安装都会失败(error=3)。
Android 设备的 GPU (Adreno) 驱动无法在 Termux 中直接使用(需要 Android HAL,不能被普通进程加载)。因此使用 CPU 软件渲染:
- lavapipe:Mesa 的软件 Vulkan 实现,通过 llvmpipe Gallium 驱动执行 Vulkan 管线
- llvmpipe:Mesa 的软件 OpenGL 实现,使用 LLVM 在运行时生成优化的 SIMD 代码
Vulkan 调用 (vkCmdDraw, vkCreateBuffer, ...)
→ lavapipe 接收 Vulkan 命令
→ 转换为 Gallium3D 管线操作
→ llvmpipe 使用 LLVM JIT 编译着色器
→ CPU 执行顶点处理 + 光栅化 + 片段着色
→ 像素写入帧缓冲 → X11 显示
性能:软件渲染当然比硬件 GPU 慢,但对于 2D/轻度 3D 视觉小说游戏来说足够流畅。
游戏是 x86_64 机器码,Android 设备是 ARM64 CPU,指令集完全不同。Box64 通过动态重编译 (DynaRec) 实时翻译:
x86_64 指令流
→ Box64 DynaRec 识别代码块 (Basic Block)
→ 翻译为等价的 ARM64 指令序列
→ 执行翻译后的 ARM64 代码
→ 缓存翻译结果 (DynaCache),下次直接执行
系统库 wrapper: Wine 程序会链接 x86_64 的 libpulse.so、libvulkan.so 等,但系统只有 aarch64 版本。Box64 内置了这些库的 wrapper,将 x86_64 的函数调用翻译为 aarch64 原生调用:
Wine (x86_64) 调用 pa_simple_new()
→ Box64 wrapped libpulse 拦截
→ 翻译参数 (x86_64 → ARM64 ABI)
→ 调用系统 aarch64 libpulse.so 的 pa_simple_new()
→ 翻译返回值 (ARM64 → x86_64 ABI)
WoW64 (32 位支持): Proton 11 的 WoW64 实现允许 64 位 Wine 进程加载和运行 32 位 DLL。巧可甜恋 (ac_chinese.exe) 是 32 位程序,通过 WoW64 在 64 位 Wine 进程中运行:
64 位 wine 进程
├── 加载 64 位 DLL (kernel32, user32, ...)
└── WoW64 层
└── 加载 32 位 DLL (游戏本体, BGI 引擎, d3d9, ...)
Box64 性能优化:
BOX64_DYNAREC_BIGBLOCK=3 # 最大化翻译代码块,减少跳转开销
BOX64_DYNAREC_STRONGMEM=2 # x86 内存序保证 (Wine 需要)
BOX64_DYNAREC_CALLRET=1 # 优化 CALL/RET 指令对
BOX64_DYNAREC_FORWARD=1024 # 更大的前向跳转间隙
BOX64_DYNACACHE=1 # 缓存翻译结果到磁盘,下次跳过重编译Android 没有原生 X11 显示服务器。TigerVNC 在 Termux 中运行一个 X11 服务器,VNC 客户端(手机上的 VNC Viewer App)连接后显示画面:
Wine 渲染像素 → X11 窗口 → TigerVNC Xserver → VNC 协议 → 手机 VNC App 显示
游戏音频 (DirectSound/XAudio2)
→ winepulse.drv (Wine 内置 PulseAudio 驱动)
→ Box64 wrapped libpulse (x86_64 → aarch64 翻译)
→ 通过 TCP 连接 PulseAudio (127.0.0.1:4713)
→ PulseAudio module-aaudio-sink
→ Android AAudio API
→ 手机扬声器/耳机输出
为什么用 TCP 而不是 Unix Socket? Wine/Box64 通过 Unix socket 连接 PulseAudio 时会产生死锁(PA 在处理连接时阻塞,Wine 等待 PA 响应也阻塞)。改用 TCP 连接后问题消失。
为什么禁用 module-suspend-on-idle? PulseAudio 默认在空闲时挂起音频设备以省电。但 AAudio sink 挂起后无法恢复(Android AAudio 的限制),导致 PA 卡死。
为什么禁用 winealsa?
winealsa.drv 尝试加载 ALSA 配置文件,但路径指向 winlator 的 imagefs(/data/data/com.winlator/files/...),Termux 中不存在。通过 WINEDLLOVERRIDES="winealsa.drv=" 禁用。
~/proton11/
├── proton-11/ # Proton 11.0-1 x86_64 Bionic
│ └── lib/wine/
│ ├── x86_64-unix/wine # Wine 主二进制 (x86_64 ELF)
│ ├── x86_64-unix/winepulse.so # PulseAudio 驱动 (x86_64)
│ ├── x86_64-unix/winealsa.so # ALSA 驱动 (x86_64, 已禁用)
│ ├── x86_64-windows/winepulse.drv
│ ├── i386-windows/winepulse.drv # 32 位音频驱动 (WoW64)
│ └── i386-windows/wined3d.dll # 32 位 D3D9 翻译
├── p11prefix/ # Proton 11 wineprefix
│ ├── drive_c → drive_d # 符号链接 (关键!)
│ └── drive_d/windows/
│ ├── system32/ # 808+ DLLs + DXVK (d3d11.dll, dxgi.dll)
│ ├── Fonts/ # simsun.ttc, YuGothR.ttc, msyi.ttf, malgun.ttf
│ └── mono/mono-2.0/ # Wine Mono 11.1.0 (.NET 运行时)
├── box64/ # Box64 v0.4.3 bionic (源码+编译)
│ └── build/box64 # Box64 可执行文件
├── xaw64/ # xaw64 框架 (已修改支持 Proton 11)
│ └── xaw64 # 主脚本 (含 Proton 11 兼容补丁)
├── xaw64_box64/ # Box64 配置
│ ├── bin/box64 # v0.4.3 (v0.3.5 备份为 box64-0.3.5-bak)
│ └── etc/box64.box64rc # Box64 运行时配置
├── xaw64_wine/ # Wine 版本管理
│ ├── .wine-version # 内容: proton-11
│ ├── 10.7-stable → proton-11 # 兼容 xaw64 硬编码版本号
│ └── proton-11 → ~/proton11/proton-11 # 符号链接
├── xaw64_drivers/ # xaw64 驱动
├── dxvk-2.5.3/ # DXVK 2.5.3
│ └── x64/d3d11.dll, dxgi.dll # DX11 → Vulkan 翻译 DLL
├── wine-mono-11.1.0-x86.msi # Wine Mono 安装包
├── .cache/ # 缓存目录
│ ├── dynacache/ # Box64 代码翻译缓存 (~2.5MB)
│ └── shaders/ # 着色器缓存 (~2.8MB)
├── proton11-run # 通用启动器 (自动检测架构/DX版本)
├── run-proton11.sh # 星空列车与白的旅行专用脚本
├── fast-launch.sh # 巧可甜恋专用脚本
├── start-audio.sh # PulseAudio 管理脚本
└── PROTON11-SETUP-GUIDE.md # 本文档
| 项目 | 版本 | 作用 | 地址 |
|---|---|---|---|
| Termux | latest | Android 终端模拟器 + Linux 环境 | https://github.com/termux/termux-app |
| Box64 | v0.4.3 bionic | x86_64 → ARM64 动态重编译器 | https://github.com/ptitSeb/box64 |
| Proton | 11.0-1 | Windows 兼容层 (Valve Wine 增强版) | https://github.com/ValveSoftware/Proton |
| xaw64-wine | - | Termux Wine 一键部署框架 | https://github.com/ar37-rs/xaw64-wine |
| DXVK | 2.5.3 | DirectX 9/10/11 → Vulkan 翻译层 | https://github.com/doitsujin/dxvk |
| Mesa | Termux pkg | lavapipe (软件Vulkan) + llvmpipe (软件OpenGL) | https://gitlab.freedesktop.org/mesa/mesa |
| PulseAudio | 17.0 | 音频服务器 | https://www.freedesktop.org/wiki/Software/PulseAudio/ |
| TigerVNC | Termux pkg | X11 VNC 服务器 | https://github.com/TigerVNC/tigervnc |
| Wine Mono | 11.1.0 | .NET 兼容实现 | https://wiki.winehq.org/Mono |
pkg install x11-repo
pkg install tigervnc mesa vulkan-loader-android pulseaudio从 GitHub Release v1.0 下载所有 .tar.gz 文件,解压到 ~/proton11/:
mkdir -p ~/proton11
cd ~/proton11
tar xzf proton-11.tar.gz
tar xzf p11prefix.tar.gz
tar xzf box64.tar.gz
tar xzf xaw64.tar.gz
tar xzf xaw64_box64.tar.gz
tar xzf dxvk-2.5.3.tar.gz
gunzip wine-mono-11.1.0-x86.msi.gzcd ~/proton11/xaw64_wine
rm -f proton-11
ln -s ~/proton11/proton-11 proton-11mkdir -p ~/.config/pulse创建 ~/.config/pulse/default.pa:
#!/usr/bin/pulseaudio -nF
.ifexists module-aaudio-sink.so
load-module module-aaudio-sink sink_name=android_output
.else
load-module module-null-sink sink_name=dummy_output
.nofail
.endif
load-module module-device-restore
load-module module-stream-restore
load-module module-card-restore
load-module module-augment-properties
load-module module-native-protocol-unix auth-anonymous=1 socket=/data/data/com.termux/files/home/.config/pulse/pulseaudio.sock
load-module module-native-protocol-tcp auth-anonymous=1 listen=127.0.0.1 port=4713
load-module module-always-sink
创建 ~/.config/pulse/client.conf:
autospawn = no
~/.vnc/xstartup 中添加:
$HOME/proton11/start-audio.sh# 启动 VNC
vncserver :1
# 通用启动器 (自动检测架构)
~/proton11/proton11-run /path/to/game.exe
# 查看环境状态
~/proton11/proton11-run --status
# 音频管理
~/proton11/start-audio.sh # 启动
~/proton11/start-audio.sh status # 状态
~/proton11/start-audio.sh stop # 停止
~/proton11/start-audio.sh restart # 重启| 游戏 | 引擎 | 架构 | DirectX | 渲染路径 | 状态 |
|---|---|---|---|---|---|
| 星空列车与白的旅行 | Unity 2020.3 | x86_64 | DX11 | DXVK → lavapipe | 正常 (画面+音频) |
| 巧可甜恋 | BGI | x86 (32-bit) | DX9 | wined3d → llvmpipe | 正常 (画面+音频) |
n### Proot 环境 (machine-id / alsa)
在 Termux proot 环境下,某些系统文件不可访问导致游戏启动时的警告信息:
| 警告 | 原因 | 解决 |
|---|---|---|
Failed to open /etc/machine-id |
/etc 目录在 proot 内只读,无法读取 machine-id | LD_PRELOAD 劫持 open 调用重定向到 $HOME/.fake_machine_id |
Error initializing native libasound.so |
Box64 需要 libasound.so.2 但只有 libasound.so | 创建符号链接: ln -sf libasound.so libasound.so.2 |
proton11-init 会自动创建 alsa 符号链接。machine-id 修复在每个 launch-*.sh 脚本中已包含。
| 原因 | 症状 | 解决 |
|---|---|---|
| 缺少 drive_c 符号链接 | create_dest_file failed error=3 |
创建 drive_c → drive_d 符号链接 |
| 使用了 Wine 10.7 创建的前缀 | wineboot 每次都触发 | 用 Proton 11 创建独立前缀 |
| 时间戳过期 | wineboot 反复运行 | date +%s > .update-timestamp |
| box64 版本过低 | wineboot 长时间卡住 | 升级到 v0.4.3 bionic |
| 原因 | 症状 | 解决 |
|---|---|---|
| 未设置 VK_ICD_FILENAMES | 无 DXVK/Vulkan 输出 | 设置 lavapipe ICD 路径 |
| DllOverrides 为 builtin | DXVK 不加载 | 改为 native,builtin |
| 使用 virgl + vulkan-null | 黑屏 | 改用 DXVK + lavapipe |
| 原因 | 解决 |
|---|---|
| 缺少中文字体 | 复制 simsun.ttc、YuGothR.ttc 等到 Fonts 目录 |
| FontSubstitutes 未设置 | regedit 添加字体替代注册表项 |
| 原因 | 症状 | 解决 |
|---|---|---|
| module-suspend-on-idle | PA 运行后不久卡死 | 从 default.pa 移除该模块 |
| Unix socket 死锁 | 游戏启动后 PA 卡死 | 改用 TCP 连接 (PULSE_SERVER=tcp:127.0.0.1:4713) |
| winealsa 代替 winepulse | ALSA conf 找不到 | WINEDLLOVERRIDES="winealsa.drv=" 禁用 |
| socket 路径不匹配 | pactl 连接被拒 | 指定 socket= 路径参数 |
| autospawn 干扰 | kill 后自动重启 | client.conf 设 autospawn=no |
预装 Wine Mono: msiexec /i wine-mono-11.1.0-x86.msi /quiet
- 启动时间:首次 ~60s(DynaCache + 着色器编译),后续 ~30s(使用缓存)
- 帧率:2D 游戏流畅;3D 游戏受限于 CPU 软件渲染,复杂场景可能掉帧
- 硬件加速:不可用。Turnip (Adreno Vulkan 驱动) 需要 Android 15+ Bionic 的
call_once@LIBC_R符号,与当前设备不兼容 - 内存:建议 4GB+ RAM 设备,Proton 11 + 游戏运行时约占用 1-2GB