Hacker News 中文摘要

RSS订阅

PSP《战神》重编译为 WebAssembly,可在浏览器中运行 -- God of War on PSP, recompiled to WebAssembly and running in the browser

在浏览器里不用模拟器运行 PSP(PlayStation Portable)游戏:游戏的 MIPS 机器码会被提前翻译成 C++,编译为 WebAssembly,再链接到一个精简重写的 PSP 操作系统和图形芯片实现上,画面由 WebGL2 绘制。第一个以这种方式跑起来的作品是《战神:奥林匹斯之链》(God of War: Chains of Olympus),它从开机启动即可运行。

正文

无需模拟器,PSP 游戏就能在浏览器里运行。游戏的 MIPS 机器码会被提前翻译成 C++,编译成 WebAssembly,再链接一个自行实现的小型 PSP 操作系统和绘图芯片(用 WebGL2 绘制)。

战神:奥林匹斯之链在浏览器中运行

标题菜单

网页

战斗画面(全屏 1440×816)

用这种方式跑起来的第一款游戏是《战神:奥林匹斯之链》。在笔记本的 Chrome 和 Firefox 上,目前测过的场景都能从开机、菜单、过场到战斗以每秒 60 帧运行,分辨率最高可达 PSP 的 4 倍,手机上也能用屏幕触控操作。音乐、语音和音效都正常,电影过场暂时跳过。

《战神:斯巴达之魂》用同样的脚本接着跑通了。它只需要对一个小文件做 PSP 的 DRM 解密、补几个系统调用和一处光照修正,无需性能优化:在 3 倍原生分辨率下能跑 55 到 60 帧。

本仓库不含任何游戏数据。你需要自备拥有的游戏光盘镜像,用本仓库的脚本在本地把它转成一个网页。

工作原理

重新编译。 PSPRecomp 分析解密后的可执行文件,找出函数,每 16 KiB 客户机代码生成一个翻译单元,输出 C++。这些代码运行在寄存器文件和 PSP 内存模型上。本项目给 PSPRecomp 加了一些修正(在 patches/),以及新目标:profile/ 里的 web 配置。

小型 PSP 内核。 游戏向 PSP 操作系统提的要求,都由 profile/host 里的高级模拟应答:带信号量、事件标志和回调的协作线程,内存分区,文件系统(光盘数据通过 HTTP Range 请求流式传输,所以只需先下载可执行文件),手柄、音频输出、存档和消息对话框,以及电影播放器的簿记。客户机时间以帧推进,所以无论主机跑多快,游戏看到的都是稳定的 60 Hz。

图形。 PSP 的图形芯片 GE 接收显示列表。ge.cpp 在 CPU 上解码,包括顶点格式、蒙皮、光照、纹理生成、裁剪和背面剔除,把批处理三角形和渲染状态交给 ge_gl.cpp。在那里,帧缓冲变成按 VRAM 位置索引的 WebGL 渲染目标,所以渲染到纹理再读回的特效全程留在 GPU。像素格式重解读、PSP 的 stencil-in-alpha、雾和块传输也在 GPU 上模拟,一切都能以原生 480×272 的 1 到 4 倍分辨率渲染。

像 PSP 一样双线程。 PSP 上图形芯片处理某一帧显示列表时,CPU 在准备下一帧,游戏也依此编写。这里 GE 和 WebGL 上下文跑在带 OffscreenCanvas 的工作线程上:入队列表立即返回,只有显式同步调用会等待。一帧的耗时取较忙的那条线程,而非两者之和。

声音。 音效来自重新实现的 PSP 语音合成器(32 个 ADPCM 声部,带音高和 ADSR 包络),音乐和语音来自用 FFmpeg 解码器解出的 ATRAC3+ 流,混音送入 AudioWorklet。

提速经验

第一个能玩的版本只有 6 帧每秒。从 6 到接近 60,大部分提升来自搞清楚时间到底耗在哪,而非让渲染器更快。

《战神》换帧缓冲时不等垂直消隐,于是不加限制时,游戏每上屏一帧就要画约八帧。把在一个消隐期内换两次缓冲的线程按住直到下一次消隐(PPSSPP 也用这招),单这一项就把每显示帧的工作量降到八分之一。

在 WebAssembly 里,用 std::chrono 读时钟会走 clock_gettime 和 JavaScript 的 BigInt 转换。每个图元都读时钟的性能计时器占了约三分之一帧时间,直到改为只在性能分析开启时读 performance.now()。

Firefox 会复制每次 WebGL 缓冲上传到 GPU 进程,且索引缓冲一改动,下次绘制会重新校验整个缓冲。于是共享 4 MB 索引环让 Firefox 掉到 3 帧每秒;给每次绘制分配独立的小索引缓冲就修好了。反向操作(把顶点数据分块写进一个大缓冲)对任何浏览器都没用,还让手机卡住,因为移动 GPU 驱动在修改 GPU 可能还在读的缓冲时会等待或复制。

PSP 把模板缓冲放在帧缓冲的 alpha 通道里,《战神》用它做投影阴影和限制模糊。用全屏 Pass 互相同步模板和 alpha 虽然正确,但在 4 倍分辨率下每帧多花 6000 万像素。后来追踪具体哪些矩形、哪些模板位和常量值真的变化了,把这个数降到约 700 万。

最后,《战神》会入队每帧显示列表,在等待前继续做下一帧。让 GE 跑在自己线程上,把激烈战斗的成本从“游戏加图形”(Firefox 里约 17 毫秒)变成了两者中较大的那个(约 10 毫秒)。

第二款游戏

