Porting WINE to a new Hobby OS

Porting WINE to a new Hobby OS

将 WINE 移植到自制操作系统 (Hobby OS)

A few months ago, I posted about Astral, a hobby OS I have been working on over the years, running Minecraft. Since then, others have gotten modern versions of Minecraft to run as well as Factorio (using a glibc compatible libc). However, while these games are made or packaged in a way that makes it easier to get them to run under a new OS, most games are not. A lot of games are closed source and compiled for Windows, which makes something like Wine a necessity for playing them. One of my favorite games, Cogmind, falls under that umbrella. It is a 32-bit Windows only roguelike, and it became my goal to run it under Astral. While there was already an existing Wine port, it was extremely incomplete, as not even notepad.exe worked properly. To run Cogmind, the Wine port had to be finished, which also meant adding the ability to run 32-bit code on an otherwise 64-bit-only OS.

几个月前,我发布了关于 Astral 的文章,这是我多年来一直在开发的自制操作系统,当时它已经可以运行《我的世界》(Minecraft)。从那以后,其他人也成功让现代版本的《我的世界》以及《异星工厂》(Factorio)(使用兼容 glibc 的 libc)在上面运行。然而,虽然这些游戏的制作或打包方式使得它们更容易在新操作系统上运行,但大多数游戏并非如此。许多游戏是闭源的,且专为 Windows 编译,这使得像 Wine 这样的工具成为运行它们的必要条件。我最喜欢的游戏之一《Cogmind》就属于这一类。它是一款仅限 32 位 Windows 的 Roguelike 游戏,让它在 Astral 上运行成了我的目标。虽然之前已经存在一个 Wine 移植版本,但它极不完整,甚至连 notepad.exe 都无法正常工作。为了运行《Cogmind》,必须完善这个 Wine 移植版,这也意味着需要增加在纯 64 位操作系统上运行 32 位代码的能力。

Basic Wine Functionality

Wine 的基本功能

The first step of getting Wine up and running was downloading MinGW and enabling it in the Wine build, as it is needed to compile the PE DLLs. With it enabled, notepad.exe works and selecting “Save as” does not crash it anymore!

让 Wine 运行起来的第一步是下载 MinGW 并在 Wine 构建中启用它,因为编译 PE DLL 需要用到它。启用后,notepad.exe 可以正常工作,选择“另存为”也不会再导致程序崩溃了!

Compiling libEGL.so

编译 libEGL.so

There is still one big problem: Wine is being compiled without OpenGL support. While Astral did have OpenGL, Wine explicitly needs EGL to work, which Astral’s Mesa port did not provide. EGL connects rendering APIs like OpenGL to the windowing system, which Wine needs to properly initialize graphics. At first, it seemed like a simple fix, just enable it in Mesa. However, Mesa does not support EGL on the xlib backend, forcing me to switch over to the DRI backend instead. DRI (Direct Rendering Infrastructure) allows applications to communicate more directly with the GPU rather than going through the X server. This led me into a rabbit hole of having to patch Mesa to allow the X.org server to start without /dev/dri. In the end, I was successful and got a real game, Deltarune, to start.

目前还有一个大问题:Wine 在编译时没有 OpenGL 支持。虽然 Astral 确实有 OpenGL,但 Wine 明确需要 EGL 才能工作,而 Astral 的 Mesa 移植版并未提供 EGL。EGL 将 OpenGL 等渲染 API 连接到窗口系统,这是 Wine 正确初始化图形所必需的。起初,这看起来是个简单的修复,只需在 Mesa 中启用它即可。然而,Mesa 不支持 xlib 后端上的 EGL,这迫使我转而使用 DRI 后端。DRI(直接渲染基础设施)允许应用程序更直接地与 GPU 通信,而不是通过 X 服务器。这让我陷入了一个深坑,不得不修补 Mesa 以允许 X.org 服务器在没有 /dev/dri 的情况下启动。最终,我成功了,并让一款真正的游戏《Deltarune》运行了起来。

WoW64 and 32-bit Windows programs

WoW64 与 32 位 Windows 程序

As Cogmind is 32-bit and Astral is 64-bit, more infrastructure is needed. This was accomplished using Wine’s WoW64 mode, which does not need any 32-bit Unix libraries. It works by running 32-bit Windows binaries in a 64-bit process, translating between 32-bit and 64-bit system calls and data structures as needed, avoiding the need for a full 32-bit userspace. Getting it to work mostly involved implementing LDT (Local Descriptor Table) support in the kernel, as x86-64 allows you to run 32-bit code in long mode using 32-bit segment descriptors. These describe how memory should be accessed and, in the case of code segments, how the processor runs those instructions. The LDT is one of the mechanisms used to define these segment descriptors and allows them to be configured on a per-process basis. It also needed some finicky glue in Wine’s signal and syscall handling code.

由于《Cogmind》是 32 位的,而 Astral 是 64 位的,因此需要更多的基础设施。这是通过 Wine 的 WoW64 模式实现的,它不需要任何 32 位的 Unix 库。它的工作原理是在 64 位进程中运行 32 位 Windows 二进制文件,根据需要转换 32 位和 64 位系统调用及数据结构,从而避免了对完整 32 位用户空间的需求。要实现这一点,主要涉及在内核中实现 LDT(局部描述符表)支持,因为 x86-64 允许通过 32 位段描述符在长模式下运行 32 位代码。这些描述符定义了内存的访问方式,对于代码段,还定义了处理器如何运行这些指令。LDT 是用于定义这些段描述符的机制之一,并允许按进程进行配置。此外,这还需要在 Wine 的信号和系统调用处理代码中进行一些精细的适配。

