/* Copyright (C) 2005-2011, Thorvald Natvig All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - Neither the name of the Mumble Developers nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "lib.h" #include #include Direct3D9Data *d3dd = NULL; typedef IDirect3D9* (WINAPI *pDirect3DCreate9)(UINT SDKVersion) ; typedef HRESULT (WINAPI *pDirect3DCreate9Ex)(UINT SDKVersion, IDirect3D9Ex **ppD3D) ; struct D3DTLVERTEX { float x, y, z, rhw; // Position float tu, tv; // Texture coordinates }; static const DWORD D3DFVF_TLVERTEX = D3DFVF_XYZRHW | D3DFVF_TEX1; class DevState : public Pipe { public: IDirect3DDevice9 *dev; IDirect3DStateBlock9 *pSB; LONG initRefCount; LONG refCount; // Thread-specific threadcount LONG myRefCount; DWORD dwMyThread; D3DTLVERTEX vertices[4]; LPDIRECT3DTEXTURE9 texTexture; clock_t timeT; unsigned int frameCount; DevState(); void createCleanState(); void releaseData(); void releaseAll(); void draw(); void postDraw(); virtual void blit(unsigned int x, unsigned int y, unsigned int w, unsigned int h); virtual void setRect(); virtual void newTexture(unsigned int width, unsigned int height); }; static map devMap; static bool bHooked = false; DevState::DevState() { dev = NULL; pSB = NULL; dwMyThread = 0; initRefCount = 0; refCount = 0; myRefCount = 0; texTexture = NULL; timeT = clock(); frameCount = 0; for (int i=0;i<4;++i) { vertices[i].x = vertices[i].y = 0.0f; vertices[i].tu = vertices[i].tv = 0.0f; vertices[i].z = vertices[i].rhw = 1.0f; } } void DevState::releaseData() { ods("D3D9: Release Data"); if (texTexture) { texTexture->Release(); texTexture = NULL; } } void DevState::blit(unsigned int x, unsigned int y, unsigned int w, unsigned int h) { // Blit is called often. Thus, we do not want to always log here. #ifdef EXTENDED_OVERLAY_DEBUGOUTPUT ods("D3D9: Blit %d %d %d %d", x, y, w, h); #endif if (! texTexture || !a_ucTexture) return; D3DLOCKED_RECT lr; if ((x == 0) && (y == 0) && (w == uiWidth) && (h == uiHeight)) { if (texTexture->LockRect(0, &lr, NULL, D3DLOCK_DISCARD) != D3D_OK) return; } else { RECT r; r.left = x; r.top = y; r.right = x + w; r.bottom = y + h; if (texTexture->LockRect(0, &lr, &r, 0) != D3D_OK) return; } for (unsigned int r=0;r < h;++r) { unsigned char *dptr = reinterpret_cast(lr.pBits) + r * lr.Pitch; unsigned char *sptr = a_ucTexture + 4 * ((y + r) * uiWidth + x); memcpy(dptr, sptr, w * 4); } texTexture->UnlockRect(0); } void DevState::setRect() { ods("D3D9: New subrect"); float w = static_cast(uiWidth); float h = static_cast(uiHeight); float left = static_cast(uiLeft) - 0.5f; float top = static_cast(uiTop) - 0.5f; float right = static_cast(uiRight) + 0.5f; float bottom = static_cast(uiBottom) + 0.5f; float texl = (left) / w; float text = (top) / h; float texr = (right + 1.0f) / w; float texb = (bottom + 1.0f) / h; vertices[0].x = left; vertices[0].y = top; vertices[0].tu = texl; vertices[0].tv = text; vertices[1].x = right; vertices[1].y = top; vertices[1].tu = texr; vertices[1].tv = text; vertices[2].x = right; vertices[2].y = bottom; vertices[2].tu = texr; vertices[2].tv = texb; vertices[3].x = left; vertices[3].y = bottom; vertices[3].tu = texl; vertices[3].tv = texb; } void DevState::newTexture(unsigned int width, unsigned int height) { ods("D3D9: New texture %d x %d", width, height); if (texTexture) { texTexture->Release(); texTexture = NULL; } dev->CreateTexture(uiWidth, uiHeight, 1, D3DUSAGE_DYNAMIC, D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT, &texTexture, NULL); for (int i=0;i<4;++i) { vertices[i].x = vertices[i].y = vertices[i].z = 0.0f; vertices[i].tu = vertices[i].tv = 0.0f; vertices[i].rhw = 1.0f; } } void DevState::releaseAll() { ods("D3D9: Release All"); releaseData(); if (pSB) pSB->Release(); pSB = NULL; } void DevState::draw() { clock_t t = clock(); float elapsed = static_cast(t - timeT) / CLOCKS_PER_SEC; ++frameCount; if (elapsed > OVERLAY_FPS_INTERVAL) { OverlayMsg om; om.omh.uiMagic = OVERLAY_MAGIC_NUMBER; om.omh.uiType = OVERLAY_MSGTYPE_FPS; om.omh.iLength = sizeof(OverlayMsgFps); om.omf.fps = frameCount / elapsed; sendMessage(om); frameCount = 0; timeT = t; } D3DVIEWPORT9 vp; dev->GetViewport(&vp); checkMessage(vp.Width, vp.Height); if (! a_ucTexture || (uiLeft == uiRight)) return; if (! texTexture) { unsigned int l, r, t, b; l = uiLeft; r = uiRight; t = uiTop; b = uiBottom; newTexture(uiWidth, uiHeight); blit(0, 0, uiWidth, uiHeight); uiLeft = l; uiRight = r; uiTop = t; uiBottom = b; setRect(); } dev->SetTexture(0, texTexture); dev->DrawPrimitiveUP(D3DPT_TRIANGLEFAN, 2, vertices, sizeof(D3DTLVERTEX)); } void DevState::createCleanState() { DWORD dwOldThread = dwMyThread; if (dwOldThread) { ods("D3D9: CreateCleanState from other thread."); } dwMyThread = GetCurrentThreadId(); if (pSB) pSB->Release(); pSB = NULL; IDirect3DStateBlock9* pStateBlock = NULL; dev->CreateStateBlock(D3DSBT_ALL, &pStateBlock); if (! pStateBlock) return; pStateBlock->Capture(); dev->CreateStateBlock(D3DSBT_ALL, &pSB); if (! pSB) { pStateBlock->Release(); return; } D3DVIEWPORT9 vp; dev->GetViewport(&vp); dev->SetVertexShader(NULL); dev->SetPixelShader(NULL); dev->SetFVF(D3DFVF_TLVERTEX); dev->SetRenderState(D3DRS_FILLMODE, D3DFILL_SOLID); dev->SetRenderState(D3DRS_SHADEMODE, D3DSHADE_GOURAUD); dev->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE); // 0x16 dev->SetRenderState(D3DRS_WRAP0, FALSE); // 0x80 dev->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE); dev->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_ONE); dev->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA); dev->SetRenderState(D3DRS_ALPHATESTENABLE, TRUE); dev->SetRenderState(D3DRS_ALPHAFUNC, D3DCMP_GREATER); dev->SetRenderState(D3DRS_ZENABLE, FALSE); dev->SetRenderState(D3DRS_ZWRITEENABLE, FALSE); dev->SetRenderState(D3DRS_ZFUNC, D3DCMP_ALWAYS); dev->SetRenderState(D3DRS_COLORVERTEX, FALSE); dev->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_MODULATE); dev->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE); dev->SetTextureStageState(0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE); dev->SetRenderState(D3DRS_LIGHTING, FALSE); pSB->Capture(); pStateBlock->Apply(); pStateBlock->Release(); dwMyThread = dwOldThread; } static HardHook hhCreateDevice; static HardHook hhCreateDeviceEx; static HardHook hhReset; static HardHook hhResetEx; static HardHook hhAddRef; static HardHook hhRelease; static HardHook hhPresent; static HardHook hhPresentEx; static HardHook hhSwapPresent; static void doPresent(IDirect3DDevice9 *idd) { DevState *ds = devMap[idd]; if (ds && ds->pSB) { DWORD dwOldThread = ds->dwMyThread; if (dwOldThread) ods("D3D9: doPresent from other thread"); ds->dwMyThread = GetCurrentThreadId(); IDirect3DSurface9 *pTarget = NULL; IDirect3DSurface9 *pRenderTarget = NULL; idd->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &pTarget); idd->GetRenderTarget(0, &pRenderTarget); // Present is called for each frame. Thus, we do not want to always log here. #ifdef EXTENDED_OVERLAY_DEBUGOUTPUT ods("D3D9: doPresent BackB %p RenderT %p", pTarget, pRenderTarget); #endif IDirect3DStateBlock9* pStateBlock = NULL; idd->CreateStateBlock(D3DSBT_ALL, &pStateBlock); pStateBlock->Capture(); ds->pSB->Apply(); if (pTarget != pRenderTarget) idd->SetRenderTarget(0, pTarget); idd->BeginScene(); ds->draw(); idd->EndScene(); pStateBlock->Apply(); pStateBlock->Release(); pRenderTarget->Release(); pTarget->Release(); // ods("D3D9: Finished ref is %d %d", ds->myRefCount, ds->refCount); ds->dwMyThread = dwOldThread; } } typedef HRESULT(__stdcall *SwapPresentType)(IDirect3DSwapChain9 *, CONST RECT *, CONST RECT *, HWND, CONST RGNDATA *, DWORD); static HRESULT __stdcall mySwapPresent(IDirect3DSwapChain9 * ids, CONST RECT *pSourceRect, CONST RECT *pDestRect, HWND hDestWindowOverride, CONST RGNDATA *pDirtyRegion, DWORD dwFlags) { // Present is called for each frame. Thus, we do not want to always log here. #ifdef EXTENDED_OVERLAY_DEBUGOUTPUT ods("D3D9: SwapChain Present"); #endif IDirect3DDevice9 *idd = NULL; ids->GetDevice(&idd); if (idd) { doPresent(idd); idd->Release(); } //TODO: Move logic to HardHook. // Call base without active hook in case of no trampoline. SwapPresentType oSwapPresent = (SwapPresentType) hhSwapPresent.call; hhSwapPresent.restore(); HRESULT hr = oSwapPresent(ids, pSourceRect,pDestRect,hDestWindowOverride,pDirtyRegion,dwFlags); hhSwapPresent.inject(); return hr; } typedef HRESULT(__stdcall *PresentType)(IDirect3DDevice9 *, CONST RECT *, CONST RECT *, HWND, CONST RGNDATA *); static HRESULT __stdcall myPresent(IDirect3DDevice9 * idd, CONST RECT* pSourceRect,CONST RECT* pDestRect,HWND hDestWindowOverride,CONST RGNDATA* pDirtyRegion) { // Present is called for each frame. Thus, we do not want to always log here. #ifdef EXTENDED_OVERLAY_DEBUGOUTPUT ods("D3D9: Device Present"); #endif doPresent(idd); //TODO: Move logic to HardHook. // Call base without active hook in case of no trampoline. PresentType oPresent = (PresentType) hhPresent.call; hhPresent.restore(); HRESULT hr = oPresent(idd,pSourceRect,pDestRect,hDestWindowOverride,pDirtyRegion); hhPresent.inject(); return hr; } typedef HRESULT(__stdcall *PresentExType)(IDirect3DDevice9Ex *, CONST RECT *, CONST RECT *, HWND, CONST RGNDATA *, DWORD); static HRESULT __stdcall myPresentEx(IDirect3DDevice9Ex * idd, CONST RECT* pSourceRect, CONST RECT* pDestRect, HWND hDestWindowOverride, CONST RGNDATA* pDirtyRegion, DWORD dwFlags) { // Present is called for each frame. Thus, we do not want to always log here. #ifdef EXTENDED_OVERLAY_DEBUGOUTPUT ods("D3D9: Device Present Ex"); #endif doPresent(idd); PresentExType oPresentEx = (PresentExType) hhPresentEx.call; hhPresentEx.restore(); HRESULT hr = oPresentEx(idd, pSourceRect, pDestRect, hDestWindowOverride, pDirtyRegion, dwFlags); hhPresentEx.inject(); return hr; } typedef HRESULT(__stdcall *ResetType)(IDirect3DDevice9 *, D3DPRESENT_PARAMETERS *); static HRESULT __stdcall myReset(IDirect3DDevice9 * idd, D3DPRESENT_PARAMETERS *param) { ods("D3D9: Chaining Reset"); DevState *ds = devMap[idd]; if (ds) { DWORD dwOldThread = ds->dwMyThread; if (dwOldThread) ods("D3D9: myReset from other thread"); ds->dwMyThread = GetCurrentThreadId(); ds->releaseAll(); ds->dwMyThread = dwOldThread; } //TODO: Move logic to HardHook. // Call base without active hook in case of no trampoline. ResetType oReset = (ResetType) hhReset.call; hhReset.restore(); HRESULT hr = oReset(idd, param); hhReset.inject(); if (ds) ds->createCleanState(); return hr; } typedef HRESULT(__stdcall *ResetExType)(IDirect3DDevice9Ex *, D3DPRESENT_PARAMETERS *, D3DDISPLAYMODEEX *); static HRESULT __stdcall myResetEx(IDirect3DDevice9Ex * idd, D3DPRESENT_PARAMETERS *param, D3DDISPLAYMODEEX * param2) { ods("D3D9: Chaining ResetEx"); DevState *ds = devMap[idd]; if (ds) { DWORD dwOldThread = ds->dwMyThread; if (dwOldThread) ods("D3D9: myResetEx from other thread"); ds->dwMyThread = GetCurrentThreadId(); ds->releaseAll(); ds->dwMyThread = dwOldThread; } //TODO: Move logic to HardHook. // Call base without active hook in case of no trampoline. ResetExType oResetEx = (ResetExType) hhResetEx.call; hhResetEx.restore(); HRESULT hr = oResetEx(idd, param, param2); hhResetEx.inject(); if (ds) ds->createCleanState(); return hr; } typedef ULONG(__stdcall *AddRefType)(IDirect3DDevice9 *); static ULONG __stdcall myAddRef(IDirect3DDevice9 *idd) { Mutex m; // AddRef is called very often. Thus, we do not want to always log here. #ifdef EXTENDED_OVERLAY_DEBUGOUTPUT ods("D3D9: Chaining AddRef"); #endif DevState *ds = devMap[idd]; if (ds) { if (ds->dwMyThread == GetCurrentThreadId()) { ds->myRefCount++; } else { ds->refCount++; } return ds->refCount + ds->initRefCount; } //TODO: Move logic to HardHook. // Call base without active hook in case of no trampoline. AddRefType oAddRef = (AddRefType) hhAddRef.call; hhAddRef.restore(); ULONG res = oAddRef(idd); hhAddRef.inject(); // AddRef is called very often. Thus, we do not want to always log here. #ifdef EXTENDED_OVERLAY_DEBUGOUTPUT ods("D3D9: Chained AddRef with result %d", res); #endif return res; } static ULONG __stdcall myWin8AddRef(IDirect3DDevice9 *idd) { Mutex m; // AddRef is called very often. Thus, we do not want to always log here. #ifdef EXTENDED_OVERLAY_DEBUGOUTPUT ods("D3D9: Chaining AddRef (Win8)"); #endif DevState *ds = devMap[idd]; if (ds && ds->dwMyThread == GetCurrentThreadId()) { ds->myRefCount++; return ds->refCount; } //TODO: Move logic to HardHook. // Call base without active hook in case of no trampoline. AddRefType oAddRef = (AddRefType) hhAddRef.call; hhAddRef.restore(); ULONG res = oAddRef(idd); hhAddRef.inject(); if (ds) ds->refCount = res; // AddRef is called very often. Thus, we do not want to always log here. #ifdef EXTENDED_OVERLAY_DEBUGOUTPUT ods("D3D9: Chained AddRef (Win8) with result %d", res); #endif return res; } typedef ULONG(__stdcall *ReleaseType)(IDirect3DDevice9 *); static ULONG __stdcall myRelease(IDirect3DDevice9 *idd) { Mutex m; // Release is called very often. Thus, we do not want to always log here. #ifdef EXTENDED_OVERLAY_DEBUGOUTPUT ods("D3D9: Chaining Release"); #endif DevState *ds = devMap[idd]; if (ds) { if (ds->dwMyThread == GetCurrentThreadId()) { ds->myRefCount--; return ds->refCount + ds->initRefCount; } else { ds->refCount--; } if (ds->refCount <= 1) { ds->disconnect(); } if (ds->refCount >= 0) return ds->refCount + ds->initRefCount; ods("D3D9: Final release is following. MyRefs = %d, Tot = %d", ds->myRefCount, ds->refCount); DWORD dwOldThread = ds->dwMyThread; if (dwOldThread) ods("D3D9: finalRelease from other thread"); ds->dwMyThread = GetCurrentThreadId(); ds->releaseAll(); ds->dwMyThread = dwOldThread; ods("D3D9: Final release. MyRefs = %d Tot = %d", ds->myRefCount, ds->refCount); devMap.erase(idd); delete ds; } //TODO: Move logic to HardHook. // Call base without active hook in case of no trampoline. ReleaseType oRelease = (ReleaseType) hhRelease.call; hhRelease.restore(); ULONG res = oRelease(idd); hhRelease.inject(); // Release is called very often. Thus, we do not want to always log here. #ifdef EXTENDED_OVERLAY_DEBUGOUTPUT ods("D3D9: Chained Release with result %d", res); #endif return res; } static ULONG __stdcall myWin8Release(IDirect3DDevice9 *idd) { Mutex m; // Release is called very often. Thus, we do not want to always log here. #ifdef EXTENDED_OVERLAY_DEBUGOUTPUT ods("D3D9: Chaining Release (Win8)"); #endif DevState *ds = devMap[idd]; if (ds) { if (ds->dwMyThread == GetCurrentThreadId()) { ds->myRefCount--; return ds->refCount; } if (ds->refCount == 1) { ds->disconnect(); ods("D3D9: Final release. MyRefs = %d, Tot = %d", ds->myRefCount, ds->refCount); DWORD dwOldThread = ds->dwMyThread; if (dwOldThread) ods("D3D9: finalRelease from other thread"); ds->dwMyThread = GetCurrentThreadId(); ds->releaseAll(); ds->dwMyThread = dwOldThread; ods("D3D9: Final release, MyRefs = %d Tot = %d", ds->myRefCount, ds->refCount); devMap.erase(idd); delete ds; ds = NULL; } } //TODO: Move logic to HardHook. // Call base without active hook in case of no trampoline. ReleaseType oRelease = (ReleaseType) hhRelease.call; hhRelease.restore(); ULONG res = oRelease(idd); hhRelease.inject(); if (ds) ds->refCount = res; // Release is called very often. Thus, we do not want to always log here. #ifdef EXTENDED_OVERLAY_DEBUGOUTPUT ods("D3D9: Chained Release (Win8) with result: %d", res); #endif return res; } typedef HRESULT(__stdcall *CreateDeviceType)(IDirect3D9 *, UINT, D3DDEVTYPE, HWND, DWORD, D3DPRESENT_PARAMETERS *, IDirect3DDevice9 **); static HRESULT __stdcall myCreateDevice(IDirect3D9 * id3d, UINT Adapter, D3DDEVTYPE DeviceType, HWND hFocusWindow, DWORD BehaviorFlags, D3DPRESENT_PARAMETERS *pPresentationParameters, IDirect3DDevice9 **ppReturnedDeviceInterface) { Mutex m; ods("D3D9: Chaining CreateDevice"); // BehaviorFlags &= ~D3DCREATE_PUREDEVICE; //TODO: Move logic to HardHook. // Call base without active hook in case of no trampoline. CreateDeviceType oCreateDevice = (CreateDeviceType) hhCreateDevice.call; hhCreateDevice.restore(); HRESULT hr = oCreateDevice(id3d, Adapter, DeviceType, hFocusWindow, BehaviorFlags, pPresentationParameters, ppReturnedDeviceInterface); hhCreateDevice.inject(); if (FAILED(hr)) return hr; IDirect3DDevice9 *idd = *ppReturnedDeviceInterface; // Get real interface, please. bool bfound = false; do { bfound = false; IDirect3DSwapChain9 *pSwap = NULL; idd->GetSwapChain(0, &pSwap); if (pSwap) { IDirect3DDevice9 *idorig = NULL; if (SUCCEEDED(pSwap->GetDevice(&idorig))) { if (idorig != idd) { ods("D3D9: Prepatched device, using original. %p => %p", idorig, idd); if (idd != *ppReturnedDeviceInterface) idd->Release(); idd = idorig; bfound = true; } else { idorig->Release(); } } pSwap->Release(); } } while (bfound); DevState *ds = new DevState; ds->dev = idd; idd->AddRef(); ds->initRefCount = idd->Release(); if (devMap.find(idd) != devMap.end()) { ods("Device exists in devMap already - canceling injection into device"); delete ds; return hr; } devMap[idd] = ds; // The offsets are dependent on the declaration order of the struct. // See IDirect3DDevice9 (2nd, 3rd, 17th, 18th functions) const unsigned int offsetAddref = 1; const unsigned int offsetRelease = 2; const unsigned int offsetReset = 16; const unsigned int offsetPresent = 17; if (bIsWin8) { hhAddRef.setupInterface(idd, offsetAddref, reinterpret_cast(myWin8AddRef)); hhRelease.setupInterface(idd, offsetRelease, reinterpret_cast(myWin8Release)); } else { hhAddRef.setupInterface(idd, offsetAddref, reinterpret_cast(myAddRef)); hhRelease.setupInterface(idd, offsetRelease, reinterpret_cast(myRelease)); } hhReset.setupInterface(idd, offsetReset, reinterpret_cast(myReset)); hhPresent.setupInterface(idd, offsetPresent, reinterpret_cast(myPresent)); IDirect3DSwapChain9 *pSwap = NULL; idd->GetSwapChain(0, &pSwap); if (pSwap) { // The offset is dependent on the declaration order of the struct. // See IDirect3DSwapChain9 (Present is the fourth function) const unsigned int offsetPresent = 3; hhSwapPresent.setupInterface(pSwap, offsetPresent, reinterpret_cast(mySwapPresent)); pSwap->Release(); } else { ods("D3D9: Failed to get swapchain"); } ds->createCleanState(); return hr; } typedef HRESULT(__stdcall *CreateDeviceExType)(IDirect3D9Ex *, UINT, D3DDEVTYPE, HWND, DWORD, D3DPRESENT_PARAMETERS *, D3DDISPLAYMODEEX *, IDirect3DDevice9Ex **); static HRESULT __stdcall myCreateDeviceEx(IDirect3D9Ex * id3d, UINT Adapter, D3DDEVTYPE DeviceType, HWND hFocusWindow, DWORD BehaviorFlags, D3DPRESENT_PARAMETERS *pPresentationParameters, D3DDISPLAYMODEEX* pFullscreenDisplayMode, IDirect3DDevice9Ex** ppReturnedDeviceInterface) { Mutex m; ods("D3D9: Chaining CreateDeviceEx"); // BehaviorFlags &= ~D3DCREATE_PUREDEVICE; //TODO: Move logic to HardHook. // Call base without active hook in case of no trampoline. CreateDeviceExType oCreateDeviceEx = (CreateDeviceExType) hhCreateDeviceEx.call; hhCreateDeviceEx.restore(); HRESULT hr = oCreateDeviceEx(id3d, Adapter, DeviceType, hFocusWindow, BehaviorFlags, pPresentationParameters, pFullscreenDisplayMode, ppReturnedDeviceInterface); hhCreateDeviceEx.inject(); if (FAILED(hr)) return hr; IDirect3DDevice9Ex *idd = *ppReturnedDeviceInterface; DevState *ds = new DevState; ds->dev = idd; idd->AddRef(); ds->initRefCount = idd->Release(); if (devMap.find(idd) != devMap.end()) { ods("Device exists in devMap already - canceling injection into device"); delete ds; return hr; } devMap[idd] = ds; // The offsets are dependent on the declaration order of the struct. // See IDirect3DDevice9 (2nd, 3rd, 17th, 18th functions) const unsigned int offsetAddref = 1; const unsigned int offsetRelease = 2; const unsigned int offsetReset = 16; const unsigned int offsetPresent = 17; // On IDirect3DDevice9Ex const unsigned int offsetPresentEx = 121; const unsigned int offsetResetEx = 132; if (bIsWin8) { hhAddRef.setupInterface(idd, offsetAddref, reinterpret_cast(myWin8AddRef)); hhRelease.setupInterface(idd, offsetRelease, reinterpret_cast(myWin8Release)); } else { hhAddRef.setupInterface(idd, offsetAddref, reinterpret_cast(myAddRef)); hhRelease.setupInterface(idd, offsetRelease, reinterpret_cast(myRelease)); } hhReset.setupInterface(idd, offsetReset, reinterpret_cast(myReset)); hhResetEx.setupInterface(idd, offsetResetEx, reinterpret_cast(myResetEx)); hhPresent.setupInterface(idd, offsetPresent, reinterpret_cast(myPresent)); hhPresentEx.setupInterface(idd, offsetPresentEx, reinterpret_cast(myPresentEx)); IDirect3DSwapChain9 *pSwap = NULL; idd->GetSwapChain(0, &pSwap); if (pSwap) { // The offset is dependent on the declaration order of the struct. // See IDirect3DSwapChain9 (Present is the fourth function) const unsigned int offsetPresent = 3; hhSwapPresent.setupInterface(pSwap, offsetPresent, reinterpret_cast(mySwapPresent)); pSwap->Release(); } else { ods("D3D9: Failed to get swapchain for DevEx"); } ds->createCleanState(); return hr; } static void HookCreateRaw(voidFunc vfCreate) { ods("D3D9: Injecting CreateDevice Raw"); hhCreateDevice.setup(vfCreate, reinterpret_cast(myCreateDevice)); } static void HookCreateRawEx(voidFunc vfCreate) { ods("D3D9: Injecting CreateDeviceEx Raw"); hhCreateDeviceEx.setup(vfCreate, reinterpret_cast(myCreateDeviceEx)); } static void HookCreate(IDirect3D9 *pD3D) { ods("D3D9: Injecting CreateDevice"); hhCreateDevice.setupInterface(pD3D, 16, reinterpret_cast(myCreateDevice)); } static void HookCreateEx(IDirect3D9Ex *pD3D) { ods("D3D9Ex: Injecting CreateDevice / CreateDeviceEx"); hhCreateDevice.setupInterface(pD3D, 16, reinterpret_cast(myCreateDevice)); hhCreateDeviceEx.setupInterface(pD3D, 20, reinterpret_cast(myCreateDeviceEx)); } void hookD3D9(HMODULE hD3D, bool preonly); // @param preonly If rawpatching the createdevice-functions fails, don't try to // patch Direct3DCreate9. // Should be true on PROC_ATTACH, and false on THREAD_ATTACH. (?) void checkD3D9Hook(bool preonly) { static bool bCheckHookActive = false; if (bCheckHookActive) { ods("D3D9: Recursion in checkD3D9Hook"); return; } bCheckHookActive = true; HMODULE hD3D = GetModuleHandle("D3D9.DLL"); if (hD3D != NULL) { if (! bHooked) { hookD3D9(hD3D, preonly); } } bCheckHookActive = false; } void hookD3D9(HMODULE hD3D, bool preonly) { const int procnamesize = 2048; char procname[procnamesize]; GetModuleFileName(NULL, procname, procnamesize); ods("D3D9: hookD3D9 in App %s", procname); // Add a ref to ourselves; we do NOT want to get unloaded directly from this process. GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, reinterpret_cast(&HookCreate), &hSelf); bHooked = true; // Can we use the prepatch data? GetModuleFileName(hD3D, procname, procnamesize); if (_stricmp(d3dd->cFileName, procname) == 0) { // The module seems to match the one we prepared d3dd for. unsigned char *raw = (unsigned char *) hD3D; HookCreateRaw((voidFunc)(raw + d3dd->iOffsetCreate)); if (d3dd->iOffsetCreateEx) HookCreateRawEx((voidFunc)(raw + d3dd->iOffsetCreateEx)); } else if (! preonly) { ods("D3D9: Interface changed, can't rawpatch."); pDirect3DCreate9 d3dcreate9 = reinterpret_cast(GetProcAddress(hD3D, "Direct3DCreate9")); if (d3dcreate9) { ods("D3D9: Got %p for Direct3DCreate9", d3dcreate9); IDirect3D9 *id3d9 = d3dcreate9(D3D_SDK_VERSION); if (id3d9) { HookCreate(id3d9); id3d9->Release(); } else { ods("D3D9: Failed call to Direct3DCreate9"); } } else { ods("D3D9: Library without Direct3DCreate9?"); } //TODO: hook for Direct3DCreate9Ex // pDirect3DCreate9Ex d3dcreate9ex = reinterpret_cast(GetProcAddress(hD3D, "Direct3DCreate9Ex")); } else { bHooked = false; } } void freeD3D9Hook(HMODULE hModule) { HMODULE hD3D = GetModuleHandle("D3D9.DLL"); if (bHooked && !hD3D) { ods("D3D9: Freeing hooks for module %p", hModule); hhCreateDevice.reset(); hhCreateDeviceEx.reset(); hhReset.reset(); hhResetEx.reset(); hhAddRef.reset(); hhRelease.reset(); hhPresent.reset(); hhPresentEx.reset(); hhSwapPresent.reset(); bHooked = false; } } // Checks if the module of the fnptr equals the name/path of the one saved in @global d3dd. bool IsFnInModule(char* refmodulepath, const char* fnptr, const std::string & textindicator) { char modulename[2048]; // A handle to the module. HMODULE hRef = NULL; bool success = GetModuleHandleEx( GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, fnptr, &hRef); if (!success) { ods(("D3D9: Failed to get module for " + textindicator).c_str()); } else { GetModuleFileName(hRef, modulename, 2048); return _stricmp(refmodulepath, modulename) == 0; } return false; } extern "C" __declspec(dllexport) void __cdecl PrepareD3D9() { if (! d3dd) return; ods("D3D9: Preparing static data for D3D9 Injection"); HMODULE hD3D = LoadLibrary("D3D9.DLL"); if (hD3D != NULL) { GetModuleFileName(hD3D, d3dd->cFileName, 2048); std::string d3d9FnName("Direct3DCreate9"); pDirect3DCreate9 d3dcreate9 = reinterpret_cast(GetProcAddress(hD3D, d3d9FnName.c_str())); if (! d3dcreate9) { ods(("D3D9: Library without " + d3d9FnName).c_str()); } else { if (!IsFnInModule(d3dd->cFileName, (const char*)d3dcreate9, "D3D9")) { ods(("D3D9: " + d3d9FnName + " is not in D3D9 library").c_str()); } else { IDirect3D9 *id3d9 = d3dcreate9(D3D_SDK_VERSION); if (id3d9) { void ***vtbl = (void ***) id3d9; // vtable offset: CreateDevice is 17th method (0 based 16th) // in IDirect3D9. See d3d9.h of win-/D3D-API. void *pCreate = (*vtbl)[16]; if (!IsFnInModule(d3dd->cFileName, (const char*)pCreate, "CreateDevice")) { ods("D3D9: CreateDevice is not in D3D9 library"); } else { unsigned char *b = (unsigned char *) pCreate; unsigned char *a = (unsigned char *) hD3D; d3dd->iOffsetCreate = b-a; ods("D3D9: Successfully found prepatch offset: %p %p %p: %d", hD3D, d3dcreate9, pCreate, d3dd->iOffsetCreate); } id3d9->Release(); } } } std::string d3d9exFnName("Direct3DCreate9Ex"); pDirect3DCreate9Ex d3dcreate9ex = reinterpret_cast(GetProcAddress(hD3D, d3d9exFnName.c_str())); if (! d3dcreate9ex) { ods(("D3D9: Library without " + d3d9exFnName).c_str()); } else { if (!IsFnInModule(d3dd->cFileName, (const char*)d3dcreate9ex, "D3D9")) { ods(("D3D9: " + d3d9exFnName + " is not in D3D9 library").c_str()); } else { IDirect3D9Ex *id3d9 = NULL; d3dcreate9ex(D3D_SDK_VERSION, &id3d9); if (id3d9) { void ***vtbl = (void ***) id3d9; // vtable offset: CreateDeviceEx is 20th method (0 based 19th) // in IDirect3D9Ex as declared in d3d9.h of win-/D3D-API, // but is actually the 21th. TODO: How come? // CreateDeviceEx defines one less method before-hand than // CreateDevice. Maybe that one comes in anyway? void *pCreateEx = (*vtbl)[20]; if (!IsFnInModule(d3dd->cFileName, (const char*)pCreateEx, "CreateDeviceEx")) { ods("D3D9: CreateDeviceEx is not in D3D9 library"); } else { unsigned char *b = (unsigned char *) pCreateEx; unsigned char *a = (unsigned char *) hD3D; d3dd->iOffsetCreateEx = b-a; ods("D3D9: Successfully found prepatch ex offset: %p %p %p: %d", hD3D, d3dcreate9ex, pCreateEx, d3dd->iOffsetCreateEx); } id3d9->Release(); } } } FreeLibrary(hD3D); } }