《斯巴达之魂》用 port.sh 从 ZIP 变成网页,脚本和重新编译器都没改,但开机后卡死。它用 PSP 的 DRM 标志打开一个 176 字节文件,把密钥交给 sceIoIoctl 再检查回读。文件是 PGD 格式,由 amctrl.prx 用 KIRK 加密引擎解密,光盘游戏实质是带 KIRK 密钥库三个密钥的 AES-128:基于 CMAC 的头部校验和数据计数器模式。profile/host/pgd.cpp 实现了它。

下一个问题是白屏天空,菜单也有同样白雾。对一帧的绘制做二分排查,定位到一层开了光照的云,其不透明度来自全局环境光的 alpha,而光照代码漏了这因子。性能无需优化:一帧耗时 6 到 8 毫秒,和《奥林匹斯之链》一样。

移植你自己的游戏

你需要 git、CMake、Ninja、C++20 编译器和 Python 3。全部在 Linux 跑过;macOS 应该能构建浏览器版但未测试,原生测试运行器需要 EGL 和 OpenGL ES 头文件(Debian/Ubuntu 上是 libegl-dev 和 libgles-dev)。每个游戏约需 2 GB 磁盘空间存放解压光盘、生成代码和构建产物。

先克隆仓库并进入目录,运行 scripts/setup.sh 获取 PSPRecomp、补丁和 Emscripten SDK。接着用 PSP_DECRYPT=/path/to/decrypter scripts/port.sh mygame "My Game.iso" 移植,再 scripts/serve.sh mygame 打开 http://localhost:8613/。port.sh 会解压光盘(ISO、含 ISO 的 ZIP 或已解压文件夹),解密 PSP_GAME/SYSDIR/EBOOT.BIN,翻译成 C++,写好流式读取其余光盘的清单并构建网页。耗时几分钟;8 核笔记本上《战神》从 ZIP 到可玩网页约 4 分钟。加 --native 还能构建无头运行器,可把帧导出图片、录音频,是看新游戏能跑多远的最快方法。

零售光盘上的可执行文件是加密的。PSP_DECRYPT 指定任意按 tool <in> <out> 调用并输出纯 ELF 的工具,如 DecEboot 或 pspdecrypt。PPSSPP 也可在跑游戏时转储解密后的可执行文件(设置、工具、开发者工具)。

预期要现实:目前只跑通两款游戏,且都是 Ready at Dawn 用同一引擎做的,所以对其他工作室的游戏能跑多远没什么参考价值。另一款游戏很可能会卡在某个没人实现的系统调用(日志显示 [hle] unimplemented ...)或不支持的 GE 特性。docs/internals.md 介绍了查缺的工具,代码组织上加个调用只需几行。

托管

网页用 SharedArrayBuffer 做线程,所以必须跨源隔离托管,带 Cross-Origin-Opener-Policy: same-origin 和 Cross-Origin-Embedder-Policy: require-corp 头。scripts/serve.py 会发这两个头,并支持光盘流式传输依赖的 Range 请求。想在手机试,在前面套隧道,如 cloudflared tunnel --url http://127.0.0.1:8613,它会透传这些头。隧道运行期间任何拿到地址的人都能加载游戏,所以别公开,用完就关。

不能在 OffscreenCanvas 上画 WebGL2 的浏览器会自动退化为单线程;?threads=0 强制单线程,?profile 在状态栏加每帧耗时分解。

法律

本仓库只有原创代码、PSPRecomp 补丁和各自授权的第三方代码,不含游戏代码或数据。生成的 C++ 和 WebAssembly 是游戏可执行文件的翻译,归游戏所有者所有:留在自己机器上,别发布。用你拥有的游戏光盘镜像。《战神》是索尼互动娱乐商标;本项目与索尼或任何游戏发行商无关,未获背书。

致谢

静态重编译由贡献者的 PSPRecomp(MIT)完成。PPSSPP 和 JPCSP 记录了这里模拟的大量硬件行为,音乐语音用了 PPSSPP 独立打包的 FFmpeg ATRAC3/ATRAC3+ 解码器(LGPL 2.1 或以上,在 profile/third_party/at3_standalone)。Emscripten 构建 WebAssembly。

许可证

本仓库代码用 MIT 许可证(见 LICENSE)。profile/third_party/at3_standalone 保留 LGPL 2.1 许可。

评论

社区普遍惊叹网页直接运行PSP游戏的技术进步与保存意义,少数人质疑其本质仍是模拟器且质量不佳。

认为浏览器运行PSP游戏是惊人的技术飞跃

  • 从早年模拟器需笨重硬件且帧率低,到如今网页端流畅运行,被视作魔法;用户也期待跨平台轻松玩经典。认可度:较多人 > 十年前在i7笔记本上这游戏只有15帧,现在简直像魔法(@sigma5)

看好逆向编译移植对老游戏保存的价值

  • 认为AI正打破模拟器模式,通过逆向直接移植保存老游戏,在厂商不开放IP时开发者值得赞赏。认可度:较多人 > 厂商不再从老游戏获利却不肯释放IP,因此非常感激这类开发者的工作(@fb03)

认为这本质仍是模拟器且实现拙劣

  • 指出所谓无模拟器实为带小型内核的高层模拟,将模拟器与游戏捆绑,属需特定补丁的劣质做法;温和意见则认为仍是类似JIT的模拟栈。认可度:少数人 > 这不过是多了步骤的糟糕模拟器,把模拟器和游戏塞进一个二进制里,纯粹糊弄(@anonymous908213)

担忧索尼等版权方将很快下架项目

  • 用户猜测版权方可能迅速要求下架,并提及类似项目已被移除。认可度:少数人 > 不知道索尼多久会把它拿下(@pier25)

有价值的补充

  • PSP上的《战神》两作发售较晚,画质曾被认为超越大量PS2游戏,在掌机平台属图形标杆(@accrual)