Skip to content

Instantly share code, notes, and snippets.

@t-mat
Created December 30, 2011 15:08
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save t-mat/1540248 to your computer and use it in GitHub Desktop.
Save t-mat/1540248 to your computer and use it in GitHub Desktop.
D3D9プロキシDLLの作り方

知りたいこと

既存の D3D アプリケーションのレンダリング結果に、 自前のポスト処理/オーバーレイを付加したり、スクリーンショット撮ったりするのはどーやってるの? (FXAA とかああいうの)

逆順に説明すると、以下のようになる:

  • IDirect3DDevice9::EndScene()IDirect3DSwapChain9::Present() をフックして、レンダリングが終わった後に加工を行う
  • Direct3DCreate9() を実行した際に、COM インターフェースの vtable を書き換えて、EndScene() 等をフックする
  • → ニセの d3d9.dll (プロキシDLL) を使って、Direct3DCreate9 をフックする

プロキシ DLL によるフックの仕組み

ターゲットとなる実行ファイルがあるディレクトリに、プロキシ 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

最小限の、プロキシ 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;
}

d3dx9core.hを修正

以下では、C++ コードから CINTERFACE を使うので、d3dx9core.h の記述ミスを修正する。 d3dx9core.h の中で

DECLARE_INTERFACE_(ID3DXFont, IUnknown)
{
...
#ifdef __cplusplus

となっているものを

DECLARE_INTERFACE_(ID3DXFont, IUnknown)
{
...
#if defined(__cplusplus) && !defined(CINTERFACE)

と修正する。

IDirect3D9::CreateDevice()をフック

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;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment