SerenityOS 移植 Cave Story:深入解析 `Ports/cavestory` 的三份补丁
2026/9/12 12:25:38 网站建设 项目流程

SerenityOS 移植 Cave Story:深入解析Ports/cavestory的三份补丁

【免费下载链接】serenityThe Serenity Operating System 🐞项目地址: https://gitcode.com/GitHub_Trending/se/serenity

导读

本文围绕 Ports/cavestory/patches/ReadMe.md 展开,逐份剖析 SerenityOS 为把开源游戏引擎 nxengine-evo(Cave Story 的重制引擎)移植到系统上而维护的三份源码补丁:修正fstat文件大小探测、修复时区偏移计算、以及关闭 SDL 硬件加速渲染。读完本文,你将理解 SerenityOS 的 Ports 补丁机制如何在「软件移植」这一工程实践中运作,并能直接读懂这三份补丁的每一行 diff 背后的系统级原因。

背景:Cave Story 在 SerenityOS 上如何构建

Cave Story(洞窟物语)是经典的独立平台游戏,而 SerenityOS 移植的是其开源引擎nxengine-evo。整个移植由 Ports/cavestory/package.sh 描述:

  • 端口版本:2.6.5-1,对应上游源码包nxengine-evo(工作目录nxengine-evo-b427ed7bcd403a4dbb07703fe0eb015c3350bbfc);
  • 依赖端口:libjpeglibpngSDL2SDL2_imageSDL2_mixerSDL2_ttf
  • 构建方式:cmake -B build+make -C build+make -C build install
  • 安装后以Cave Story名称出现在系统游戏分类(launcher_category='&Games'),启动命令为/usr/local/bin/nxengine-evo,图标取自platform/switch/icon.jpg

在 Ports/AvailablePorts.md 的可用端口列表中,cavestory条目同样登记为 Cave Story,版本2.6.5-1,上游地址为https://github.com/nxengine/nxengine-evo

