跳到內容

Technology

開源|AnyPS5:把 PS5 執行檔重新連結成 Linux 與 Windows 原生程式的轉譯工具

AnyPS5 不模擬 CPU,把 PS5 執行檔改寫成標準的 ELF 或 PE,讓作業系統的載入器把系統函式庫呼叫綁到自寫的實作,再把 RDNA 著色器重編成 SPIR-V。整理它的 NID 改名技巧、著色器重編譯管線,以及用 Vulkan graphics pipeline library 減少卡頓的做法。

AI
Published: at 10:00 AM
編輯本頁下載圖卡

專案 GitHub:boykopovar/AnyPS5

重點

先說清楚

這類把主機遊戲搬到 PC 的工具處在法律灰色地帶。AnyPS5 的 README 說專案用於互通、研究、保存與相容性,不包含、不散布也不需要受著作權保護的軟體、韌體、加密金鑰或專有函式庫,使用者要自行確認來源與用途合法。程式也還在很早期的階段。這篇文章只說明它的程式架構,不涉及如何取得或執行遊戲,也不涉及任何保護機制。

以 2026 年 10 月 13 日從 GitHub API 查到的資料,AnyPS5 在 2026 年 8 月 4 日(台北時間)建立,有約 32,955 顆星、651 個開啟中的 issue,授權是 GPL-2.0(README 註明 only 版),最新版本是 9 月 29 日發布的 v0.1.1。程式以 C++ 和 CMake 撰寫,繪圖用 Vulkan。

內容根據 README、docs/dev/ARCHITECTURE.md,以及 core/libs/nid/src/NidCompute.cpp 等原始碼。

先認識幾個名詞

整體流程

flowchart LR
  elf["PS5 執行檔"] --> relinker["relinker:改匯入表、轉成 ELF/PE"]
  relinker --> app["原生執行檔"]
  prx["core/libs/prx 自寫系統函式庫"] --> nid["nid_patcher:匯出符號改名成 NID"]
  nid --> libs["libs/*.prx"]
  app -- "宿主載入器依 NID 綁定" --> libs
  app -- "執行時送出著色器" --> gpu["RDNA → SPIR-V → Vulkan"]

架構文件開宗明義寫著:沒有任何東西被模擬,轉換後的執行檔以普通行程執行,呼叫系統函式庫的原生實作。整個工具有兩半:

關鍵技巧:把符號改名成 NID

PS5 的執行檔匯入系統函式時,不寫函式名稱,寫的是一個 11 字元的雜湊值,稱為 NID。NidCompute.cpp 的算法如下:

  1. 函式名稱的位元組後面接上一段固定的 16 位元組;
  2. 算 SHA-1;
  3. 取前 8 個位元組,順序反過來;
  4. 用一種把 / 換成 - 的 Base64 字母表編碼,取 11 個字元。
// core/libs/nid/src/NidCompute.cpp(節錄)
const auto digest = Sha1(input);   // symbolName + 16-byte suffix
uint8_t reversed[8];
for (int i = 0; i < 8; i++)
  reversed[i] = digest[7 - i];

常見的模擬器做法,是自己寫一個載入器:讀遊戲的匯入表、逐一查 NID、把位址指到自己的 HLE 函式。AnyPS5 把方向反過來:讓自寫的函式庫本身就以 NID 為名匯出,再把遊戲的匯入表改成 Linux 或 Windows 看得懂的格式。兩邊的名字一致了,宿主系統原本的載入器就能完成綁定,不需要額外的執行期。函式名稱未知、只知道 NID 時,可以用 APS5_EXPORT("<nid>", func) 直接指定。

CPU 指令本身不必翻譯,因為 PS5 就是 x86-64。唯一的例外是 AMD 專有的指令,選用的 --to-intel 步驟會把它們改寫成 Intel CPU 也能執行的寫法。

著色器:從 RDNA 機器碼到 SPIR-V

CPU 那一半可以直接跑,GPU 就不行了。遊戲送給 GPU 的是 RDNA 機器碼和一串命令緩衝區,PC 上的 Vulkan 只接受 SPIR-V。core/shader/recompiler/Recompiler.cpp 依序跑這些階段:

  1. RdnaDecoder:把機器碼解成指令;
  2. ControlFlow:建控制流程圖並結構化。機器碼是一堆跳轉,SPIR-V 要求有結構的 if 與迴圈,這一步要把前者整理成後者;
  3. Translation:RDNA 指令轉成中間表示(IR);
  4. Optimization:轉成 SSA 形式,整理資源與綁定;
  5. SpirvBackend:輸出 SPIR-V。編譯時開啟 ANYPS5_ENABLE_SPIRV_TOOLS 會再用 SPIRV-Tools 驗證與最佳化。

編好的結果放在記憶體,ShaderDiskCache 也存到磁碟,下次執行可以重用。

幾個工程上的取捨:

繪圖管線:拆成四塊來重用

Vulkan 的「繪圖管線」(pipeline)把著色器和一大堆狀態(頂點格式、混色方式、輸出格式……)編在一起,建一個要花不少時間。主機遊戲在 PC 上常見的卡頓,很多就是第一次遇到某種組合時現場建管線造成的。狀態的組合數量又很多,全部預先建好也不實際。

AnyPS5 在顯示卡支援 VK_EXT_graphics_pipeline_library(且支援快速連結)、VK_EXT_extended_dynamic_state 和 VK_KHR_dynamic_rendering 時,把管線拆成四個 library:

library內容
vertex input頂點資料的格式
pre-rasterization shaders頂點著色器等光柵化前的階段
fragment shader像素著色器
fragment output輸出目標格式與混色

每塊依裝置快取。下一個管線只差混色或頂點格式時,只需要重建那一塊;剔除方向、正面方向、深度與模板狀態設成動態狀態,改了也只要重新連結。繪圖呼叫只等「快速連結」完成,後台執行緒再用連結時最佳化(LTO)重新連一次,下一次繪圖起換成最佳化過的版本。

還有一個小設計:push constant(每次繪圖前快速寫給著色器的一小塊資料)用 256 位元組,切成兩個 128 位元組的區段。光柵化前的階段從第 0 個位元組開始放,像素著色器固定從第 128 個位元組開始。這樣像素著色器的 library 不必管頂點著色器用了多少空間,可以單獨建、單獨重用。

不支援這些擴充的顯示卡(或設定 APS5_NO_GPL=1)退回傳統的 render pass 與整個管線。幾種特殊情況,例如幾何著色器、rect list,也一律建整個管線。

其他看得出功夫的地方

跟常見做法比

像 PS4 模擬器 shadPS4 這類專案,由模擬器自己的載入器載入遊戲,再把系統呼叫導向 HLE 實作(AnyPS5 v0.1.1 的更新紀錄裡也有移植 shadPS4 字型模組的 PR)。AnyPS5 把連結這一步提前到轉換時完成,產出一個宿主系統看來完全正常的執行檔。好處是沒有中間層,除錯工具、效能分析器都能直接使用;代價是所有函式都要以宿主系統的呼叫慣例、執行緒模型重新實作,遇到系統呼叫也要在 relinker 階段就處理。

限制與成熟度

參考資料

註釋

編輯本頁

Podcast

關於語音摘要

此語音摘要由 Google NotebookLM 提供,不完全代表作者本人對文章的理解。