DirectX Tweaker Public Beta

spin でおもしろいツールが紹介されていました.

nonatainmentが、「DirectX Tweaker」のパブリックベータ版をこちらで公開しています。DirectX Tweakerは、このツールから起動したDirect3D使用アプリケーションソフトのDirect3Dのラッパとして働き、アプリのDirect3Dに関する情報を得たり、Direct3Dの設定を動的に変更したりできる大変興味深いツールです。各種機能は、プラグイン形式で提供されます。現在、提供されているプラグインには、例えば、次のようなものがあります

  • GetDeviceCaps()でアプリ側が得るCaps情報を自由に設定できるCaps Changer
  • テクスチャフェッチのフィルタモードを強制的に設定するAF Control
  • バックバッファやZバッファのフォーマット/サイズ/数、マルチサンプリングAAのモードなどを強制的に設定するPresent Changer
  • シェーダのバージョン毎に強制的に色を塗り分けるShader Display
  • 描画フレーム毎のCPU時刻、描画オブジェクト数(描画コール数?)、描画頂点数をテキストファイルに出力するStatistic
  • カラーフォーマット毎にテクスチャを指定の色で塗り分けるTexture Format Display
  • テクスチャのサイズ毎にテクスチャを指定の色で塗り分けるTexture Size Display
  • ワイヤフレーム描画に強制的に設定するWireframe
  • ピクセルシェーダの全命令の演算精度を部分精度(_pp/16bit)に強制的に設定するForce Shader PP
  • ピクセルシェーダの全命令の演算精度をフル精度(32bit/24bit)に強制的に設定するForce Shader FP
  • nonatainment独自のアルゴリズムでアルファテストの部分にも有効なFSAA処理を行うAlpha Test AA

ざっと見たところ,「DirectX Tweaker」は .NET で書かれた設定ツールと,ネイティブコードによる DirectX Graphics のフック機構からなるようです.プラグインについても実際のフックロジックに関係するものは Win32 DLL で,設定ツールの UI に関係するものは .NET Assembly で作成するようでした.
DebugBreak() を仕掛けたテストプログラムを DirectX Tweaker に食わせて実行時の挙動をデバッガで眺めてみましたが,どうやら「PIX」と同じタイプのフックを行っているようです.WinMain に入った段階で d3d9.dll がロードされていて,かつDirect3DCreate9 の先頭数命令*1 が書き換えられていることが確認できました.恐らく IDirect3D9 のプロキシオブジェクトを返すようなコードに飛ばされているのでしょう*2
ちなみに DLL が公開する関数のフックと言えば他にも Proxy DLL を利用する方法やインポートセクション書き換える方法もありますが,これらは絶対パスで LoadLibrary を呼び出すタイプのアプリケーションに対してはうまく機能しません.実際最近の SDK に付属するサンプルフレームワークでは,LoadLibrary によって d3d9.dll を絶対パスで読み込んでいるため,以下のような但し書きが追加されています.

現在のサンプルはデバッグ ユーティリティ D3DSpy と一緒には動作しません。\windows\system32 から D3D9.dll を直接ロードするアプリケーションはどれも動作しないでしょう。

一方「PIX」や「DirectX Tweaker」のプログラムローダは,ターゲットの WinMain 開始前に,%WinDir%\System32\d3d9.dll のロードと Direct3DCreate9 の書き換えを行っています.後でアプリケーションが絶対パス指定で LoadLibrary を呼び出しても,実際には DLL の参照カウントが増えるのみで依然として書き換えられた Direct3DCreate9 は有効なまま,という仕組みなんでしょう.
Web で検索してみたところ,アプローチとしては『Advanced Windows 改訂第4版(asin:4756138055)』22.8「CreateProcessを使ったコードの注入」あたりに詳しくまとまっていそうですね.実は未読なんですけど……折角だし買っておくか……

*1:GetProcAddress が返すアドレスの方です.d3d9.dll (Ver. 5.03.2600.2180) の rebasing が行われていなければ 0x4B68AED0.

*2:以後はさらに IDirect3D9::CreateDevice が IDirect3DDevice9 のプロキシオブジェクトを返すのでしょう.