按照 Ports/README.md 说明的移植流程,进入Ports/cavestory/目录后运行./package.sh即可依次执行installdepends → fetch → patch → configure → build → install。其中patch步骤会按序应用patches/*.patch目录下所有补丁,成功后会在工作目录生成.foo_applied标记文件,确保每个补丁只应用一次。

补丁机制速览:三个补丁改了什么

ReadMe 里记录了三份补丁,全部由 gloof11 于 2023-06-29 提交,分别针对两个文件:

补丁修改的文件目的
0001-Added-serenity-as-a-proper-define-so-that-fstat-is-u.patchdeps/spdlog/details/os.h让 spdlog 在 SerenityOS 上使用fstat而非fstat64探测文件大小
0002-Added-serenity-as-a-proper-define-for-time-generatio.patchdeps/spdlog/details/os.h让 spdlog 在 SerenityOS 上走tm_gmtoff缺失的分支计算 UTC 时区偏移
0003-Removed-hardware-acceleration-from-SDL_CreateRendere.patchsrc/graphics/Renderer.cpp将渲染器创建从硬件加速改为软件渲染

前两份补丁都落在引擎自带的第三方日志库spdlog头文件里,第三份则落在游戏引擎自身的渲染初始化代码中。下面逐一展开。

补丁 0001:让 spdlog 用fstat而不是fstat64

补丁内容

该补丁对deps/spdlog/details/os.hfilesize(FILE*)函数的条件编译宏做了单行修改:

-#if !defined(__FreeBSD__) && !defined(__APPLE__) && (defined(__x86_64__) || defined(__ppc64__)) && !defined(__CYGWIN__) && !defined(__HAIKU__) +#if !defined(__FreeBSD__) && !defined(__APPLE__) && (defined(__x86_64__) || defined(__ppc64__)) && !defined(__CYGWIN__) && !defined(__HAIKU__) && !defined(__serenity__) struct stat64 st; if (fstat64(fd, &st) == 0) {

为什么需要它

spdlog 的这段代码想表达的逻辑是:在 64 位 Unix 平台上,优先调用fstat64以获得大文件支持;只有 FreeBSD、macOS、Cygwin、Haiku 等特殊平台才直接回退到fstat(因为那些平台上fstat64已被废弃)。

问题在于:SerenityOS 的 LibC 并不提供fstat64这个符号。它属于典型的 POSIX 64 位实现,off_t本身就是 64 位的,fstat直接就能返回正确的文件大小。因此当编译器在 x86_64 架构下展开这段代码时,会试图调用不存在的fstat64,导致链接失败。

补丁的思路非常直接:把 SerenityOS 加入「不使用fstat64」的平台清单。代码里 SerenityOS 的编译守卫宏正是__serenity__——这与 Ports/README.md 强调的「Serenity 上有软件被打补丁后得以运行」的移植模式完全一致:与其改上游逻辑,不如让上游的条件编译正确识别这个新平台

补丁目标文件位于deps/spdlog/下,说明 nxengine-evo 把 spdlog 以源码形式内嵌在依赖目录中,SerenityOS 移植时只能通过补丁方式就地修正,这也是 Ports 目录中大量补丁的典型形态。

补丁 0002:为 SerenityOS 走tm_gmtoff缺失分支

补丁内容

同样修改deps/spdlog/details/os.h,这次针对utc_minutes_offset()函数:

-#if defined(sun) || defined(__sun) || defined(_AIX) || defined(__VITA__) || defined(__SWITCH__) +#if defined(sun) || defined(__sun) || defined(_AIX) || defined(__VITA__) || defined(__SWITCH__) || defined(__serenity__) // 'tm_gmtoff' field is BSD extension and it's missing on SunOS/Solaris struct helper {

为什么需要它

spdlog 在计算本地时间与 UTC 的分钟偏移量时,倾向于直接读取struct tm中的 BSD 扩展字段tm_gmtoff。但该字段并非所有平台都提供:SunOS/Solaris、AIX、PlayStation Vita、任天堂 Switch 的 libc 都缺少它。对于这些平台,spdlog 准备了后备实现(通过mktimegmtime的差值推导偏移,即代码注释中提到的struct helper方案)。

SerenityOS 的 LibC 同样没有tm_gmtoff字段,因此必须把__serenity__追加进这个平台名单,让 spdlog 启用后备的时区偏移算法,否则会直接编译报错——引用了不存在的结构体成员。

这份补丁与前一份互为姊妹篇:一个是「某 POSIX 符号不存在」,一个是「某结构体字段不存在」。两者共同揭示了 SerenityOS 移植第三方软件时最常遇到的兼容性摩擦:上游代码总是默认某些 POSIX/BSD 扩展一定存在,而 SerenityOS 的 LibC 只实现它自己定义的 API 面。这类补丁的价值正在于用最小改动(单行条件编译)换取整条构建链的通过。

补丁 0003:渲染器从硬件加速改为软件渲染

补丁内容

修改引擎自身源码src/graphics/Renderer.cppRenderer::initVideo()

- _renderer = SDL_CreateRenderer(_window, -1, SDL_RENDERER_ACCELERATED); + _renderer = SDL_CreateRenderer(_window, -1, SDL_RENDERER_SOFTWARE);

SDL_CreateRenderer的第三个参数是渲染器标志:SDL_RENDERER_ACCELERATED请求 GPU 硬件加速,SDL_RENDERER_SOFTWARE则强制使用 CPU 软件渲染。补丁唯一的改动就是把标志从前者换成后者。

为什么需要它

这要从 SerenityOS 的图形栈说起。SerenityOS 自带的 SDL2 移植端口(见 Ports/SDL2/package.sh,当前版本2.32.10)构建时明确关闭了部分依赖项,并链接libcorebasiclibaudiolibiconv等系统库;在窗口系统层面,SerenityOS 的图形服务主要提供 2D 绘制能力,对 OpenGL/GPU 加速渲染的支持并不完整。因此SDL_RENDERER_ACCELERATED在该环境下要么不可用、要么无法获得预期加速效果。

将渲染器切换为SDL_RENDERER_SOFTWARE后,SDL 会走 CPU 位图渲染路径,从而保证 Cave Story 在 SerenityOS 上能以软件方式稳定渲染画面。这与 ReadMe 的标题「Removed hardware acceleration from SDL_CreateRenderer」完全对应,也解释了为什么这份补丁修改的是游戏引擎代码而非 SDL 库本身——问题出在调用方对渲染能力的错误假设

值得注意的是,这段代码位于Renderer::initVideo()的降级分支中:补丁上下文显示,只有当首选创建方式失败、进入if (!_renderer)回退逻辑时才会执行这次SDL_CreateRenderer调用。也就是说,这份补丁实际上把「回退路径」变成了「唯一路径」,避免在 SerenityOS 上依赖本就不存在的硬件加速。

从补丁到工程:SerenityOS Ports 的移植方法论

把三份补丁放回 Ports/README.md 描述的移植框架中,可以提炼出 SerenityOS 处理第三方软件的四条经验:

  1. 优先修条件编译,不重写上游逻辑。补丁 0001、0002 都是往#if平台清单里追加__serenity__,让上游既有的多平台分支正确识别 SerenityOS,而不是为 Serenity 单开一套代码。
  2. 最小化 diff,便于随上游升级。每份补丁仅 1 行变更,后续引擎版本升级时冲突概率极低。Ports 目录的补丁命名(0001-0002-0003-)即按应用顺序编号,patch步骤会依序应用。
  3. 针对平台能力做务实取舍。补丁 0003 放弃了硬件加速,选择软件渲染——移植的本质是让软件在目标平台上「跑得起来且可用」,而非机械照搬所有平台特性。
  4. 补丁与package.sh协作package.sh通过depends先装好 SDL2 等依赖,patch步骤再注入这些适配补丁,最后以Cave Story条目出现在系统启动器中,形成完整的「下载 → 打补丁 → 交叉编译 → 安装」闭环。

如果你也想为 SerenityOS 移植新的游戏或工具,不妨以Ports/cavestory/为样板:先跑通构建,再针对 LibC 缺失的符号、结构体字段和渲染能力逐一以补丁形式修掉,最后把补丁和package.sh一起提交回上游仓库,帮助更多软件在 SerenityOS 上运行。

【免费下载链接】serenityThe Serenity Operating System 🐞项目地址: https://gitcode.com/GitHub_Trending/se/serenity

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询