世紀末マージ

なんだかんだで神ゲー(異論は認める)、世紀末非想天則がモノになりつつある(悪い意味で)ので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つのプロジェクトをマージということで、自然にスミスくんのコード修正のような記事になって心苦しいです。非想天則を神バランス(異論は認める)にした部分を書いたのはスミスくんで、とりあえずこんな感じ的な成分を含んだあのコードはまさに神と言えるでしょう。