-
buo4 技术教程组 用户
AnyPS5 开源:不做模拟器,把 PS5 游戏可执行文件直接"重链接"成 PC 原生程序
想在 PC 上玩 PS5 游戏,过去你基本只有一条路:等一个模拟器。
模拟器的思路,是在软件里完整再造一台主机——CPU 要逐指令译码,GPU 要翻译命令流,系统调用要逐一拦截。三层开销叠在一起,换来的是性能折损和动辄以年计的适配周期。RPCS3 团队打磨十几年,才把 PS3 拉到"能玩"的水位;PS5 这边,门往哪边开都还没摸到。
但这里一直有个吊诡的问题:PS5 用的是一颗 AMD Zen 2 处理器,x86-64 架构,和你台式机里那颗是同一套指令集。游戏的机器码,你的 CPU 天生就认识。真正陌生的是芯片外面那层皮——PS5 的 PRX 模块、按 NID 绑定的符号表、满屏幕 sce_ 开头的系统调用。这些东西在 PC 上不存在,但只要把它们换掉,游戏代码本身是可以原地跑的。
于是问题就变了:既然指令集相同,为什么不直接给可执行文件"换一套系统接口"?Windows 程序能靠 Wine 跑在 Linux 上,逻辑一模一样——不翻译指令,只替换程序依赖的那层 ABI。今天要聊的 AnyPS5,就把这个逻辑搬到了主机上:自动把 PS5 可执行文件移植成 Linux 和 Windows 上的原生程序,没有模拟,没有独立的运行时进程,转换完的游戏就是一个普通进程。
换句话说,它不打算"骗过游戏,让它以为自己在 PS5 上",而是把游戏直接改造成"本来就该长这样的 PC 程序"。

