既存の D3D アプリケーションのレンダリング結果に、 自前のポスト処理/オーバーレイを付加したり、スクリーンショット撮ったりするのはどーやってるの? (FXAA とかああいうの)
逆順に説明すると、以下のようになる:
- →
IDirect3DDevice9::EndScene()
やIDirect3DSwapChain9::Present()
をフックして、レンダリングが終わった後に加工を行う - →
Direct3DCreate9()
を実行した際に、COM インターフェースの vtable を書き換えて、EndScene()
等をフックする - → ニセの d3d9.dll (プロキシDLL) を使って、
Direct3DCreate9
をフックする
ターゲットとなる実行ファイルがあるディレクトリに、プロキシ DLL (自前で用意したニセの d3d9.dll) を配置する。
[通常の DLL 検索ルール][1] の場合、ターゲットの存在するディレクトリから DLL を探した後、 システムディレクトリの DLL を見に行くので、 ターゲットと同一ディレクトリの d3d9.dll が先に読み込まれる。
プロキシ DLL からは、本物の %SystemRoot%\System32\d3d9.dll
内の関数を呼び出す。
%SystemRoot%\System32
は [GetSystemDirectory()
][2] を用いて取得する。
本物の d3d9.dll 内の処理を呼び出す前後で何らかの処理を行うことで、目的を達成する。
[1]: http://msdn.microsoft.com/en-us/library/windows/desktop/ms684175(v=vs.85).aspx
[2]: http://msdn.microsoft.com/en-us/library/windows/desktop/ms724373(v=vs.85).aspx
最小限の、プロキシ DLL の実験
- 下記のコードをコンパイル
- 出力された実行ファイルを d3d9.dll にリネーム
- リネームした d3d9.dll をターゲットの実行ファイルと同じディレクトリに配置
- DebugView を実行
- ターゲットの実行ファイルを起動
- DebugView のウィンドウに
OutputDebugString()
内のメッセージ ("My Injection Test : Direct3DCreate9"
) が出る
ターゲットによっては出ないかも。全然ダメな場合は D3D9 のチュートリアル等で試すこと。
#define _CRT_SECURE_NO_WARNINGS
#include <d3d9.h>
#include <tchar.h>
#pragma comment(linker, "/DLL")
static IDirect3D9* (WINAPI *o_Direct3DCreate9)(UINT);
IDirect3D9* WINAPI Direct3DCreate9(UINT SDKVersion) {
#pragma comment(linker, "/EXPORT:"__FUNCTION__"="__FUNCDNAME__)
OutputDebugString(_T("My Injection Test : Direct3DCreate9\n"));
IDirect3D9 *iDirect3D9 = o_Direct3DCreate9(SDKVersion);
return iDirect3D9;
}
BOOL APIENTRY DllMain(HINSTANCE, DWORD fdwReason, LPVOID) {
if(fdwReason == DLL_PROCESS_ATTACH) {
TCHAR fn[MAX_PATH];
UINT l = GetSystemDirectory(fn, _countof(fn));
_tcscpy(fn + l, _T("\\d3d9.dll"));
HMODULE hModule = LoadLibrary(fn);
* (void**) &o_Direct3DCreate9 = GetProcAddress(hModule, "Direct3DCreate9");
}
return TRUE;
}
以下では、C++ コードから CINTERFACE
を使うので、d3dx9core.h の記述ミスを修正する。
d3dx9core.h の中で
DECLARE_INTERFACE_(ID3DXFont, IUnknown)
{
...
#ifdef __cplusplus
となっているものを
DECLARE_INTERFACE_(ID3DXFont, IUnknown)
{
...
#if defined(__cplusplus) && !defined(CINTERFACE)
と修正する。
Direct3DCreate9()
が返す IDirect3D9
の vtable を加工することで、IDirect3D9::CreateDevice()
をフックする。
d3d9.h を #include
する前に、シンボル CINTERFACE を定義しておくと、COM のインターフェースがより扱いやすい形で見えるようになる。
具体的には、以下のような定義が行われる
struct IDirect3D {
struct IDirect3DVtbl* lpVtbl;
};
struct IDirect3DVtbl {
HRESULT (STDMETHODCALLTYPE* QueryInterface)(...);
...
};
ここで、IDirect3D::lpVtbl
が指す構造体がターゲットの vtable となる。
CINTERFACE
のまま d3d9.h を用いても良いが、通常の C++ インターフェースが使えずに冗長になるので、下記のコードでは細工を行っている。
細工の結果として、namespace MyInterface
の場合は CINTERFACE
版の IDirect3D
にアクセスできるようになる。
あとは MyInterface::IDirect3D::lpVtbl
が指す vtable を書き換えれば良いのだが、
このメモリ領域は保護指定が PAGE_EXECUTE_READ
(0x0020) になっているので、
まず書き換え可能 PAGE_EXECUTE_WRITECOPY
(0x0080) に変更し、書き換え後、元に戻す必要がある。
#define _CRT_SECURE_NO_WARNINGS
#include <d3d9.h>
#include <tchar.h>
#pragma comment(linker, "/DLL")
namespace MyInterface {
#pragma warning(push)
#define CINTERFACE // Windows SDK : BaseTypes.h
#pragma warning(disable : 4005)
#define STDMETHOD_(type,method) type (STDMETHODCALLTYPE * method)
#define STDMETHOD(method) STDMETHOD_(HRESULT,method)
#define PURE
#define THIS INTERFACE FAR* This
#define THIS_ THIS,
#define DECLARE_INTERFACE(iface) typedef interface iface { struct iface##Vtbl FAR* lpVtbl; } iface; \
typedef struct iface##Vtbl iface##Vtbl; struct iface##Vtbl
#define DECLARE_INTERFACE_(iface,b) DECLARE_INTERFACE(iface)
#undef _D3D9_H_
#include <d3d9.h>
#pragma warning(pop)
} // namespace MyInterface
static IDirect3D9* (WINAPI *o_Direct3DCreate9)(UINT);
static HRESULT (WINAPI *o_IDirect3D9_CreateDevice)(IDirect3D9*, UINT, D3DDEVTYPE, HWND, DWORD, D3DPRESENT_PARAMETERS*, IDirect3DDevice9**);
static HRESULT WINAPI my_IDirect3D9_CreateDevice(IDirect3D9* direct3d, UINT adapter, D3DDEVTYPE type, HWND window, DWORD flag, D3DPRESENT_PARAMETERS* param, IDirect3DDevice9** ppDevice) {
HRESULT hr = o_IDirect3D9_CreateDevice(direct3d, adapter, type, window, flag, param, ppDevice);
if(SUCCEEDED(hr)) {
OutputDebugString(_T("My Injection Test : IDirect3D9::CreateDevice - SUCCEEDED\n"));
}
return hr;
}
IDirect3D9* WINAPI Direct3DCreate9(UINT SDKVersion) {
#pragma comment(linker, "/EXPORT:"__FUNCTION__"="__FUNCDNAME__)
IDirect3D9 *iDirect3D9 = o_Direct3DCreate9(SDKVersion);
if(iDirect3D9) {
OutputDebugString(_T("My Injection Test : d3d9.dll::Direct3DCreate9 - SUCCEEDED\n"));
MyInterface::IDirect3D9Vtbl* p = ((MyInterface::IDirect3D9*) iDirect3D9)->lpVtbl;
DWORD protect = PAGE_EXECUTE_WRITECOPY;
VirtualProtect(p, sizeof(*p), protect, &protect);
* (void**) &o_IDirect3D9_CreateDevice = (void*) p->CreateDevice;
* (void**) &p->CreateDevice = (void*) my_IDirect3D9_CreateDevice;
VirtualProtect(p, sizeof(*p), protect, &protect);
}
return iDirect3D9;
}
BOOL APIENTRY DllMain(HINSTANCE, DWORD fdwReason, LPVOID) {
if(fdwReason == DLL_PROCESS_ATTACH) {
TCHAR fn[MAX_PATH];
_tcscpy(fn + GetSystemDirectory(fn, _countof(fn)), _T("\\d3d9.dll"));
HMODULE hModule = LoadLibrary(fn);
* (void**) &o_Direct3DCreate9 = GetProcAddress(hModule, "Direct3DCreate9");
}
return TRUE;
}