dwgx@blog:~$dwgx
> cd ../posts

PTTRDLL: 実ゲーム上の DX11 オーバーレイ + IL2CPP フック

// created

PTTR という Unity IL2CPP ゲーム向けに書いたオーバーレイ DLL だ。大義はない。それぞれ少し知っている二つを一本につなぎたかっただけだ。グラフィックス層の Hook と、IL2CPP のリバース。手元に本物のゲームがあったので注入し、画面に ImGui メニューを重ね、プレイヤー状態(体力・バフ・アップグレード・座標)を読み書きした。

見間違えやすい点:GitHub の主言語が C# になっているのは、Data/ に巨大な IL2CPP の dump.cs があり統計が偏るからで、実際にビルドできる本体は C++ だ。

画面側から切る

ゲーム画面に描くいちばんきれいな道は、Present 経路を握ることだ。MinHook で IDXGISwapChain::Present(vtable 8)と ResizeBuffers(13)、さらに D3D11CreateDeviceAndSwapChain を掛ける。

詰まるのは、注入時点ではゲームの swap chain がまだ無く、vtable が取れないことだ。一時的な dummy swap chain を自前で作り、その偽オブジェクトの vtable から関数アドレスを解き、本物にフックする。ここが通れば DX Hook の残りは流れ作業だ。

オーバーレイは Dear ImGui の Win32 + DX11 バックエンド、暗色の ClickGUI。リサイズ時は RTV を作り直さないと画面が死ぬ。表示切替は INSERT、WndProc フック経由。ImGui と MinHook は vendored で一緒にビルドし、外部パッケージマネージャは使わない。

ゲーム論理へ

画面ができたら次は IL2CPP と正面から向き合う。GameAssembly.dllPTTRPlayer の固定 RVA に Update / Awake / OnEnable / OnDestroy / Die / set_health をフックする。Awake / OnEnable でローカルプレイヤーを掴み、Update で常駐 R/W、Dieset_health で起こしたくない状態変化を止める。

座標は一段遠回りする。il2cpp_resolve_icallget_transformget_position_Injected を解き、XYZ をメニューに出す。

いわゆる無敵は核がロック体力で、血量 / ブースト / アップグレード値をスライダーで触り、現在値をリアルタイム表示する。config.h にコンパイル時デフォルト(god ON、血 10000、boost 9999、upgrade 99)、実行時はオーバーレイ側で上書きできる。

踏んだ穴

いちばん高い授業料は野ポインタだ。死んだゲームオブジェクトに読み書きするとホストプロセスが即死する。ゲームメモリへのアクセスはすべて __try/__except で包み、壊れたポインタはログして終わり、ゲームを道連れにしない。追記専用のファイルログで注入初期化と描画各段を残す。ログ無しのこの手の作業は目隠し運転だ。

もう一つの先天的な脆さ:RVA_* は特定ビルドの dump から抜いた値で、ゲームが更新されれば全部死ぬ。だからこれは汎用ツールではなく、個人の玩具のままになる。

いま

個人プロジェクト、ずっと WIP、MIT。あとで忘れたときの DX11 Hook と IL2CPP の手順書として残している。完成度は高くないが、「画面からゲーム論理まで一段ずつ潜る」感触は今でも好きだ。