这张图来自项目官方 README:六款游戏已经通过 AnyPS5 在 PC 上跑了起来。当然,"跑起来"和"能通关"之间还有很长的路,这一点我们放到最后说。
这件事的意义还不只是"多一种玩游戏的方式"。主机是有寿命的,硬件会坏、服务会关,而游戏的二进制文件只要还在,理论上就该有办法在别的机器上继续运行。模拟器路线当然也是保存,但它保的是"整台机器的行为";AnyPS5 这类项目保的是"程序与系统之间的接口契约"——后者更轻,也更容易随着时间沉淀成可维护的基础设施。往大了说,当主机和桌面 PC 在架构上越来越像,"移植"这件事本身就应该被重新定义。
一句话定位
AnyPS5 是一个用 C++ 写的 PS5 可执行文件移植工具,采用 GPL-2.0 协议。核心就两块:一个 relinker(重链接器),负责把 PS5 的 ELF 可执行文件改写成目标系统的原生格式;一套手写的 PS5 系统库重实现(源码在 core/libs/prx),编译成普通动态库,供转换后的程序链接。
官方对它的描述非常克制:"自动把可执行文件移植到 Linux 和 Windows 的工具"。不承诺还原源码,不承诺跑一切游戏,甚至不承诺跑起来的游戏一定能通关。它只把一件事做扎实:二进制层面的接口替换。
输出侧支持三种目标:Linux ELF、Windows PE,以及 x86-64 Mach-O(macOS,Apple 芯片上经 Rosetta 运行)。不过要心里有数:官方发布包只附带 Linux 和 Windows 两套系统库,macOS 路线的库需要自己构建。项目目前仍处于密集开发期,接口和构建方式还在快速演进,README 里甚至专门有一份"技术债"文档记录已知的欠账——这种坦诚在早期项目里反而是加分项。
核心一:relinker 的四步流水线
relinker 的工作顺序,官方架构文档写得很清楚。拿到一个"干净的" ELF 可执行文件后,它依次做四件事:
- 按 NID 解析导入表,把游戏想调用的系统函数一个个对上号;
- 扫描系统调用,检查它们在目标平台上的合法性和兼容性;
- 过滤没有被引用的 NID,没用的导入直接丢掉;
- 按目标平台的 ABI 构建 SysV 动态段,生成可动态链接的节。
随后交给两个补丁器之一收尾:Linux 走 LinuxElfPatcher,Windows 走 WindowsPePatcher,产物分别是 app.elf 和 app.exe。游戏自带的 sce_module/* 打包模块也不闲着,由 GuestModuleBuilder 单独转换成 app0/sce_module/* 的目标格式,和主程序一起被系统加载器绑定到库上。
这套链路是可复现的——同样的输入,得到同样的原生程序。对一个移植工具来说这是底线:没人希望自己的"游戏"每次转换都长得不一样。
顺带解释一个入门门槛:输入必须是"干净的" ELF。PS5 可执行文件在主机上通常装在 SELF/fSELF 容器里,relinker 会直接跳过这种容器并给出警告——你需要先用项目自带的 tools/unwrap_self.py 把解密后的容器解包成 ELF(它会按 SELF 段记录的偏移摆放各个段,并拒绝那些看起来还是加密的容器)。模块目录也有讲究:sce_module/、sce_modules/、prx/ 三者只能出现一个,且只处理目录内的直属文件。
还有个细节值得说:PS5 代码里有少量 AMD 专有指令,Intel 平台的机器跑不了。AnyPS5 提供 --to-intel 选项,在转换阶段就把这些指令改写成等效的通用指令。注意它的边界——只能改写在可执行文件和打包模块里静态存在的指令,程序运行时动态生成的那些它管不了,所以这类游戏仍然需要一台 AMD 主机。macOS 路线类似:--macos 产出 x86-64 Mach-O(Apple 芯片上经 Rosetta 运行),--to-rosetta 则进一步压低 Rosetta 不支持的指令。
核心二:NID——为什么链接机制可以和主机保持一致
这是我觉得整个项目最有味道的设计。
PS5 的系统库不是按函数名导出符号的,而是按 NID——一个从函数名计算出来的数字 ID。游戏的可执行文件里记录的是"我要 NID 为 xxxxx 的那个函数",主机加载器再把这个数字绑定到对应实现上。
AnyPS5 的重实现库沿用了同一套机制:构建完成后,nid_patcher 会把每个导出符号重命名成它的 NID;遇到名字对不上的函数,可以用 APS5_EXPORT("", func) 直接硬写。这样一来,宿主系统的加载器就能按 NID 完成导入绑定——和主机上的机制一模一样,只是库的实体换成了开源实现。
这个选择的好处是双重的。一方面,游戏代码里的导入逻辑一行都不用改,它本来就在按 NID 找函数;另一方面,重实现库可以一个函数一个函数地往外挤——每实现一个,覆盖率就涨一格,不需要憋大招。配合项目主页上的进度页(系统库覆盖率和着色器覆盖率),外部观察者能直观看到项目在往哪走。
重实现库的组织方式也挺有工程洁癖:libc 之外的每个库都通过 libc.exports 文件获知自己该导出哪些名字,nid_patcher 只在这份清单变化时才重写它;Windows 上生成导入库时使用固定的 SOURCE_DATE_EPOCH,并且只有在字节真正变化时才替换文件——改了某个库的源码,重新链接和重打补丁的就只有它自己。对一个函数数量以千计、还在高速增长的系统库工程来说,这种"增量可复现"直接决定开发者还敢不敢频繁提交。
不过要提醒一句:README 自己给这个百分比加了注释——它是"项目迄今已知并声明实现的功能数"占比,不是"PS5 全部系统功能"的占比,分母本身会随着新功能的声明而变大。看这类数字,别急着下结论。
核心三:CPU 原地跑,GPU 现场翻译
如果说 CPU 侧的思路是"不动代码",GPU 侧就是另一番景象了。
PS5 的 GPU 是 AMD RDNA 2 架构,着色器是 RDNA 汇编,PC 上的 Vulkan 驱动可不认识这东西。AnyPS5 的图形栈由 libSceAgcDriver 承担:游戏照旧往命令缓冲里灌指令(PM4 格式,分 DCB 直接命令缓冲和 ACB 间接命令缓冲),驱动在 Execution/Pm4 环节拆解状态、绘制和 dispatch 调用,遇到着色器就送进重编译器。
重编译器的流水线是这样的:RdnaDecoder 先解码指令,ControlFlow 构建控制流图并做结构化,翻译成中间 IR,优化阶段做 SSA、资源和绑定优化,最后 SpirvBackend 产出 SPIR-V,交给 Vulkan pipeline。如果构建时打开了 ANYPS5_ENABLE_SPIRV_TOOLS,产物还会过一遍 SPIRV-Tools 做校验和优化。
这里有几个我觉得挺有意思的工程细节:
- 编译结果有内存缓存,ShaderDiskCache 还会落一份到磁盘,下次启动直接复用,同一个着色器不用反复过一遍反编译流水线;
- 设备支持 VK_EXT_graphics_pipeline_library、VK_EXT_extended_dynamic_state 和 VK_KHR_dynamic_rendering 时,绘制走 vkCmdBeginRenderingKHR 而不是 render pass,pipeline 会拆成顶点输入、前光栅化着色器、片元着色器、片元输出四段分别链接——换混合模式或顶点布局时只需要重建对应那一段,其余复用;不支持的设备则退回 render pass 和整体 pipeline 的传统路径;
- pipeline 的构建过程会被记入日志文件,下次启动时在后台线程池里提前重建,争取让第一帧绘制就命中已驻留的 pipeline;
- compute 着色器如果需要的 LDS 共享内存超出宿主机的 maxComputeSharedMemorySize,不会直接失败,而是退到一个连贯的 storage buffer,按 WorkgroupID 索引——跑得慢,总比跑不了强。
所以准确的说法是:CPU 代码原生执行,GPU 侧边跑边译。这也是它和"全指令翻译"路线的本质区别——性能天花板取决于 GPU 这层翻译的效率,而这部分是有优化空间的。更何况着色器的重编译是一次性成本:编译、缓存、命中,三步之后每帧的开销就只剩 PM4 命令缓冲的解释,而这部分数据量比整帧像素小得多。翻译成本被前置到了加载阶段,这正是它可以接受的原因。

容易被忽略的四个设计取舍
一、不静默出错,直接终止。 README 里白纸黑字:遇到不支持或意外的状态,严格抛出 std::runtime_error,把原因打到 stderr,然后进程终止。没有降级模式,没有"假装能跑"。对一个移植工具来说这是清醒的价值观——一个能启动但行为诡异的游戏,比一个启动不了的游戏危害大得多,前者会浪费使用者大量时间去追一个根本追不到的 bug。
二、线程取消语义照着 FreeBSD libthr 实现,而且明确标注这是"参考语义,不是主机实测"。 官方架构文档里,pthread 取消行为——新线程默认开启延迟取消、被禁用的请求保持挂起、cleanup handler 按逆序执行、条件变量等待被取消时先重新拿回 guest mutex——全部对标 FreeBSD libthr 的参考实现,还钉死了具体 commit。这种"照公开规范实现、不靠黑盒逆向"的 HLE(高层模拟)哲学贯穿整个项目:接口行为以规范为准绳,而不是以摸清某个具体固件为准绳。
三、启动时先查 CPU 扩展,缺什么说什么。 PS5 的 Zen 2 代码编译时就假定 AVX2、FMA、F16C、BMI1、BMI2、LZCNT、MOVBE 这些扩展存在,运行时不做检查。AnyPS5 在转换后的程序启动时先替你做一遍检查,缺哪个扩展、哪颗 CPU,清清楚楚报出来,在跑到任何游戏代码之前就停下。这种"提前把话说清楚"的作风,比让游戏在半小时后莫名崩溃要体贴得多。
四、GPL-2.0 only 加严格的免责声明。 项目只采用 GPL-2.0,README 的免责声明把边界划得很死:面向互操作、研究、保存和兼容性目的,不包含、不分发、也不需要任何受版权保护的软件、固件、加密密钥或专有库,用户自行确保所使用的二进制来源合法。所有 prx 系统库都是自己重写的——这是它能维持这个合规姿态的前提,也是 clean-room 重实现这条路能走远的关键。
安装与使用
最省事的方式是用官方网页版转换器:打开项目主页,选文件、转换,全程在本地完成,文件不出设备。
命令行用法也不复杂。准备一个干净的 ELF 可执行文件,把它自带的模块放进旁边的 sce_module/、sce_modules/ 或 prx/ 目录(如果是从 SELF/fSELF 容器里提取的,先用 tools/unwrap_self.py 解包),然后:
# Linux 输出
relinker source/input.elf app.elf
# Windows 输出
relinker --windows source/input.elf app.exe
# macOS 输出(Apple 芯片上经 Rosetta 运行)
relinker --macos source/input.elf eboot
运行时还需要把构建好的系统库放进 libs/,游戏资源放进 app0/,布局大致是这样:
app.elf(或 app.exe)
libs/
*.prx
app0/
eboot.bin
sce_module/
<转换后的模块>
想从源码构建的话,项目基于 CMake 和 C++20。只构建转换器本身可以走精简模式,不需要初始化 SDL、Vulkan、FFmpeg、FreeType 那些子模块:
cmake -S . -B build-relinker -G Ninja -DCMAKE_BUILD_TYPE=Release -DANYPS5_RELINKER_ONLY=ON -DBUILD_TESTING=ON
cmake --build build-relinker --parallel
完整构建需要先 git submodule update --init --recursive,Windows 上则指定了 MinGW-w64 工具链。手柄输入走 SDL 映射,摇杆和扳机都支持;键鼠可以通过工作目录下的 anyps5-input.ini 配置。
如果转换后的程序起不来,别急着怀疑工具链:Windows 上可以加 --windows-diagnostics 让它启动时打印依赖诊断;系统库的搜索路径默认是可执行文件旁的 libs/,也可以用 --rpath 改;另外别忘了有些游戏还要求 app0/sce_sys/param.json 就位——AnyPS5 要靠它读 title ID 和显示名,缺了它连窗口都建不出来。
适合谁
- 游戏研究与保存从业者:手里有合法取得的镜像,想在开放硬件上研究 PS5 的格式、模块打包和 NID 机制,这个项目是稀缺的一手材料;
- 图形与工具链开发者:RDNA 汇编到 SPIR-V 的完整重编译流水线、Vulkan pipeline library 的工程化用法,值得逐行读;
- PS5 homebrew 作者:把自研程序转换到 PC 上测试,比反复在真机上部署迭代快得多;
- 对"非模拟器移植"这条路线好奇的人:它证明了当主机与 PC 同架构时,"给二进制换接口"在工程上是走得通的——这种思路比结论更值钱;
- 掌机和开源硬件爱好者:官方使用文档里已经提到 Ruffle、VLC 等开源程序转换后在 PC 上运行的案例,同样的流程也适用于其他 x86-64 开放硬件,值得上手折腾。
结尾
AnyPS5 现在离"随便挑个游戏都能玩"显然还有距离:官方 README 的展示图里只有六款游戏的身影,社区维护的兼容性路线图(由公开讨论区数据生成、经人工核对)里达到"可玩"阶段的也只有个位数,未解决的阻塞问题还数以百计,系统库覆盖率的分母仍在一天天变大。
但它的价值从来不只在于"今天能玩什么"。当主机和桌面越来越像同一台机器,"模拟整台设备"的做法就显得越来越笨拙——Wine 早就证明了替换接口层这条路更轻、更快、更可持续。AnyPS5 把同样的哲学带进了主机移植,而且是少见的严格姿态:GPL-2.0、全量 clean-room 重写、fail-fast。哪怕你从来不碰 PS5,也值得花半小时读一遍它的架构文档——那里面关于"二进制接口替换"的工程判断,比你想象中更通用。
说到底,评价一个早期基础设施项目,不该看它此刻的兼容列表有多长,而该看它选的路对不对、走的步稳不稳。AnyPS5 两边都占了。剩下的事,交给时间和社区。
相关资源:
- GitHub 仓库:https://github.com/boykopovar/AnyPS5
- 网页版转换器:https://boykopovar.github.io/AnyPS5/
- 官方架构文档:docs/dev/ARCHITECTURE.md(仓库内)
- 官方使用文档:docs/user/USAGE.md(仓库内)
- 社区兼容性路线图:https://alxcbtw.github.io/anyps5-roadmap/