妖々夢 decomp を fork して分解して遊ぶ
はっきり言っておく。これは俺が書いたものでも、俺が逆解析したものでもない。上海アリス幻樂団『東方妖々夢 ~ Perfect Cherry Blossom』PCB 1.00b を C++ に反編譯したプロジェクトで、原作は ZUN。純粋に中を開けて見たくて fork した。ついでに自分で動くやつをビルドしてみるのも目的だ。
昔のゲームの逆向にはずっと執着がある。PCB は 2003 年のもので、MSVC 7 でコンパイルされた Win32 の弾幕 exe。バイナリは小さいが、五臓六腑は全部揃っている。固定小数点演算、スクリプトシステム、リソースのパッキング、描画ループ、全部が一つの exe に詰め込まれている。一人で書き上げた完結したゲームは学習に最適で、現代のエンジニアリングみたいな抽象や依存の山がない。コールチェーンを頭から追っていけばそのまま理解できる。
しかもこの decomp は先人の肩の上に立っている。紅魔郷 th06 にはとっくに成熟した反編譯があって、妖々夢とほぼ同じ型から彫り出したようなものだ。型、命名、ファイル分割、そのまま持ってきて参考にできるので、当てずっぽうの手間が大幅に省ける。
今の実装度は 100%、精度は 97% くらい。頭から最後まで遊べて、挙動もだいたい一致するが、まだ byte accuracy には達していなくて、たまにバグが出る。面白いのは、完全マッチのビルドは長く走らせると 3999 回の Supervisor サイクルの後に自分で崩れる。チェックサムがまだ原版とまったく同じではなくて、完全性チェックを通らないからだ。長く走らせたければ --no-matching でそれを切るしかない。あえて原版の自壊機構を残してある。ここが matching decomp の面白いところで、目標は「動けばいい」ではなく、ビルドしたバイトが 2003 年のあれと寸分違わないことだ。
ツールチェーンは少しレトロだ。uv で Python を管理し、ninja でビルドし、Linux では MSVC を走らせるのに wine も要る。最初の落とし穴がそこにある。古い wine は MSVC の msi の解凍が完全に壊れていて、どうやっても抽出できない。最新の devel 版に換えてやっと通る。ビルドのエントリはルートディレクトリのスクリプト一つで、そのまま:
uv run scripts/build.py
成果物は build/th07.exe。完全性チェックに邪魔されず長く走らせたければ --no-matching を付ける。原版 exe が手元になくて(アイコン抽出に要る)--no-icon を付ければ、アイコンなしのやつもビルドできる。
俺にとっての価値は妖々夢をもう一周することじゃない。他人が一塊の機械語を、名前と構造のある C++ にどう還元していくかを見ることだ。中では EstexNT から MSVC 7 に移した var_order pragma を大量に使って、ローカル変数のスタック上の配置順序を制御している。バイトマッチのためにここまで細かくコンパイラを操るような手は、普段のコーディングでは使いようがないが、逆向では命綱だ。作者自身も Todo で「これは片付いていないゴミ溜めで、matching もまだ本格的に始まっていない」とこぼしている。俺みたいに野次馬半分・学習半分の人間には、むしろちょうどいい。混乱に紛れて、還元の最も生々しい痕跡が見える。
リポジトリはここ: https://github.com/dwgx/th07。深く掘りたいなら、紅魔郷のあの th06 こそが源流で、突き合わせて読むのが一番実りが大きい。