- 游戏开发
- 逆向工程
【免费下载链接】gbe_fork
Fork of https://gitlab.com/Mr_Goldberg/goldberg_emulator
本文围绕 gbe(Goldberg 模拟器 fork)仓库中resources/目录展开,系统讲解该项目在 Windows 构建过程中如何利用 Microsoft 资源编译器rc.exe为生成的steam_api(64).dll、steamclient(64).dll、steam.exe、GameOverlayRenderer(64).dll等二进制注入版本信息与模仿原始二进制中的附加资源,并揭示构建后 DOS Stub 改写的原理与实现。读完本文,你将掌握该仓库资源文件的组织方式、VERSIONINFO与WEVT_TEMPLATE等资源的编写规范、premake5.lua中winrsrc/dosstub选项的启用方法,以及file_dos_stub工具对 PE 文件头部偏移写入的底层细节。
一、resources/目录在构建流程中的定位
根据 resources/README.md 的说明,resources/目录存放的是“构建期间使用的附加资源”(additional resources used during build)。这些资源并非运行时逻辑代码,而是构建产物在二进制层面与原版 Steam 文件“外观”保持一致的关键部分。
该目录下只有一个子目录win/,其作用明确:把要添加到.dll/.exe二进制中的资源收集在一起,具体包含两类内容:
- 版本信息(version info):通过
VERSIONINFO资源描述文件声明,用于让生成的模拟器 DLL/EXE 在资源管理器中展示与原始 Steam 组件一致的版本属性; - 对原始二进制中“任何附加资源”的模仿(an immitation of any extra resources found in the original .dll/.exe):例如
steamclient.dll与GameOverlayRenderer.dll中存在的 Windows 事件跟踪模板WEVT_TEMPLATE资源。
一个值得注意的措辞是 README 使用了“immitation”(模仿)而非“copy”(复制),从源码结构看,这些资源是项目开发者根据对原始 Steam 二进制中资源结构的观察,用资源脚本重新编写出来的等价物,而非从原文件直接抽取的字节拷贝。
二、资源构建链路:rc.exe→*.res→cl.exe
README 明确描述了完整的资源编译流程,这也是 Windows 平台标准的资源编译链路:
- 编译资源脚本:构建过程中调用微软资源编译器
rc.exe编译resources/win/**/*.rc文件; - 产出中间文件:编译产物为
*.res文件,统一存放在build\tmp\win\rsrc目录下; - 参与链接:生成的
.res文件随后被当作普通.cpp/.c源文件一样,直接传给编译器cl.exe参与最终链接。
README 给出了示例命令:
cl.exe myfile.cpp myres.res -o myout.exe这条命令说明.res文件可以直接作为输入参数与源码一同编译链接,最终把资源段合并进输出的 PE 二进制中。在 gbe 仓库中,这一流程的实际触发点位于 premake5.lua 中定义的三个 Windows 专用构建选项(见下文“构建集成”小节),各项目的源文件列表中会按 x32 / x64 平台分别加入对应的resources.rc。
三、win/子目录逐项解析
README 将win/下的资源按目标二进制分为五组,下面结合仓库中的实际文件逐一说明。
3.1api/:模拟steam_api(64).dll的资源
目录 resources/win/api/ 下按 32/64 位分平台存放resources.rc,用于模拟steam_api.dll(32 位)与steam_api64.dll(64 位)中的资源。
以 resources/win/api/64/resources.rc 为例,其核心结构包含:
- 第一段中性语言块(
LANGUAGE 0, 0,语言 = Neutral,子语言 = Neutral):FILEVERSION 8,33,9,23、FileVersion "08.33.09.23":模仿 Steam 客户端 API 的版本号 8.33.9.23;InternalName "GSE Client API (win64)"、OriginalFilename "steam_api.dll":声明内部名称与原始文件名,注意 64 位与 32 位(resources/win/api/32/resources.rc 中为GSE Client API (win32))在此处区分;FILEOS 0x00000004L(VOS__WINDOWS32)、FILETYPE 0x2L(VFT_DLL):声明目标系统为 Windows 32 位家族、文件类型为 DLL;Source Control ID "8330923":由SOURCE_CONTROL_ID指令从../SOURCE_CONTROL_ID.txt文件(即 resources/win/api/SOURCE_CONTROL_ID.txt)读取;
- 第二段美式英语块(
LANGUAGE 0x09, 0x01):提供一份精简的VERSIONINFO,字段值与第一段一致但统一为1, 0, 0, 1; VarFileInfo块:Translation 0x0409, 0x04B0,声明语言为英语(0x409)、字符集为 Windows ANSI 代码页 1252(0x04B0)。
3.2client/:模拟steamclient(64).dll的资源
目录 resources/win/client/ 用于模拟steamclient.dll与steamclient64.dll的资源,相比api/多出一项特殊资源。以 resources/win/client/64/resources.rc 为例:
版本信息:
FILEVERSION 8,56,38,63、OriginalFilename "Steamclient.dll"、Source Control ID "8563863"(来自 resources/win/client/SOURCE_CONTROL_ID.txt);附加资源模仿:文件末尾出现
LANGUAGE 0x33, 0x04 1 WEVT_TEMPLATE "../WEVT_TEMPLATE1_1.bin"这是 Windows 事件跟踪(Event Tracing for Windows)的模板资源,内容直接引用同目录下的二进制文件 resources/win/client/WEVT_TEMPLATE1_1.bin。该资源的存在说明原版
steamclient.dll中带有事件模板,模拟器构建时通过.rc引用原样嵌入该.bin数据。语言代码0x33, 0x04(即 4147,Slovak / Slovak)与前面两段不同,同样是模仿原始二进制中的语言块划分。
3.3launcher/:模拟steam.exe的资源
目录 resources/win/launcher/ 用于模拟steam.exe(Steam 启动器)的资源。以 resources/win/launcher/64/resources.rc 为例,其与 DLL 资源的关键差异在于:
FILETYPE 0x00000001L(VFT_APP):文件类型声明为应用程序(EXE)而非 DLL;- 版本信息:
FILEVERSION 8,56,38,63、OriginalFilename "steam.exe"、ProductVersion "01.00.00.02"、Copyright (C) 2021 GSE。
3.4game_overlay_renderer/:模拟GameOverlayRenderer(64).dll的资源
目录 resources/win/game_overlay_renderer/ 用于模拟GameOverlayRenderer.dll(32 位)与GameOverlayRenderer64.dll(64 位)的资源。以 resources/win/game_overlay_renderer/64/resources.rc 为例:
- 版本信息:
FILEVERSION 8,74,80,95、OriginalFilename "GameOverlayRenderer.dll"、Source Control ID "8748095"(来自 resources/win/game_overlay_renderer/SOURCE_CONTROL_ID.txt); - 与
client/相同,末尾同样带有一段LANGUAGE 0x33, 0x04的1 WEVT_TEMPLATE "../WEVT_TEMPLATE1_1.bin",即 resources/win/game_overlay_renderer/WEVT_TEMPLATE1_1.bin,说明原版GameOverlayRenderer.dll也含有事件跟踪模板资源。
3.5file_dos_stub/:构建后的 DOS Stub 改写
目录 resources/win/file_dos_stub/ 与前四组不同,它不包含.rc资源脚本,而是包含一个独立的命令行工具及其预编译产物:
- resources/win/file_dos_stub/file_dos_stub.cpp:工具的源码;
file_dos_stub_x32.exe与file_dos_stub_x64.exe:两个预编译好的可执行文件。
README 将其定位为“对 DOS Stub 在构建后进行操作的模仿”。DOS Stub 是 PE 文件格式中位于 DOS 头与 PE 头之间的遗留小程序段;gbe 在此处通过改写 DOS Stub 中的特定字节,使模拟器产物在结构上更接近原版二进制(详见第六节源码剖析)。
下表汇总五组资源与目标二进制的对应关系:
| 目录 | 目标二进制(x32 / x64) | 资源内容要点 |
|---|---|---|
| resources/win/api/ | steam_api.dll/steam_api64.dll | VERSIONINFO(8.33.9.23)、Source Control ID |
| resources/win/client/ | steamclient.dll/steamclient64.dll | VERSIONINFO(8.56.38.63)、WEVT_TEMPLATE事件模板 |
| resources/win/launcher/ | steam.exe | VERSIONINFO(VFT_APP类型,8.56.38.63) |
| resources/win/game_overlay_renderer/ | GameOverlayRenderer.dll/GameOverlayRenderer64.dll | VERSIONINFO(8.74.80.95)、WEVT_TEMPLATE事件模板 |
| resources/win/file_dos_stub/ | 全部 Windows 构建产物 | 构建后 DOS Stub 改写工具 |
四、VERSIONINFO资源脚本的写法要点
gbe 的resources.rc是理解 Windows 版本资源脚本的极佳范例。综合 resources/win/api/64/resources.rc 等文件,其编写规范可归纳为:
- 语言块声明:每个语言块前用
LANGUAGE primary, sublanguage指定,例如LANGUAGE 0, 0(Neutral)与LANGUAGE 0x09, 0x01(English - US,十进制 1033); SOURCE_CONTROL_ID SCID "路径":自定义指令,用于把外部文本文件(SOURCE_CONTROL_ID.txt)的内容嵌入为资源字符串,避免在多个.rc中重复维护版本号;VERSIONINFO块:包含FILEVERSION/PRODUCTVERSION(以逗号分隔的 4 段数值)、FILEFLAGSMASK 0x17、FILEFLAGS 0x0、FILEOS 0x00000004L、FILETYPE 0x2L/0x1L、FILESUBTYPE 0x0L等固定字段;StringFileInfo块:子块"040904b0"为语言(0409)+ 字符集(04B0)的复合键,内部用VALUE "字段名", "字符串"声明CompanyName、FileDescription、FileVersion、InternalName、LegalCopyright、OriginalFilename、ProductName、ProductVersion等可读字段;VarFileInfo块:VALUE "Translation", 0x0409, 0x04B0声明翻译表。
在 32 位版本的 resources/win/api/32/resources.rc 中,还保留了关于语言 ID 位结构的注释:
* +-------------------------+-------------------------+ * | SubLanguage ID | Primary Language ID | * +-------------------------+-------------------------+ * 15 10 9 0并注明1033 = English - United States = 0x0409 = 0000 0100 0000 1001,方便后续维护者理解语言 ID 的二进制布局。
五、构建集成:premake5.lua中的资源选项
资源脚本并不会无条件参与所有构建,而是通过 premake5.lua 中定义的 Windows 专用选项按需启用(定义于第 159–179 行):
| 选项 | 触发开关 | 作用 |
|---|---|---|
dosstub | --dosstub | 构建后改写 Windows 产物的 DOS Stub(调用file_dos_stub工具) |
winsign | --winsign | 用假证书对 Windows 产物进行签名(调用third-party/build/win/cert/sign_helper.bat) |
winrsrc | --winrsrc | 将resources/win/**/resources.rc加入各项目并编译进产物 |
资源文件的接入方式(以api_regular项目为例,见 premake5.lua):
filter { "system:windows", "platforms:x32", "options:winrsrc", } files { "resources/win/api/32/resources.rc" } filter { "system:windows", "platforms:x64", "options:winrsrc", } files { "resources/win/api/64/resources.rc" }即:只有system:windows且显式传入--winrsrc时才编译资源脚本;Linux 构建(libsteam_api)则完全不会引入这些.rc文件。同样的模式还出现在client、launcher、game_overlay_renderer以及实验性 steamclient loader 等项目中([premake5.lua](https://link.gitcode.com/i/eec8626b18b65329b8778e944f10ed24#L997-L1002、L1097-L1104、L1221-L1228 等位置)。
DOS Stub 改写的后置命令定义在第 680–683 行:
filter { "system:windows", "options:dosstub", } postbuildcommands { '"' .. dos_stub_exe .. '" %[%{!cfg.buildtarget.abspath}]', }其中dos_stub_exe指向按平台展开的预编译工具路径:
local dos_stub_exe = path.translate( path.getabsolute('resources/win/file_dos_stub/file_dos_stub_%{cfg.platform}.exe', _MAIN_SCRIPT_DIR), '\\')由此可见,resources/win/file_dos_stub/下的两个预编译 EXE 正是为这条后置命令准备的,其源码对应的 Premake 项目tool_file_dos_stub_changer定义在 premake5.lua,产物名称为file_dos_stub_%{cfg.platform}。
六、源码剖析:file_dos_stub如何改写 DOS Stub
resources/win/file_dos_stub/file_dos_stub.cpp 是一个独立的命令行小工具,逻辑非常清晰,完整展示了在二进制层面改写 DOS Stub 的过程。
6.1 关键常量
constexpr static size_t DOS_STUB_OOFSET = offsetof(IMAGE_DOS_HEADER, e_lfanew) + sizeof(IMAGE_DOS_HEADER::e_lfanew); constexpr static const char DOS_STUB_SIGNATURE[] = "VLV"; constexpr static uint32_t DOS_STUB_ONE = 1; // what is this?- 写入位置:DOS Stub 内容从
IMAGE_DOS_HEADER中e_lfanew字段之后开始。e_lfanew是 DOS 头中指向 PE 头偏移的 4 字节字段,紧随其后的区域(自文件头起 0x40 处)正是经典 DOS Stub(如 “This program cannot be run in DOS mode” 消息)的存放位置,因此把改写位置固定在这个偏移是符合 PE 格式布局的; - 写入内容:依次写入 4 字节签名
'V' 'L' 'V' '\0'、一个固定值1(源码注释为 “what is this?”,即该字段含义未完全确认,只是模仿原版二进制的既有值)、以及 PE 镜像大小。
6.2 主流程
参数解析:工具接受一个或多个二进制文件路径(
argc < 2时报错退出);读取文件:以二进制读写模式(
in | out | binary)打开文件,并通过std::filesystem::u8path处理 Unicode 路径;获取 PE 大小:借助
pe_helpers::get_pe_size(来自 helpers/pe_helpers/pe_helpers.hpp,源码见 helpers/pe_helpers.cpp)解析 PE 头得到镜像大小;出于性能考虑,仅读取文件前 1MB(注释 “1MB is enough”)用于解析;定位与写入:
file.seekp(DOS_STUB_OOFSET, std::ios::beg)定位到 DOS Stub 起始偏移,依次写入:// 4 bytes: 'V' 'L' 'V' '\0' file.write(DOS_STUB_SIGNATURE, sizeof(DOS_STUB_SIGNATURE)); // 4 bytes: (uint32_t)1 file.write((const char *)&DOS_STUB_ONE, sizeof(DOS_STUB_ONE)); // 4 bytes: PE image size file.write((const char *)&pe_size, sizeof(pe_size));多文件循环:对所有传入参数依次处理,任何一步失败(打不开、取大小失败、解析 PE 失败)都会立即报错并返回 1。
从源码结构可以推断,这套改写的目的是让模拟器产物的 DOS Stub 区域带有与原始 Steam 二进制一致的标记字节序列,从而在更深层的结构检查中不被轻易区分——但具体每个字段的业务含义,仓库源码并未完整注释(如DOS_STUB_ONE处保留了疑问注释),本文不做超出源码的断言。
七、验证与自查:如何确认资源已生效
构建完成后,可以从两个层面验证资源注入结果:
- 查看产物文件版本信息:在 Windows 资源管理器中右键构建出的
steam_api64.dll等文件 → “属性” → “详细信息”,若已带--winrsrc构建,应能看到与resources.rc声明一致的“文件版本”“公司”等字段(如 08.33.09.23 / GSE / Steamclient.dll 等); - 检查中间文件:按 README 所述,
rc.exe的编译产物应位于build\tmp\win\rsrc目录下,文件为*.res;确认这些.res存在即说明资源编译环节正常执行; - 检查 DOS Stub:可用十六进制查看器(或
pe_helpers类解析工具)读取产物偏移 0x40 处的内容,确认存在VLV签名、01 00 00 00与 4 字节 PE 大小——这是--dosstub后置命令成功执行的标志。
八、适用前提与注意事项
- 平台前提:
resources/全部内容只服务于 Windows 构建(os.target() == "windows"分支),Linux / SteamOS 构建产物(libsteam_api)不涉及资源注入,无需关注本节内容; - 选项前提:资源脚本仅在传递
--winrsrc时参与编译,DOS Stub 改写仅在传递--dosstub时执行,二者互不依赖、可单独启用; - 工具链依赖:资源编译依赖 Windows 环境中的微软工具链(
rc.exe、cl.exe),跨平台(如 Linux 上用 wine 交叉构建)场景下未在 README 中说明相应替代方案; - 信息边界:
VERSIONINFO中的版本号、WEVT_TEMPLATE内容、DOS Stub 中的VLV标记与固定值1,均来自对原始 Steam 二进制的模仿与仓库内现有数据,本文未对超出源码可确认范围的字段含义作进一步推测。
结语
resources/目录虽然体量不大,却是 gbe 模拟器在 Windows 端“形似”原版 Steam 组件的关键一环:通过VERSIONINFO资源脚本维持版本信息一致、通过WEVT_TEMPLATE与SOURCE_CONTROL_ID补齐附加资源、再通过file_dos_stub工具在构建后改写 DOS Stub 标记。结合 resources/README.md、各resources.rc与 premake5.lua 中winrsrc/dosstub选项的对照阅读,即可完整复现并理解这套 Windows 构建资源流水线。
- 游戏开发
- 逆向工程
【免费下载链接】gbe_fork
Fork of https://gitlab.com/Mr_Goldberg/goldberg_emulator
相关推荐
Humanizer 本地化资源访问指南:深入解析 Localisation.Resources 与资源键体系
Humanizer 本地化资源访问指南:深入解析 Localisation.Resources 与资源键体系 导读 本文聚焦 Humanizer 的本地化基础设
开发工具深入理解 python-sdk 的 MCP 资源(Resources)体系:声明、模板、安全与内容返回
深入理解 python sdk 的 MCP 资源(Resources)体系:声明、模板、安全与内容返回 导读 本文是 Python SDK(Model Cont
人工智能MCP 服务MCP Clientsmoto EMR 资源数据目录(moto/emr/resources)解析:目录结构、去重策略与自动同步脚本
moto EMR 资源数据目录(moto/emr/resources)解析:目录结构、去重策略与自动同步脚本 本指南基于 moto 仓库中 moto/emr/r
Mock测试
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考