xenonrecomp について
XenonRecompは、Xbox 360の実行ファイルをC++コードに変換するツールで、ゲームの再コンパイルに取り組む開発者向けです。XenonAnalyseでジャンプテーブルを検出できますが、生成コードには別途ランタイムが必要です。
主な機能
- 01Xbox 360実行ファイルをC++に変換
- 02ジャンプテーブルを解析して出力
- 03TOMLで再コンパイルを設定
- 04関数フックとアセンブリ中間フック
- 05任意のレジスター最適化
活用シーン
- 01Xbox 360ゲームからC++コードを生成
- 02XEXファイル内のジャンプテーブルを解析
- 03再コンパイル用の関数境界とフックを設定
設定
ローカルサーバーの設定はどのクライアントでも共通です。コピーしてクライアントの MCP 設定ファイルに貼り付けてください。
このサーバーのパッケージやリモートエンドポイントはまだ取得できていません。プロジェクトの README の手順に従ってください。
ツール
ローカルサーバーのツールはマシン上で起動して初めて取得できます。メンテナーはまだツール一覧を提供していません。
詳細
XenonRecomp
XenonRecomp は、Xbox 360 の実行ファイルを C++ コードに変換し、任意のプラットフォーム向けに再コンパイルできるようにするツールです。
このプロジェクトは、N64 の実行ファイル向けの類似ツールである N64: Recompiled に強く影響を受けています。
免責事項: このプロジェクトはランタイム実装を提供しません。ゲームコードを C++ に変換するだけであり、ランタイムがなければ正しく動作しません。ゲームを動作させるのはユーザーの責任です。
実装の詳細
命令
命令は、逆コンパイルされたコードに似せるための工夫を加えずに直接変換されるため、出力はあまり読みやすくありません。CPU の状態は各 PPC 関数に引数として渡されます。これには、すべての PPC レジスタの定義と実行時の値が含まれます。2 つ目の引数はベースアドレスへのポインターです。Xbox 360 の CPU は 32 ビットポインターを使用するためです。
多くの PPC 命令が実装されています。未実装のものは、主に実装済み命令の派生版です。D3D のアンパック/パック命令など、一部の命令はすべてのオペランド型をサポートしていません。未対応のケースが見つかると、警告が生成されるか、変換後の C++ コードにデバッグブレークが挿入されます。
命令の実装はリトルエンディアン値を扱います。ただし、Xbox 360 はビッグエンディアンのマシンであるため、メモリ読み込み命令は値の読み取り時にエンディアンを入れ替え、メモリ書き込み命令は書き込み前にビッグエンディアンに戻します。Clang による安全でないコードの並べ替えを防ぐため、すべてのメモリ読み込みと書き込みには volatile が指定されています。
ベクトルレジスタのエンディアン処理は、より複雑です。個々の 32 ビット要素を入れ替える代わりに、リコンパイラーはベクトル全体の 16 バイトを反転します。そのため、命令ではこの順序の反転を考慮する必要があります。たとえば、内積では XYZ ではなく WZY の要素を使用し、ベクトルのパック命令では引数を逆順にする必要があります。
FPU は非正規化数を変更せずに保持する必要がありますが、VMX 命令は常に非正規化数をゼロにフラッシュします。これは、CPU 状態構造体に現在の浮動小数点状態を保存し、各命令の実行前に必要に応じて非正規化数のフラッシュを有効または無効にすることで処理されます。
ほとんどの VMX 命令は x86 の組み込み関数を使用して実装されています。ARM64 のサポートには SIMD Everywhere が使用されています。
MMIO
MMIO は、通常 XMA デコードなどのハードウェア操作に使用されますが、現在は未実装です。MMIO を実装する試みは進められていましたが、サポートの実現は容易ではなく、命令の高度な解析が必要になる可能性があります。
間接関数
仮想関数呼び出しは、実行時に「完全ハッシュテーブル」を作成して解決されます。64 ビットポインター(元の命令アドレスを 2 倍した値)を逆参照すると、再コンパイルされた関数のアドレスが得られます。以前は 8 GB の仮想メモリ領域を確保する方法で実装されていましたが、メモリーへの負荷が大きすぎました。現在は、関数アドレスがベースメモリポインター内の有効な XEX メモリ領域より後ろに配置されることを前提としています。これらの領域は、出力される ppc_config.h ファイル内でマクロとしてエクスポートされます。
ジャンプテーブル
少なくとも古い Xbox 360 のバイナリでは、ジャンプテーブルには予測しやすいアセンブリパターンがよく見られるため、仮想マシンを必要とせず、静的に簡単に検出できます。XenonAnalyse には Sonic Unleashed のジャンプテーブルを検出するロジックが含まれていますが、ほかのゲームではパターンが異なる場合があり(Xbox 360 コンパイラーの更新による可能性が高いです)、検出ロジックの変更が必要になることがあります。現在、ジャンプテーブルを処理する完全に汎用的な方法はないため、ほかのゲームでは検出ロジックの更新が必要になる場合があります。
ジャンプテーブルを見つける一般的な方法は、mtctr r0 命令を検索することです。この命令の後にはほぼ必ず bctr が続き、その前の命令でジャンプ先アドレスが計算されています。
XenonAnalyse は、検出したジャンプテーブルを含む TOML ファイルを生成します。このファイルはメインの TOML 設定ファイルから参照できます。これにより、再コンパイラーはこれらのジャンプテーブルに対して実際の switch ケースを生成できます。
関数境界の解析
XenonAnalyse には、ほとんどの場合に適切に機能する関数境界アナライザーが含まれています。スタック領域を持つ関数の境界は、XEX の .pdata セグメントで定義されています。このセグメントに見つからない関数については、アナライザーが分岐リンク命令を検索して関数の開始位置を検出し、静的解析によって関数の長さを判定します。
ただし、ジャンプテーブルを含む関数は、十分な情報がなく末尾呼び出しのように見えるため、アナライザーでの処理が困難です。現在、この問題に対する解決策はありませんが、TOML ファイルで定義されたジャンプテーブルを考慮するように関数アナライザーを拡張することは、比較的簡単かもしれません。回避策として、再コンパイラーの TOML ファイルではユーザーが関数境界を手動で定義できます。
例外
再コンパイラーは現在、例外をサポートしていません。リンクレジスターを使用すること、また例外ハンドラーが任意のコード位置へジャンプできることから、対応は困難です。
setjmp
setjmp と longjmp は、ネイティブ実装へリダイレクトすることで実装されています。Xbox 360 には多数のベクトルレジスターがあるため、ゲスト CPU の状態構造体は x86 CPU の状態を保持できる大きさがあり、ほかのアーキテクチャーの状態も保持できる可能性があります。
最適化
Xbox 360 のバイナリは通常、安定した ABI に従うため、コード構造について一定の前提を置くことで、Clang コンパイラーがより優れたコードを生成できるようになります。再コンパイラーではいくつかの最適化オプションを利用できますが、再コンパイルが正常に動作するようになってから試すことをお勧めします。
ゲームが例外を使用しないと仮定すれば、リンクレジスターを省略できます。再コンパイルのプロセス全体で、関数の戻り動作はすでに処理されているためです。
ゲームが ABI に違反しないと仮定すれば、以下のレジスターは関数のスコープ外に出ることがないため、安全にローカル変数へ変換できます。
- カウントレジスター
- XER
- 予約済みレジスター
- 条件レジスター
- 引数以外のレジスター
- 非揮発性レジスター
ローカル変数の最適化は、特に大きな改善をもたらします。レジスターの復元/保存関数の呼び出しを完全に削除でき、PPC コンテキスト構造体への冗長な書き込みも排除できるためです。Unleashed Recompiled では、これらの最適化によって実行ファイルのサイズが約 20 MB 減少し、フレーム時間が数ミリ秒短縮されます。
パッチの仕組み
XenonRecomp は、Clang コンパイラーの手法を使用して、フックしやすい方法で PPC 関数を定義します。PPC 関数を「実装関数」にエイリアスし、元の関数を弱いリンクとしてマークすることで、元の関数へのアクセスを維持したまま、ユーザーがカスタム実装で上書きできます。
PPC_FUNC_IMPL(__imp__sub_XXXXXXXX);
PPC_FUNC(sub_XXXXXXXX)
{
__imp__sub_XXXXXXXX(ctx, base);
}
また、特定の命令アドレスにミッドアセンブリフックを翻訳後の C++ コードに直接挿入できます。リコンパイラーがこれらの関数呼び出しを挿入し、ユーザーは再コンパイルプロジェクトでそれらを実装する必要があります。コンパイル時にリンカーがそれらを解決します。
使用方法
XenonAnalyse
コマンドラインアプリケーションとして XenonAnalyse を使用すると、XEX ファイルを入力引数として渡し、実行ファイル内で検出されたすべてのジャンプテーブルを含む TOML ファイルを出力できます。
XenonAnalyse [input XEX file path] [output jump table TOML file path]
ただし、前のセクションで説明したように、ゲームによって違いがあるため、さまざまなパターンに対応するには追加のサポートが必要になる場合があります。
ジャンプテーブルの TOML ファイルの例は、Unleashed Recompiled リポジトリで確認できます。
XenonRecomp
XenonRecomp には、リコンパイラーの設定を記述した TOML ファイルと、XenonUtils ディレクトリにある ppc_context.h ファイルへのパスを指定します。
XenonRecomp [input TOML file path] [input PPC context header file path]
リコンパイラー用 TOML ファイルの例は、Unleashed Recompiled リポジトリで確認できます。
Main
[main]
file_path = "../private/default.xex"
patch_file_path = "../private/default.xexp"
patched_file_path = "../private/default_patched.xex"
out_directory_path = "../ppc"
switch_table_file_path = "SWA_switch_tables.toml"
すべてのパスは、TOML ファイルが保存されているディレクトリからの相対パスです。
| プロパティ | 説明 |
|---|---|
| file_path | XEX ファイルへのパスです。 |
| patch_file_path | XEXP ファイルへのパスです。ゲームにタイトルアップデートがない場合は不要です。 |
| patched_file_path | パッチ適用済みの XEX ファイルへのパスです。ファイルが存在しない場合、XenonRecomp は自動的に作成し、その後の再コンパイルで再利用します。XEXP ファイルが指定されていない場合、この設定は機能しません。この出力ファイルを XenonAnalyse に渡すことができます。 |
| out_directory_path | 出力 C++ コードを格納するディレクトリへのパスです。リコンパイラーを実行する前に、このディレクトリを作成しておく必要があります。 |
| switch_table_file_path | ジャンプテーブルの定義を含む TOML ファイルへのパスです。リコンパイラーはこのファイルを使用して、ジャンプテーブルを実際の switch ケースに変換します。 |
最適化
skip_lr = false
skip_msr = false
ctr_as_local = false
xer_as_local = false
reserved_as_local = false
cr_as_local = false
non_argument_as_local = false
non_volatile_as_local = false
ドキュメントの前の方で説明した各種最適化を有効または無効にします。再コンパイルが正常に動作するまでは、これらの最適化を有効にしないことをおすすめします。
レジスターの復元・保存関数
restgprlr_14_address = 0x831B0B40
savegprlr_14_address = 0x831B0AF0
restfpr_14_address = 0x831B144C
savefpr_14_address = 0x831B1400
restvmx_14_address = 0x831B36E8
savevmx_14_address = 0x831B3450
restvmx_64_address = 0x831B377C
savevmx_64_address = 0x831B34E4
Xbox 360 のバイナリには、switch 文の case のフォールスルーと同様に機能する、専用のレジスター復元・保存関数があります。非揮発性レジスターを使用する関数はすべて、これらの関数をインライン化したバージョンを持つか、明示的に呼び出します。リコンパイラーでこれらの関数を正しく再コンパイルするには、TOML ファイルに各復元・保存関数の開始アドレスが必要です。これらの関数は自動検出できる可能性がありますが、現在その仕組みはありません。
| プロパティ | 説明 | バイトパターン |
|---|---|---|
| restgprlr_14_address | __restgprlr_14 関数の開始アドレスです。ld r14, -0x98(r1) で始まり、残りの非揮発性レジスターに対して同じ操作を繰り返し、最後にリンクレジスターを復元します。 | e9 c1 ff 68 |
| savegprlr_14_address | __savegprlr_14 関数の開始アドレスです。std r14, -0x98(r1) で始まり、残りの非揮発性レジスターに対して同じ操作を繰り返し、最後にリンクレジスターを保存します。 | f9 c1 ff 68 |
| restfpr_14_address | __restfpr_14 関数の開始アドレスです。lfd f14, -0x90(r12) で始まり、残りの非揮発性 FPU レジスターに対して同じ操作を繰り返します。 | c9 cc ff 70 |
| savefpr_14_address | __savefpr_14 関数の開始アドレスです。stfd r14, -0x90(r12) で始まり、残りの非揮発性 FPU レジスターに対して同じ操作を繰り返します。 | d9 cc ff 70 |
| restvmx_14_address | __restvmx_14 関数の開始アドレスです。li r11, -0x120 と lvx v14, r11, r12 で始まり、v31 まで残りの非揮発性 VMX レジスターに対して同じ操作を繰り返します。 | 39 60 fe e0 7d cb 60 ce |
| savevmx_14_address | __savevmx_14 関数の開始アドレスです。li r11, -0x120 と stvx v14, r11, r12 で始まり、v31 まで残りの非揮発性 VMX レジスターに対して同じ操作を繰り返します。 | 39 60 fe e0 7d cb 61 ce |
| restvmx_64_address | __restvmx_64 関数の開始アドレスです。li r11, -0x400 と lvx128 v64, r11, r12 で始まり、残りの非揮発性 VMX レジスターに対して同じ操作を繰り返します。 | 39 60 fc 00 10 0b 60 cb |
| savevmx_64_address | __savevmx_64 関数の開始アドレスです。li r11, -0x400 と stvx128 v64, r11, r12 で始まり、残りの非揮発性 VMX レジスターに対して同じ操作を繰り返します。 | 39 60 fc 00 10 0b 61 cb |
longjmp と setjmp
longjmp_address = 0x831B6790
setjmp_address = 0x831B6AB0
これらは、実行ファイル内の longjmp 関数と setjmp 関数のアドレスです。リコンパイラーは、これらの関数をネイティブ版に直接リダイレクトします。これらの関数の実装は、ゲームによって異なる場合があります。場合によっては、RtlUnwind の呼び出しを探すことで longjmp を見つけられます。また、通常、その直後に setjmp があります。
ゲームでこれらの関数を使用していない場合は、TOML ファイルからプロパティを削除できます。
明示的な関数境界
functions = [
{ address = 0x824E7EF0, size = 0x98 },
{ address = 0x824E7F28, size = 0x60 },
]
XenonAnalyse が関数を正しく解析できない場合、たとえばジャンプテーブルを含む関数の場合は、functions プロパティを使用して関数境界を明示的に定義できます。
無効な命令のスキップ
invalid_instructions = [
{ data = 0x00000000, size = 4 }, # Padding
{ data = 0x831B1C90, size = 8 }, # C++ Frame Handler
{ data = 0x8324B3BC, size = 8 }, # C Specific Frame Handler
{ data = 0x831C8B50, size = 8 },
{ data = 0x00485645, size = 44 } # End of .text
]
invalid_instructions プロパティでは、リコンパイラーが特定のバイト列に遭遇したときに、そのバイト列をスキップするよう指示する 32 ビット整数値を定義できます。たとえば、Unleashed Recompiled では、関数間に配置された有効なコードではない例外処理データをスキップするために、これらの値が使用されています。
Mid-asm フック
[[midasm_hook]]
name = "IndexBufferLengthMidAsmHook"
address = 0x82E26244
registers = ["r3"]
void IndexBufferLengthMidAsmHook(PPCRegister& r3)
{
// ...
}
TOML ファイルでは複数の mid-asm フックを定義でき、指定したアドレスにリコンパイラーが関数呼び出しを挿入できます。再コンパイルプロジェクトでフックを実装すると、リンカーが呼び出しを自動的に解決します。
| プロパティ | 説明 |
|---|---|
| name | mid-asm フックの関数名です。同じ実装を複数のアドレスに配置するために、関数名を再利用できます。それ以外の場合、実装ごとに一意の名前が必要です。 |
| address | 関数呼び出しを配置する命令のアドレスです。指定したアドレスにある命令は上書きされません。 |
| registers | mid-asm フックに引数として渡すレジスターです。ローカル変数の最適化では、最適化されたレジスターが PPC コンテキスト構造体内に保持されないため、レジスターのリストを指定します。 |
| return | true に設定すると、フックが挿入された関数は mid-asm フックの呼び出し後、直ちにリターンします。 |
| return_on_true | true に設定すると、mid-asm フックの呼び出しが true を返した場合に関数がリターンします。 |
| return_on_false | true に設定すると、mid-asm フックの呼び出しが false を返した場合に関数がリターンします。 |
| jump_address | mid-asm フックの呼び出し直後にジャンプするアドレスです。このアドレスはフックが配置された関数内でなければなりません。 |
| jump_address_on_true | mid-asm フックが true を返した場合にジャンプするアドレスです。このアドレスはフックが配置された関数内でなければなりません。 |
| jump_address_on_false | mid-asm フックが false を返した場合にジャンプするアドレスです。このアドレスはフックが配置された関数内でなければなりません。 |
| after_instruction | true に設定すると、命令の前ではなく直後に mid-asm フックを配置します。 |
一部のプロパティは相互に排他的です。たとえば、return と jump_address を同時に使用することはできず、直接リターン/ジャンプと条件付きリターン/ジャンプを組み合わせることもできません。これに従わない場合、リコンパイラーが警告を表示します。
テスト
XenonRecomp は Xenia の PPC テストを再コンパイルし、リポジトリ内の XenonTests プロジェクトで実行できます。Xenia のビルドシステムを使ってテストをビルドした後、XenonRecomp の入力として src/xenia/cpu/ppc/testing/bin ディレクトリを処理すると、指定した出力ディレクトリに C++ ファイルが生成されます。
XenonRecomp [input testing directory path] [input PPC context header file path] [output directory path]
ファイルが生成されたら、XenonTests の CMake キャッシュを更新して、ファイルをプロジェクトに反映してください。その後、テストを実行して、命令の結果を期待値と比較できます。
ビルド
ビルドには CMake 3.20 以降と Clang 18 以降が必要です。リポジトリにはサブモジュールが含まれているため、再帰的にクローンしてください。
Clang 以外のコンパイラーはテストされておらず、再コンパイル後の出力に使用する場合も推奨されません。このプロジェクトは、他のコンパイラーでは正しく機能しない可能性のある、コンパイラー固有の組み込み関数や手法に依存しています。また、多くの最適化手法は Clang のコード生成に依存します。
Windows では、clang-cl ツールセットを使用し、Visual Studio の CMake 統合でプロジェクトを開くことができます。
謝辞
このプロジェクトは、Xenia エミュレーターなしには実現できませんでした。CPU コード変換プロセスの多くの部分は、Xenia の PPC コードトランスレーターを大いに参考にして実装されています。また、XEX バイナリーのパッチには Xenia Canary のコードも使用しています。
コメント
AI アプリケーションが外部のツールやデータソースに統一的な方法で接続するためのオープンなプロトコルです。クライアントはモデルとの対話を担い、MCP サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。