AnyPS5 NID系统揭秘:PS5的SHA-1符号哈希如何在Linux/Windows上被解析与重绑定
【免费下载链接】AnyPS5Tool for automatic PS5 executables porting to Linux and Windows项目地址: https://gitcode.com/gh_mirrors/an/AnyPS5
AnyPS5 是一款自动移植 PS5 可执行文件到 Linux 与 Windows 的工具,其核心难点之一是NID 符号哈希系统。PS5 的动态链接并不直接使用普通函数名,而是使用基于SHA-1 摘要生成的短字符串标识,即 NID。AnyPS5 的 NID 模块 会重新计算这些 NID,并将 ELF 或 PE 二进制中的导出、导入符号改写为宿主系统可识别的名称,从而让 PS5 程序在 Linux/Windows 上完成动态链接与重绑定。
为什么 PS5 移植不能直接保留原始符号名?
在常规 Linux 或 Windows 程序中,动态链接器通常通过函数名解析依赖,例如某个共享库导出了某个函数,程序就会按名字找到它。PS5 的许多系统库则使用NID(Named Identifier)作为符号标识。NID 是一串由 SHA-1 摘要派生出的 Base64 风格字符,本质上是一种“符号哈希指纹”。
对移植工具来说,这带来了两个问题:
- 原始 NID 不能直接作为普通函数名使用,因为宿主系统的动态链接器不认识它;
- PS5 库导出的符号需要被转换成宿主平台可解析的形式,这样才能与系统 PRX 库重新绑定。
AnyPS5 的 NID 系统就是负责完成这件事:读取符号、计算 NID、识别特殊后缀、改写二进制,并最终让 Linux ELF 或 Windows PE 文件能正常加载。
NID 是如何从函数名计算出来的?
NID 的计算逻辑位于 NidCompute.cpp。整个流程可以概括为四步:
- 取原始符号名:例如某个 PS5 系统函数名;
- 追加固定 16 字节后缀:项目内置了一组固定字节,用来保证 NID 与特定 PS5 标识空间一致;
- 计算 SHA-1 摘要:由 Sha1.cpp 提供实现;
- 取摘要前 8 字节并反转编码:最终得到 11 个字符的 NID 字符串。
这里的 SHA-1 并不是用来加密或校验文件,而是作为符号到短标识的单向映射。同一个函数名只要经过相同算法,就会得到稳定的 NID。
关键函数ComputeNid()位于 NidCompute.cpp。它接收符号名和库名,最终返回一个 11 字符的 NID。
特殊后缀:NID 系统里的“免哈希”标记
并不是所有符号都必须通过 SHA-1 重新计算。NID 系统支持一批特殊后缀,用来告诉解析器如何处理当前符号。这些规则集中在 NidPatcherUtils.hpp。
常见的后缀包括:
_nid_postfix:表示这是一个已经带有 NID 风格的占位名,需要剥离后再参与计算;_nid_no_patch:表示该符号保持原样,不重新生成 NID;_nid_no_patch_cut:表示保留原始名称的一部分,然后继续解析;_nid_disambig:用于区分重载或重复名称,计算前会去掉消歧义数字;SDL_前缀:某些运行时接口需要直接保留,不进入 NID 计算。
这种设计非常重要。因为如果所有符号都无差别地做哈希重写,一些需要保持名字兼容的接口就会失效。特殊后缀相当于给符号打上了处理策略标签。
解析入口在 NidResolver.cpp,其中的ResolveOneName()会先判断特殊后缀,再决定是否调用ComputeNid()。
Linux 上的重绑定:改写 ELF 导出与导入符号
在 Linux 平台上,PS5 可执行文件最终会被转换为 ELF 格式。动态链接器依赖.dynsym与.dynstr等节区来解析符号。NID 系统的 ELF 补丁器主要工作包括:
- 扫描
.dynsym中所有可导出符号; - 调用 NidResolver.cpp 批量生成 NID 映射;
- 重建
.dynstr字符串表; - 修正
DT_NEEDED、DT_SONAME等动态段字符串偏移; - 重建
gnu.hash,保证链接器仍能以 O(1) 级别快速查找符号; - 修正重定位项中引用的符号索引。
相关实现位于 ElfNidPatcher.cpp。其中PatchNids()是整个 ELF 重绑定的核心入口,它会把原始符号名替换为计算后的 NID,同时维护字符串表偏移一致性。
这一步的意义在于:转换后的 ELF 文件仍然能被 Linux 动态链接器识别,并且导入的 PS5 系统函数能按 NID 找到对应实现。
Windows 上的重绑定:改写 PE 导出表与导入表
在 Windows 平台上,PS5 程序会被转换成 PE 格式。PE 文件的符号解析依赖导出表和导入表。NID 系统的 PE 补丁器位于 PeNidPatcher.cpp,其工作流程为:
- 读取 PE 导出目录;
- 提取所有导出函数名;
- 通过 NID 解析器生成新的符号名;
- 将新的名称重新打包进导出字符串区域;
- 更新导出名称表与序号表;
- 进一步扫描导入表,把以
sce开头或带 NID 后缀的导入名替换为真实 NID。
这里的细节很关键:Windows 导入表中的函数名是按 RVA 引用的,补丁器不能只是“改一个字符串”,还必须同步更新指向该字符串的偏移。PeNidPatcher.cpp 实现了从导出字符串重排到导入 thunk 重写的完整链路。
完成之后,PE 文件就能以宿主 Windows 加载器能理解的方式解析符号,并将 PS5 程序对系统库的调用重绑定到 AnyPS5 提供的实现上。
排除规则:如何避免把某些符号误改成 NID?
有些库或函数并不适合参与 NID 重写,例如:
- 项目自身需要保留原始名称的导出;
- 第三方运行时接口;
- 某些与宿主系统直接交互的符号。
为此,NID 系统支持导出排除列表。该功能会从参考库文件中读取符号表,再把命中的符号从 NID 重写范围中剔除。相关逻辑位于 ExportExclusions.cpp。
它同时支持两种参考文件格式:
- PE 文件:解析 Windows DLL 的导出表;
- ELF 文件:解析 Linux 共享库的动态符号表。
这意味着开发者可以提供一个“未补丁库”作为参照,让 NID 系统知道哪些名字必须原样保留。这为自动移植提供了非常重要的安全阀:既保证大部分系统符号被正确重写,又不会误伤需要保持兼容的接口。
从命令行看 NID 重绑定流程
NID 模块的命令行入口在 main.cpp。它接受一个库名、可选的--preserve-exports参考库,以及一个或多个待补丁文件。
典型流程可以理解为:
- 读取目标二进制文件;
- 根据文件头判断是 ELF 还是 PE;
- 调用对应的 NID 补丁器;
- 将结果写回文件。
这里体现了 AnyPS5 的设计思想:NID 重绑定不是独立工具,而是整体 relinker 流水线中的一环。整体二进制转换位于 core/relinker,而系统库实现位于 core/libs/prx。NID 系统负责“符号层”的适配,relinker 负责“格式层”的转换,两者结合才让 PS5 程序在 Linux/Windows 上真正可运行。
小结:NID 系统为什么是 AnyPS5 的关键拼图
如果把 PS5 移植到 Linux/Windows 比作一次“语言翻译”,那么 NID 系统就是负责符号翻译规则的核心组件:
- 它把原始符号名映射成稳定的 SHA-1 短标识;
- 它根据特殊后缀决定是否改写、保留或裁剪;
- 它同时支持 ELF 与 PE 两套二进制结构;
- 它通过排除机制避免误改关键接口;
- 最终让宿主系统动态链接器能正确完成重绑定。
理解 NID,就理解了 AnyPS5 自动移植能力中最隐秘、也最关键的一环:它不是模拟 PS5 的运行时,而是把 PS5 的符号体系翻译成 Linux 与 Windows 都能理解的语言。
延伸阅读
- core/libs/nid/:NID 计算、解析与补丁器实现;
- core/relinker/:ELF/PE 重新链接与格式转换主流程;
- core/libs/prx/:PS5 系统 PRX 库的宿主端实现;
- docs/dev/CONVENTIONS.md:项目代码风格与约定;
- docs/dev/TechnicalDebt.md:项目当前技术债务与改进方向。
【免费下载链接】AnyPS5Tool for automatic PS5 executables porting to Linux and Windows项目地址: https://gitcode.com/gh_mirrors/an/AnyPS5
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考