Quake2(quake2sdl)移植 SerenityOS:平台适配补丁与构建运行全解析
【免费下载链接】serenityThe Serenity Operating System 🐞项目地址: https://gitcode.com/GitHub_Trending/se/serenity
导读:本文围绕 SerenityOS 仓库中 quake2 移植端口 的补丁文档展开,深入剖析
0001-Add-SerenityOS-platform-support.patch如何将经典 FPS《雷神之锤 II》(基于 quake2sdl 0.17 源码)从 Linux 平台移植到 SerenityOS。读者将掌握该移植涉及的构建系统改造、平台 API 适配(内存管理、网络、音频、视频渲染)以及数据文件与 OpenGL 模式的配置运行方法,并理解 SerenityOS 移植第三方软件的一般思路。
一、移植背景:为什么需要这份补丁
SerenityOS 是一个从零开始构建的类 Unix 操作系统,其 Ports 体系 允许将上游开源软件以"端口(port)"的形式交叉编译进系统。Quake II 的移植选用了quake2sdl(版本 0.17,提交d26d00845e95dc7d781459d0c1a7fd48ea4b6be3),这是一份基于 SDL2 的 Quake II 引擎分支。
由于上游 quake2sdl 主要面向 Linux/FreeBSD 平台,代码中充斥着__linux__/__FreeBSD__条件编译、stricmp等非 POSIX 调用、以及对 OpenGL/JPEG 等外部库的强依赖,因此无法直接编译运行。仓库中的补丁文档 Ports/quake2/patches/ReadMe.md 只做了简短说明,真正的技术细节全部沉淀在补丁文件 0001-Add-SerenityOS-platform-support.patch 与构建脚本 package.sh 中。本补丁由 Jelle Raaijmakers 提交,Jesse Buhagiar 共同参与,改动横跨 10 个文件,共 99 行新增、125 行删除。
上游信息:quake2sdl 源码托管于外部 GitHub 仓库,SerenityOS 通过
commit_hash与archive_hash(SHA-256)固定版本,具体见 package.sh。
二、构建系统改造:从通用 CMake 到 Serenity Port
2.1 顶层 CMakeLists.txt:精简外部依赖
补丁对上游根目录CMakeLists.txt做了大幅精简,核心差异如下:
- 移除
CMAKE_MODULE_PATH设置:不再依赖上游Modules/目录中的查找模块; - 保留
find_package(SDL2 REQUIRED):SDL2 是唯一必需的第三方库; - 删除
find_package(OpenGL REQUIRED):SerenityOS 自研的 LibGL 不作为构建期硬依赖,而是在运行期通过动态加载方式接入; - 删除可选编译选项:
WITH_RETEX(纹理替换包支持)与WITH_QMAX(改进图形渲染)两个 CMake 选项及其对应的find_package(JPEG REQUIRED)逻辑被整体移除。
这意味着 SerenityOS 上的 Quake II 始终使用标准的软件渲染路径,不再编译可选的高画质 OpenGL 渲染器变体。
2.2 src/CMakeLists.txt:源码清单与链接目标调整
子目录src/CMakeLists.txt的改动更加深入:
| 改动点 | 上游(Linux) | SerenityOS 移植后 |
|---|---|---|
| 编译宏 | -DHAVE_IPV6、-DLINUX_VERSION="1"、FreeBSD 下-DHAVE_SIN6_LEN、Linux 下-D_GNU_SOURCE | 全部移除 |
| 网络源码 | linux/net_udp6.c(IPv6) | linux/net_udp.c(IPv4) |
| 特效源码 | WITH_QMAX时用cl_fxmax.c,否则cl_fx.c | 固定使用cl_fx.c |
| 链接库 | SDL2 m(Linux 追加dl) | dl SDL2 pthread gfx gui c m |
| GL 渲染器 | 包含linux/joystick.c | 移除该文件 |
从源码结构可以推断,SerenityOS 的 LibGL 以libgl.so.serenity形式存在,因此补丁通过gfx gui两个链接目标接入图形与 GUI 子系统,而pthread则补足线程支持。
2.3 package.sh:端口声明与构建入口
package.sh 是 SerenityOS 端口体系的标准入口脚本,其声明信息完整还原了该移植的构建与部署方式:
#!/usr/bin/env -S bash ../.port_include.sh port='quake2' version='0.1' useconfigure='true' commit_hash='d26d00845e95dc7d781459d0c1a7fd48ea4b6be3' archive_hash='f940d71e0a4e15c040776979c6c99cb3520208744b3c22921f484d70ba82d675' files=( "https://github.com/shamazmazum/quake2sdl/archive/${commit_hash}.tar.gz#${archive_hash}" ) workdir="quake2sdl-${commit_hash}" makeopts=() configopts=( "-DCMAKE_TOOLCHAIN_FILE=${SERENITY_BUILD_DIR}/CMakeToolchain.txt" ) depends=('SDL2') launcher_name='Quake II' launcher_category='&Games' launcher_command='/usr/local/bin/quake2' icon_file='docs/quake2.gif' configure() { run cmake "${configopts[@]}" } install() { run make install }要点解读:
- 版本固定:
useconfigure='true'表示使用 CMake 配置流程,archive_hash用于校验下载包完整性; - 工具链:通过
-DCMAKE_TOOLCHAIN_FILE=${SERENITY_BUILD_DIR}/CMakeToolchain.txt指定 SerenityOS 交叉编译工具链,这是所有基于 CMake 的端口(如 SDL2)的统一做法; - 依赖:
depends=('SDL2')声明前置端口,构建时会自动先构建 SDL2; - 桌面集成:
launcher_*系列字段将该游戏注册进 SerenityOS 的启动菜单(分类&Games,命令/usr/local/bin/quake2),图标取自上游docs/quake2.gif; - 安装:
install()直接执行make install,产物安装到/usr/local前缀下。
构建时在Ports/quake2目录下直接运行./package.sh即可,这正是补丁文档中"Build like a regular Serenity Port"的含义。
三、平台 API 适配:逐文件源码级剖析
这是补丁的核心价值所在——将 Linux 专用代码逐一替换为 SerenityOS 可用的等价实现。
3.1 内存管理:Hunk 分配器的页面对齐处理
src/linux/q_shlinux.c中的Hunk_End()负责收缩游戏内存块(hunk)。上游 Linux 实现使用mremap()系统调用,而 FreeBSD/SerenityOS 分支则改用munmap()收缩。补丁的关键改动:
// FIXME: 64-bit???? #define PAGE_SIZE 4096 #if defined(__FreeBSD__) || defined(__serenity__) size_t old_size = maxhunksize; size_t new_size = curhunksize + sizeof(int); void * unmap_base; size_t unmap_len; new_size = ((new_size + PAGE_SIZE - 1) & (~(PAGE_SIZE - 1))); old_size = ((old_size + PAGE_SIZE - 1) & (~(PAGE_SIZE - 1))); if (new_size > old_size) n = 0; /* error */ else if (new_size < old_size) { unmap_base = (caddr_t)(membase + new_size); unmap_len = old_size - new_size; n = munmap(unmap_base, unmap_len) + membase; } #endif要点:
- 通过
defined(__serenity__)宏识别 SerenityOS,与 FreeBSD 共用munmap收缩路径; PAGE_SIZE被硬编码为 4096(源码注释中留有 "FIXME: 64-bit????" 的疑问,说明这是临时性处理),新旧尺寸均向上对齐到页边界后再计算待释放区间;- 之所以避开
mremap,从源码结构可以推断 SerenityOS 内核未实现该 Linux 特有系统调用。
3.2 网络:去掉 stricmp 非标准调用
src/linux/net_udp.c的NET_Socket()中,判断绑定接口是否为 localhost 时,上游使用stricmp(非标准、大小写不敏感比较),SerenityOS 的 LibC 不提供该函数,因此改为标准strcmp:
- if (!net_interface || !net_interface[0] || !stricmp(net_interface, "localhost")) + if (!net_interface || !net_interface[0] || !strcmp(net_interface, "localhost")) address.sin_addr.s_addr = INADDR_ANY;同时,构建清单中由net_udp6.c换为net_udp.c,表明该移植仅启用 IPv4 网络支持。
3.3 音频与渲染:SDL2 头文件与软件渲染
- 头文件路径:
rw_sdl.c与snd_sdl.c中的#include <SDL.h>全部改为#include <SDL2/SDL.h>,适配 SerenityOS 端口安装的头文件布局; - 软件渲染强制启用:
rw_sdl.c的SWimp_InitGraphics()中,渲染器创建标志由SDL_RENDERER_ACCELERATED改为SDL_RENDERER_SOFTWARE:
- renderer = SDL_CreateRenderer (window, -1, SDL_RENDERER_ACCELERATED); + renderer = SDL_CreateRenderer (window, -1, SDL_RENDERER_SOFTWARE);这说明 SerenityOS 上 SDL2 的 2D 渲染走软件路径,与系统图形栈的兼容性优先于 GPU 加速。
- 新增空实现:补丁在
rw_sdl.c末尾追加了UpdateHardwareGamma()的空实现(Noop),用于满足引擎对硬件伽马校正函数的引用,但 SerenityOS 端不执行任何实际校正。
3.4 系统层:控制台输入与错误处理
src/linux/sys_linux.c的改动体现了两点平台差异:
- 移除 SysV IPC 头文件:
<sys/ipc.h>、<sys/shm.h>不再引入; - 注释掉 fcntl 非阻塞 stdin 调用:
Sys_Quit()、Sys_Error()与main()中的fcntl(0, F_SETFL, ... | FNDELAY)系列调用全部被注释。从源码结构可以推断,SerenityOS 对终端非阻塞模式的fcntl支持与 Linux 存在差异,移植时选择放弃这部分能力以保证稳定; - console 输入路径调整:
Sys_ConsoleInput()中原本用select()轮询 stdin 就绪状态的逻辑被注释掉,直接执行read(),避免 select 在 SerenityOS 终端环境下不可用; - 版本标识:启动横幅由
printf ("Quake 2 -- Version %s\n", LINUX_VERSION)改为printf ("Quake 2 -- Version %s Serenity\n", "0.1"),输出 "Quake 2 -- Version 0.1 Serenity",与 package.sh 中version='0.1'保持一致。
3.5 视频模式与渲染库加载
src/linux/vid_so.c是平台差异最集中的文件之一:
- 规避视频模式切换崩溃:
vid_modes[]表中 Mode 0 的分辨率由320x240改为640x480,源码注释明确说明这是 HACK——"Trying to access graphics options or setting video mode kills the program"(访问图形选项或设置视频模式会导致程序崩溃),因此用 640x480 作为安全默认值; - 硬编码渲染库路径:
VID_LoadRefresh()原本用RESOURCE_LIBDIR拼接动态库路径,但该宏在 SerenityOS 交叉编译环境下指向无效路径(注释:RESOURCE_LIBDIR is a completely invalid path!),补丁临时硬编码为:
// FIXME: RESOURCE_LIBDIR is a completely invalid path! // Un-hardcode me eventually please! //snprintf (fn, MAX_OSPATH, "%s/%s", RESOURCE_LIBDIR, name ); snprintf (fn, MAX_OSPATH, "%s/%s", "/usr/local/lib/quake2sdl", name ); Com_Printf("%s\n", fn);即渲染器动态库(如ref-softsdl、ref-sdlgl)将从/usr/local/lib/quake2sdl/目录加载,make install会将库安装到该位置。
3.6 平台头文件:解除编译期报错
src/linux/glw_linux.h顶部原本对非 Unix 平台直接#error,补丁将其注释掉:
#if !defined(__linux__) && !defined(__FreeBSD__) // #error You shouldnt be including this file on non-unix platforms #endif这使得 GL 相关的窗口包装代码能够在 SerenityOS 上继续编译,为运行期加载 LibGL 保留可能性。
四、运行配置:数据文件与 OpenGL 模式
补丁在修改上游 README 时,写入了 SerenityOS 专属的运行说明(这也是 ReadMe.md 所指向的权威操作指南):
4.1 获取游戏数据文件
Quake II 引擎本体不含游戏内容,需要自行获取Quake II 演示版(demo)的pak0.pak数据包。补丁文档说明:iD Software 的 FTP 已下线,但演示包可在多处找到。下载后将pak0.pak放到如下目录:
/home/anon/.quake2/baseq2/随后在命令行直接运行quake2即可进入游戏。
4.2 切换 OpenGL 渲染模式
SerenityOS 移植版默认使用软件渲染。软件渲染与 LibGL 之间尚不支持运行时动态切换,若要以 OpenGL 模式运行,需要手动编辑配置文件~/.quake2/baseq2/config.cfg,追加两条指令:
set vid_ref "sdlgl" set gl_driver "libgl.so.serenity"vid_ref "sdlgl"指示引擎加载 SDL 驱动的 GL 渲染器(对应构建出的ref-sdlgl动态库);gl_driver "libgl.so.serenity"指向 SerenityOS 自研 OpenGL 实现 LibGL 的共享库文件名。
这两条配置与补丁中保留的ref-sdlgl目标(src/CMakeLists.txt 的补丁片段)相互印证:GL 渲染路径在编译期被保留,但仅能通过配置文件显式启用。
4.3 端口可用性登记
在 Ports/AvailablePorts.md 中,该端口被登记为:
| 端口 | 说明 | 版本 | 上游 |
|---|---|---|---|
quake2 | QuakeII | 0.1 | quake2sdl |
同一列表中还包含quake(Quake 1,SerenityQuake 移植)与quake3(ioquake3),三者共同构成了 SerenityOS 上的雷神之锤系列游戏阵容。
五、移植方法论小结
纵观整份补丁,Quake II 移植到 SerenityOS 的技术路径可以归纳为四条可复用的经验:
- 构建系统适配先行:移除上游对 OpenGL/JPEG 等外部库的强制查找与可选特性宏,将链接目标替换为 SerenityOS 的
gfx gui等系统库,保持最小依赖面; - 平台宏分支复用:优先复用 FreeBSD 分支(
munmap收缩、非mremap路径),通过新增defined(__serenity__)条件并入现有分支,最大限度减少重复代码; - 非标准 API 逐一替换:
stricmp → strcmp、SDL.h → SDL2/SDL.h、fcntl(FNDELAY)与select()直接禁用,属于典型的 POSIX 兼容性收敛; - 运行期降级与 HACK 记录:软件渲染兜底、视频模式固定 640x480、渲染库路径硬编码等临时手段均在源码中以
FIXME/HACK注释明确标注,为后续完善留下线索。
对于希望深入理解该移植的读者,建议按以下顺序阅读仓库中的对应文件:补丁说明文档 → 完整补丁 → 构建脚本 → SDL2 端口,即可完整还原从上游代码到 SerenityOS 可玩游戏的整个移植链路。
【免费下载链接】serenityThe Serenity Operating System 🐞项目地址: https://gitcode.com/GitHub_Trending/se/serenity
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考