Cogmind!

《Cogmind》!

After implementing WoW64 support in the Astral port and fixing a few other bugs in the kernel, Cogmind works! The game was playable and did not have any noticeable issues other than game news and scoresheet upload not working.

在 Astral 移植版中实现 WoW64 支持并修复内核中的其他几个 Bug 后,《Cogmind》成功运行了!游戏可以正常游玩,除了游戏新闻和成绩单上传功能无法使用外,没有其他明显问题。

Broken scoresheet upload

损坏的成绩单上传功能

This bug manifested by a TCP connection to the Cogmind servers being opened and instantly closed without any data being transferred. At first, I thought this was a network stack issue, but this was not the case. What made me suspicious it was something else was that the Wine debugging log function, __wine_dbg_write, was not working at all in WoW64. After digging deep into Wine’s code, I eventually noticed I was forgetting to save a register in the __wine_unix_call_dispatcher function. This broke the PE to Unix transition and led to UB (Undefined Behavior). With this fixed, scoresheet upload works!

这个 Bug 的表现是:与《Cogmind》服务器的 TCP 连接被打开后立即关闭,且没有任何数据传输。起初我以为是网络栈的问题,但事实并非如此。让我怀疑是其他原因的是,Wine 的调试日志函数 __wine_dbg_write 在 WoW64 中完全无法工作。在深入研究 Wine 的代码后,我最终发现我忘记在 __wine_unix_call_dispatcher 函数中保存一个寄存器。这破坏了 PE 到 Unix 的转换,并导致了未定义行为 (UB)。修复后,成绩单上传功能正常了!

More games and other programs

更多游戏及其他程序

I have also tried to run some other applications under Wine:

我还尝试在 Wine 下运行了一些其他应用程序:

ProgramStatusNotes
程序状态备注
FTLWorksFully playable.
FTL可运行完全可玩。
SteamPartialInstalls and updates; crashes on Chromium startup due to a broken GetInterfaceAddresses().
Steam部分运行可安装和更新;因 GetInterfaceAddresses() 损坏而在 Chromium 启动时崩溃。
iexplore.exePartialSimple sites render; complex pages crash with the same root cause as Steam.
iexplore.exe部分运行可渲染简单网站;复杂页面因与 Steam 相同的原因崩溃。
FactorioPartialWindow opens but does not progress past loading.
Factorio部分运行窗口打开但无法通过加载界面。
Spooky’s Jumpscare MansionPartialStarts but runs too slowly to be playable.
Spooky’s Jumpscare Mansion部分运行可启动但运行速度太慢,无法游玩。
NoitaPartialStarts but runs too slowly to be playable.
Noita部分运行可启动但运行速度太慢,无法游玩。
Plants vs. ZombiesBrokenBlocked by Steam DRM before reaching the main menu.
植物大战僵尸损坏在进入主菜单前被 Steam DRM 拦截。
Half-LifeBrokenAssert failure in Wine’s C++ runtime. Likely a missing implementation in the port.
半条命损坏Wine 的 C++ 运行时断言失败。可能是移植中缺少实现。
Firefox / ChromiumBrokenInstaller fails; does not reach a runnable state.
Firefox / Chromium损坏安装程序失败;无法达到可运行状态。
SCP: Containment BreachBrokenDoes not start; cause not yet diagnosed.
SCP: 收容失效损坏无法启动;原因尚未诊断。
Unity games in generalBrokenThere seem to be issues with wine-mono on Astral, as Unity games get stuck on MonoManager ReloadAssembly
Unity 游戏 (通用)损坏Astral 上的 wine-mono 似乎有问题,Unity 游戏会卡在 MonoManager ReloadAssembly。

Final thoughts

结语

Porting Wine was a fun challenge and a way of proving that hobby OSes can be used to play a lot more games than originally thought, which is a step in the direction of hobby OSes becoming viable daily drivers. There are still some rough edges, performance issues and weird crashes, but the core of it works. This has also taught me a lot about the internal workings of Wine, especially while having to dig around to fix the PE-to-Unix transition. One of my big goals related to the Wine port now is to get Steam to work, which would also mean Chromium works. As for Astral, I plan on focusing more on optimizations, new drivers and bug fixes in the near future. There is a lot of room for improvements on the kernel side. Thanks for reading!

移植 Wine 是一项有趣的挑战,也证明了自制操作系统可以运行比最初想象中多得多的游戏,这是让自制操作系统成为可行的日常驱动系统迈出的一步。虽然仍有一些粗糙之处、性能问题和奇怪的崩溃,但核心功能已经可以运行。这也让我学到了很多关于 Wine 内部运作的知识,特别是在深入研究并修复 PE 到 Unix 转换的过程中。目前,我与 Wine 移植相关的一个大目标是让 Steam 运行起来,这也意味着 Chromium 将能正常工作。至于 Astral,我计划在不久的将来更多地专注于优化、新驱动程序和 Bug 修复。内核方面还有很大的改进空间。感谢阅读!