README.md

hohohololive — 公開版

Unity 手遊資源系統的逆向工程紀錄與格式轉換工具

命名致敬 sssekai


⚠️ 先讀這段:這是刻意閹割過的版本

這個版本不包含解密。 完整版有,公開版沒有,而且這不是疏漏,是刻意的。

移除了什麼

移除 內容
hohohololive/crypto.py 金鑰衍生與解密實作
ClaudeDocs/REF_ALGORITHM_MATH.md 演算法的完整數學規格
ClaudeDocs/RE_NOTES_CRYPTO.md 解密過程的逐步紀錄
ClaudeDocs/OVERVIEW.md §3 的虛擬碼 可直接實作的填充順序、雜湊種子、白化步驟
CLI 的 decrypt-bundle / decrypt-audio 兩個直接解密的子指令
scripts/ 整個目錄 混著 Frida 解密 hook 與一次性診斷腳本,逐支審不划算
ClaudeDocs/GEMINI_TASK.mdDISPATCH_SUPERVISOR.md 給代理跑的工單,綁死本機路徑、引用未收錄的腳本

為什麼不是「只拿掉金鑰,留下演算法」

一開始的計畫是這樣,但這個切法在這裡不成立,值得寫下來:

一般的密碼系統是「公開演算法 + 秘密金鑰」,遮掉金鑰就夠了(Kerckhoffs 原則)。 這裡不是。 這套機制的金鑰是從資產名推導出來的——輸入是檔名,輸出是金鑰表。 推導程序本身就是金鑰,沒有一個獨立的秘密可以拿掉。

而原始實作裡唯一的魔術常數,是一個單一位元組的雜湊種子。在:

這兩個條件下,256 種可能的窮舉是微秒級的事。只遮那個常數,降低的工作量是零, 卻會做出一個「看起來遮過、其實沒有」的東西——那比兩個極端都糟,因為它給假的安心感。

所以整個解密層一起移除,而不是做部分遮蔽。

法律上的考量

台灣著作權法 §80-2 第二項禁止提供「主要用於規避防盜拷措施之設備、器材、零件、 技術或資訊」。注意「資訊」二字——它涵蓋的不只是可執行的程式碼,也涵蓋一份 寫得夠清楚、讀完就能自行實作的規格文件。被移除的那幾份文件正是如此。

同條第三項確實有「為達成資訊間之相互操作性所為之還原工程」的例外,但該例外 一般理解為涵蓋進行還原工程,而非公開發佈規避方法。這是灰色地帶,而 作者不是律師,因此採取保守作法。


保留了什麼(而且它們本身就有用)

留下來的是格式解析那一半:輸入是已經解密的資料,輸出是通用格式。這部分 的性質是互通性工作——把封閉格式轉成開放格式,讓資料能在別的工具裡使用。

hohohololive/
├── catalog.py            octo 目錄解析 (hash / address / size / objectName)
├── extract.py            AssetBundle 抽取,含跨 bundle 的 CAB 依賴解析
├── model3d.py            3D 模型抽取 (mesh / 貼圖 / 骨架 / 材質)
├── gltf.py               Unity 頂點串流 → 有綁定的 glTF 2.0 (含 morph target)
├── motion3d.py           mot_define_* 動作曲線解碼 (CRC32 名稱還原)
├── animglb.py            把動作併進模型 .glb
├── live2d_motion.py      Live2D 動作 → motion3.json (三次 Bezier 轉檔)
├── live2d_expression.py  Live2D 表情 → exp3.json
├── live2d_physics.py     Live2D 物理 → physics3.json
└── cdn.py                以 objectName 從 CDN 取資源

sim_swing.py              離線重現 ActorSwing 彈簧骨(純 numpy/scipy)
compare_swing.py          模擬 vs 遊戲真值的逐幀角度比對
extract_swing.py          從 bundle 抽彈簧骨參數

彈簧骨那三支放在根目錄而不是套件裡,是因為文件(RE_NOTES_AVATAR.md) 大量引用它們的檔名。它們不碰解密,但物理還沒解完——誤差仍略差於 「完全不做」,細節見 ClaudeDocs/HANDOFF.md。收錄未完成的東西是刻意的: 沒解完的過程和解完的一樣值得讀。

CLI 子指令:catalog-parse unpack-octo build-live2d extract-3d extract-motions-3d merge-motion extract-motions extract-expressions

輸入必須是已解密的 AssetBundle。 餵密文進去不會產生垃圾:PlaintextGate 會跳過它、計數,並在指令結束時說明一次。

(它一開始寫成「遇到密文就中止」,那是錯的——這些檢查全在批次迴圈裡, 而 SystemExit 不是 Exception 的子類,外層的 except Exception 攔不住, 一個檔案就會丟掉後面所有位址。跳過並統計才是對的形狀。)

文件

ClaudeDocs/ 是完整的逆向報告,以「每章回答一個問題」的方式寫成,包含走過的 死路與後來被推翻的結論——那些才是重點。

一篇關於這次逆向過程的文章:逆向一個 Unity 6 手遊時,我四次以為自己成功了


免責聲明

授權

MIT — 見 LICENSE