世紀末マージ
なんだかんだで神ゲー(異論は認める)、世紀末非想天則がモノになりつつある(悪い意味で)ので2人が違ったアプローチをとっている現在の状態を修正するべく動きます。
プロセスへの侵入方法はシステムディレクトリにあるのと同名のDLLをカレントディレクトリに置く方式にします。th123.exeが"d3d9.dll"を要求しますので、自作のd3d9.dllを置いてロードさせます。th123.exeがインポートしているのはDirect3DCreate9関数1つだけです。よってこちらも同名の関数を作り、エクスポートします。
関数をフックするということで、本来のDirect3DCreate9は必要なわけで、システムディレクトリにある本物のd3d9.dllをロードしておきます。まあSWRSToysの真似なんですけどね。
void attach() { char path[MAX_PATH]; ::GetSystemDirectory(path, MAX_PATH); ::PathAppend(path, "d3d9.dll"); hOrigDll = LoadLibrary(path); if (hOrigDll == NULL) throw std::runtime_error(string(path) + "を開けません。"); g_origDirect3DCreate9 = (Direct3DCreate9Func)GetProcAddress(hOrigDll, "Direct3DCreate9"); if (g_origDirect3DCreate9 == NULL) throw std::runtime_error("Direct3DCreate9関数が見つかりません。"); }
これを用いてDirect3DCreate9を書きます。
// .defでDLLExport extern "C" IDirect3D9 * WINAPI Direct3DCreate9(UINT SDKVersion) { log << "Direct3DCreate9(UINT) called." << std::endl; IDirect3D9 *origD3d9; origD3d9 = g_origDirect3DCreate9(SDKVersion); return new Hokuto3D9(origD3d9); }
__declspec(dllexport)をつけるとすでにd3d9.hで宣言されているシグニチャと違うと言われて詰んだので.defファイルでエクスポートすることにしました(詰んだっていわねー)。
Hokuto3D9クラスはIDirect3D9インターフェースを継承し、コンストラクタで受け取った本物に委譲しまくります。d3d9.hのインターフェース宣言をコピり、ひたすら委譲コードを書きまくります。というのはスミスくんがやってくれたコードを使いました。いじるのはCreateDeviceのみ。ちなみにDirectXのよくある初期化コードで頂点処理等についてハードウェアからソフトウェアへ試し、初めに成功したものを使うというのがありますが、スミスくんのコードのままだとオリジナルのCreateDeviceで失敗したときメモリリークしていました(new Hokuto3DDevice9が放置される)。ログ出力で気付いた。ファインプレイ(???)。
STDMETHOD(CreateDevice)( THIS_ UINT Adapter,D3DDEVTYPE DeviceType,HWND hFocusWindow, DWORD BehaviorFlags, D3DPRESENT_PARAMETERS* pPresentationParameters, IDirect3DDevice9** ppReturnedDeviceInterface) { log << "Hokuto3D9::CreateDevice called." << std::endl; *ppReturnedDeviceInterface = NULL; HRESULT res = m_d3d9->CreateDevice(Adapter, DeviceType, hFocusWindow, BehaviorFlags, pPresentationParameters, ppReturnedDeviceInterface); if (SUCCEEDED(res)) { log << "IDirect3D9::CreateDevice succeeded." << std::endl; *ppReturnedDeviceInterface = new Hokuto3DDevice9(*ppReturnedDeviceInterface); } else { log << "IDirect3D9::CreateDevice failed." << std::endl; } return res; }
スミスくんのコードからは
- IDirect3D9→DummyDirect3D→Hokuto3D9
- IDirect3DDevice9→DummyDevice→Hokuto3DDevice9
のように(ファイル名も)リネームしました。もはやダミーなどという名前はふさわしくない。
他、C/C++ランタイムをstatic linkにしました。nkmr氏のPCに入れたときに問題になったし、これからは部室と自分のPCにとどまらない可能性が出てきたからね。また、まだ書いていませんが七星ゲージ等のテクスチャをD3DXCreateTextureFromFileで読むことになりますが、自分のDirectX SDKのd3dx9.libとリンクするのはやめます。th123.exeはd3dx9_33.dllとリンクしており、この中のを使えば十分だからです(最近のはd3dx_40付近だったと思うけど、それがないと動かないという危険性が増えるだけ)。方法は、GetModuleHandleで既にロード済みのモジュールを得て、GetProcAddressで関数名から関数ポインタを取得します。LoadLibraryを第一に考えましたが、こちらは既にロードされていれば参照カウントを増やし、まだならばロードするもので、同じことはできますがFreeLibraryする手間が増えます。
// PFNCは適切な関数ポインタ HMODULE hModule = GetModuleHandle("d3dx9_33.dll"); PFNC = (PFNC)GetProcAddress(hModule, "D3DXCreateTextureFromFileA");
全体を通して、こういうことをする場合はDependency Walker(depends.exe)とかは持っていた方がいいだろうなあ。
全体を改善しながら2つのプロジェクトをマージということで、自然にスミスくんのコード修正のような記事になって心苦しいです。非想天則を神バランス(異論は認める)にした部分を書いたのはスミスくんで、とりあえずこんな感じ的な成分を含んだあのコードはまさに神と言えるでしょう。