为 Windows XP 构建 Principia

Hacker News Top 新闻

摘要

一篇详细的技术博客文章,讲述了通过创建自定义交叉编译工具链,为 Windows XP 构建开源游戏 Principia 的过程。

暂无内容
查看原文
查看缓存全文

缓存时间: 2026/06/29 14:04

# 为 Windows XP 编译 Principia 来源:https://voxelmanip.se/2026/06/28/building-principia-for-windows-xp/ 开机画面中的 Windows XP 徽标与 Principia 冒险关卡的截图融合在一起。底部可见 Windows XP Luna 任务栏,显示 Principia 正在运行。 回想当年,Principia (https://principia-web.se/) 最初于 2014 年在 Windows 上发布时,该游戏可以一直运行到 Windows XP 这样的旧版本。考虑到 Principia 1.4 发布时 Windows XP 仍处于主流支持阶段,这在当时自然是合理的,并且鉴于当时的工具链和依赖项仍与之兼容,实际上没有理由*不*支持它。 如今,Principia 已成为一个开源项目。虽然 Windows 版本*应该*仍能支持至少 Windows 7,但我们使用的现代工具链、依赖项以及 UCRT 等系统库意味着,随着生态系统向前发展,很难真正保证未来长期的兼容性。不过,我一直想在某一天改变这一现状,让 Principia 能重新在 Windows XP 上运行,并生成一个完全开源的、可在该系统上运行的游戏构建版本。 ## Principia 的技术细节 我想首先描述一下与这项工作相关的 Principia 技术细节会很有用。Principia 最初是为 2012 年左右的功能手机设计的游戏,因此只要硬件至少支持 OpenGL 2.0,就应该能在非常旧的硬件上毫无问题地运行。Principia 的依赖项很少,并使用 SDL 实现跨平台支持。SDL(包括 SDL2 和较新的 SDL3)仍然支持 Windows XP,并且如前所述,Principia 以前也能在 Windows XP 上运行。因此,除非我在开源版本的工作中所做的更改破坏了 XP 兼容性,否则应该可以通过较少的代码更改使其再次在 XP 上运行。 主要问题只是工具链和一些其他依赖项已经迁移到了更新的 Windows 版本。 Principia 的 Windows 版本仅官方支持使用 mingw-w64 Windows 工具链进行编译。虽然历史上这是因为 MSVC 缺乏对 Principia 底层引擎 TMS 所使用的 C99 功能的支持,但目前的情况我并不确定。然而,mingw-w64 也包含各种简单 C 函数的 polyfill,这些函数在 Windows 上并非原生存在,除非有人表现出极大兴趣要用 MSVC 编译 Principia,否则这种情况短期内不太可能改变。 无论如何,FOSS 工具链通常最容易获得一个能够针对旧版本 Windows 的现代版本。然而,我们用于官方 Windows 构建的 MSYS2 中基于 LLVM 的 mingw-w64 工具链不适合针对 Windows XP,原因在于它链接了 UCRT、LLVM 的 libc++ 是 7+ 版本,并且任何其他支持库都假设 Vista 及更高版本中的符号始终存在。因此,我们需要跳出这个框架,寻找其他东西……或者从源代码构建一些东西。 当追溯到比 Vista/7 更早的版本时,你能找到的现有 mingw-w64 工具链的兼容性开始下降。任何基于 LLVM 的工具链对于 C++ 项目来说最低要求大约为 Windows 7,因为 libc++ 不支持更早的版本,而其他基于 GCC 的工具链在预构建的支持库中假设 Vista+ API 始终存在。虽然有些工具链的主要目标是支持像 Windows 95 这样的旧版本,但我最终决定自己动手。这可能是最简单的方法,因为我特别想要一个可以在 Linux 主机上运行的同时针对 Windows 的交叉编译工具链。 好吧,我们将编译 GCC、binutils、mingw-w64……还有什么?哦,天哪…… 幸运的是,我手里有一张王牌:Martin Storsjö (https://voxelmanip.se/media/building-principia-for-windows-xp/gcc-mingw.docker.txt)(你可能知道他是 llvm-mingw (https://voxelmanip.se/2024/12/10/cross-compiling-for-windows-using-llvm-mingw/) 的那个人)编写的 Dockerfile,他曾在 #mingw-w64 IRC 频道中分享过这个文件。出于好奇和热情,我已经拿这个 Dockerfile,将其重写为一个简单的 shell 脚本,并分解为每个组件的不同步骤。 我对手头的版本做了一些调整,将三元组改为 `i686-w64-mingw32` 用于 32 位 Windows,将系统运行时切换为 MSVCRT 作为目标,并将默认的 `WIN32_WINNT` 设置为 0x0501(Windows NT 5.1,对应 Windows XP)。然后运行它,看看是否还能编译…… 在构建 `libgmp`(GNU MP 大数库)的第一步就失败了,出现了一个关于没有功能正常的编译器的奇怪错误。困惑之余,我查看了 autotools 生成的 `config.log`,发现它报告了一个尝试编译一些非常奇怪的代码的错误: `` for(i=0;i<1;i++){if(e(got,got,9,d[i].n)==0)h();g(i,d[i].src,d[i].n,got,d[i].want,9);if(d[i].n)h();} `` 我是用最新的 GCC 16 编译的,似乎当前最新的 GMP 稳定版(撰写本文时为 6.3.0)在默认设置下无法与 GCC 15+ 兼容。构建系统中有一个编译时检查,包含了带有空参数函数的代码。在旧版 C 中,这意味着可以传递任意数量的参数(要防止这种情况,你会使用 `foo(void)`),而在 C23 中,这一规则发生了变化:空参数列表意味着不能传递任何参数。GCC 15 默认使用 C23,导致这段代码无法编译,构建系统随后将其视为编译器故障。 我在 Arch 的 GMP 构建脚本中找到了第一个问题的补丁,但最终我只是将 GMP 配置为使用 `-std=gnu99` 编译,让它闭嘴并继续构建过程,而无需重新生成 autotools 文件。 之后,基本上一帆风顺!我先构建了 MPFR,然后是 MPC,接着是 binutils,再然后是 GCC。然后使用新构建的 GCC 构建 mingw-w64 的 CRT 和库,再使用该工具链构建 GCC 的库。 在让它构建的同时,我想检查一下 GCC 最近是否有任何会破坏 Windows XP 兼容性的更改,特别是 C++ 标准库。这时我发现,GCC 16 附带的 libstdc++ 新增了一个对 Vista+ Win32 API 函数的硬依赖。具体来说是 `GetDynamicTimeZoneInformation` (https://learn.microsoft.com/en-us/windows/win32/api/timezoneapi/nf-timezoneapi-getdynamictimezoneinformation),用于 `std::chrono` 中的时区处理。 虽然我基本上是靠自己构建这样的工具链,但我也不介意借用别人工具链中的东西。w64devkit (https://github.com/skeeto/w64devkit) 的 32 位版本使用 GCC 16,仍然支持 Windows XP,我正是在那里发现了这个问题。他们还带有一个补丁,将上述 API 函数的依赖转换为动态依赖,尝试获取符号,如果不存在则回退为空。`std::chrono` 是 C++20 的特性,而 Principia 几乎不使用超过 C++11 标准的功能,所以直接将其存根掉也可以。但现成的干净补丁总是最好的! 应用了这个补丁并花了一些时间编译 GCC 后,我现在拥有一个几乎可以肯定能生成在 Windows XP 上可运行的二进制文件的工具链。每个部分都以 NT 5.1 为目标构建,只要程序不使用较新的函数,就应该没问题!作为冒烟测试,我在 Wine 中编译并运行了一个简单的 Hello World 程序,编译器确实可以创建可运行的 Windows 可执行文件。 ## 构建依赖项 在构建 Principia 之前,我们需要使用新构建的工具链构建其依赖项。Principia 有以下几个需要外部提供的依赖项: - curl - Freetype - libjpeg-turbo - libpng - SDL - zlib 这些是世界上最便携和通用的软件库!然而,其中 curl 最终带来了一些麻烦。他们在最近的 curl 8.19.0 中放弃了对 Windows XP 的支持,对于一个网络库来说这实际上可以理解,但不知何故,即使是 curl 8.18.0 也不起作用,因为它试图在 `msvcrt.dll` 中定位一个 `freopen_s` 函数,而该函数在 XP 中不存在。我最终使用了 curl 8.17.0,考虑到我们将在哪里运行它,这个版本仍然非常新。 Luanti 核心开发者 sfan5 有一个包含各种常见 Windows 库交叉编译脚本的仓库 (link (https://github.com/sfan5/mingw-pkgs)),该仓库用于构建 Luanti 所需的依赖项,我发现它对于其他事情也很有用。Principia 所需的所有依赖项都涵盖了,就这样,所有依赖项都构建好了! ……然而,还有一个相当大的问题:GTK3,Principia 在桌面上使用它来实现对话框。这是一个*巨大*的依赖项,其依赖树我甚至不想深入探究。我可以从提交历史中复活旧的 GTK2 对话框,或者可能有一个仍然支持 Windows XP 的旧版 GTK3,但我基本上不想至少在一开始处理这个问题。相反,我决定只构建带有实验性新对话框的游戏,这些对话框使用 Dear Imgui,这是一个轻量得多的依赖项(此外更适合我们的用例,并且集成到游戏窗口中),但游戏中的许多对话框仍未实现。 希望有一天这会有所改善,但目前这些构建在可用性方面会有些局限。 ## 构建 Principia 完成到目前为止的所有先决条件后,剩下的就是实际构建 Principia。这将需要一个漫长的 CMake 命令来指向交叉编译器工具链,以及所有依赖项……经过一番与 CMake 选项名称和路径的斗争后,配置成功了。 然后开始构建。几乎快要完成时……编译器错误。在 TMS 的 `pipeline.c` 中出现了不兼容的指针类型错误,与这些 typedef 有关: `` typedef void (*TMS_UNIFORM_FN)(GLint, GLsizei, const GLfloat*); typedef void (*TMS_UNIFORM_MAT_FN)(GLint, GLsizei, GLboolean, const GLfloat*); `` `pipeline.c` 可能是代码库中最令人头疼的源文件之一。多年来我一直在改进它,但它仍然是你宁愿不去碰的文件之一。然而,事实证明问题相当简单——你听说过 `__stdcall` 吗?它定义了 Win32 API 函数的调用约定……或类似的东西。一个定义需要 `__stdcall`,而另一个则不需要,导致了编译器错误。 我不确定为什么会这样,但我在整个代码库中看到了更多类似的情况,因此我假设这只是游戏使用不完整的 GL 头文件时的遗留问题。这些 typedef 基本上是 GLAD 中已定义的函数原型的重复(除了调用约定的东西),因此我们可以将较长的定义替换为这些原型的别名: `` typedef PFNGLUNIFORM1FVPROC TMS_UNIFORM_FN; typedef PFNGLUNIFORMMATRIX4FVPROC TMS_UNIFORM_MAT_FN; `` 为什么这在 Windows 上以前从未出现过?不确定。无论如何,现在它编译通过了!哇。 ## 测试二进制文件 首先,最好立即进行冒烟测试,以确保二进制文件至少能够运行,然后再担心它是否能在 XP 上运行。我们所知道的只是我们可以用工具链生成一个 Hello World 程序。现在我们构建了一堆库以及整个 Principia,这是一个*稍微*复杂一点的程序。 有什么比……直接用 Wine 运行它更好的方法呢? 是的,运行正常。让我们转到我的 Windows XP 虚拟机,我手头正好有这样一个东西以备不时之需。 处理完上述关于 curl 8.18.0 需要 `freopen_s` 的问题后,我能够启动游戏,直到出现当你尝试在没有功能性 OpenGL 驱动程序的情况下运行 Principia 时出现的错误消息。这是一个好兆头,因为这意味着所有二进制文件(包括 Principia EXE 和库 DLL)都没有对 XP 中不存在的函数的硬依赖。 [![[错误对话框] 您的显卡驱动程序不支持 OpenGL >1.1,因此 Principia 无法启动。很可能是因为您没有安装任何显卡驱动程序,并且正在使用 Windows 的软件渲染驱动程序。请为您的显卡安装必要的驱动程序。如果您正在使用虚拟机进行测试,则可以使用 Mesa 的软件渲染器 [文本截断...]](https://voxelmanip.se/media/building-principia-for-windows-xp/principia_xp_error.webp)](https://voxelmanip.se/media/building-principia-for-windows-xp/principia_xp_error.webp)在较新版本的 Windows 上,我使用 Mesa 软件渲染器 (https://fdossena.com/?p=mesa/index.frag) 来获得缓慢但功能正常的 OpenGL 支持,以便在虚拟机中测试东西。但我未能找到能在 XP 上运行的版本,所以目前看来最好的办法是……在真实硬件上安装 Windows XP? ## 让我们安装 Windows XP 吧! 事情从这里开始就有点偏离轨道了。本打算让一位朋友在她的旧笔记本电脑上安装 Windows XP 来测试构建版本,但等我准备好的时候,她已经因为 XP 上 WiFi 驱动无法工作而跑去安装 Windows Vista 了。所以我最终不得不自己动手,但我正好有一台非常适合安装 XP 的机器。 我之前的台式机(直到 2022 年组装新电脑之前一直在用)很旧。非常旧。它有一块非 UEFI 主板和一块 AMD Phenom II X4 955 处理器。它完全处于制造商为 Windows XP 提供驱动支持的年份范围内,所以应该很完美。而且自从我组装新电脑后,它基本上就一直放在那里积灰了! 由于相信使用原始 Windows XP ISO 从 USB 安装到 SATA SSD 会很复杂,我搜索了包含额外更新和驱动程序的非官方 ISO。然而,结果并不理想。 Windows XP 蓝屏死机 (https://voxelmanip.se/media/building-principia-for-windows-xp/xp_bsod.webp)绝望之际,我的朋友向我推荐了一个我之前从未听说过的东西。Universal NT Installer (https://github.com/ages2001/Universal-NT-Installer) 是一个基于 Linux 的 ISO,可以安装 Vista 之前的所有 Windows NT 版本,无论是否打补丁。由于基于 Linux,从 USB 启动它很容易,然后我可以安装一个已经包含 AHCI 和其他功能支持的 Windows XP 版本。完美! 操作系统版本:Windows XP Professional SP2 x64(已打补丁) (https://voxelmanip.se/media/building-principia-for-windows-xp/installing_xp.webp)在真实硬件上再次看到 Windows XP 启动画面是一种相当特别的体验。 Windows XP 启动画面显示徽标下方有 "x64 Edition"。 (https://voxelmanip.se/media/building-principia-for-windows-xp/xp_splash_screen.webp)我完全用键盘完成了设置过程,因为当时还没有插上鼠标。然后我进入了一个没有网络连接的低分辨率桌面。我明明插了以太网线,不是吗……? Windows XP 驱动程序安装向导询问是否应连接到 Internet 以搜索以太网控制器的驱动程序。 (https://voxelmanip.se/media/building-principia-for-windows-xp/ethernet_controller.webp)哦,没有以太网控制器的驱动程序……是的,祝你好运,Windows,连接到 Internet 搜索以太网驱动程序吧。 接下来的时间里,我……(原文未完整,但翻译到此为止)

相似文章

像1997年那样编译Quake!

Fabien Sanglard

一份详细的指南,介绍如何重现使用Windows NT 4和Visual C++ 6等老式工具编译Quake的win32二进制文件的过程(就像1997年所做的那样)。

面向 Itanium 的 Windows XP 2002:难以抑制的愤怒

Hacker News Top

一篇复古计算博客文章报道称,一个 QEMU 分支现在能够模拟 Intel Itanium(Merced),足以运行适用于 IA-64 的 Windows XP 2002,并记录了在 macOS 上构建交叉编译器以帮助其运行的过程。

在64位Alpha AXP Windows NT上的Pinball

Lobsters Hottest

探讨了Windows内置Pinball游戏的历史、导致其无法在64位Windows(特别是Alpha AXP版本)上运行的碰撞检测Bug,以及近期模拟技术突破使得稀有的64位Alpha NT构建能够运行该游戏。

打造一台1997年的Quake PC!

Fabien Sanglard

作者记录了打造一台1997-1998年古董PC的过程,用于运行所有版本的Quake,涵盖了硬件选择,如Pentium MMX、3dfx Voodoo2和Socket 7主板。