mumble-voip_mumble/overlay/d3d9.cpp

1146 lines
34 KiB
C++

// Copyright 2007-2023 The Mumble Developers. All rights reserved.
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file at the root of the
// Mumble source tree or at <https://www.mumble.info/LICENSE>.
#include "lib.h"
#include <d3d9.h>
#include <time.h>
Direct3D9Data *d3dd = nullptr;
typedef IDirect3D9 *(WINAPI *pDirect3DCreate9)(UINT SDKVersion);
typedef HRESULT(WINAPI *pDirect3DCreate9Ex)(UINT SDKVersion, IDirect3D9Ex **ppD3D);
template< class T > class Stash {
public:
Stash(T *variable, T newValue) : var(variable), value(*var) { *variable = newValue; }
~Stash() { *var = value; }
private:
T *var;
T value;
};
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;
/// Non-Win8: Initial ref count, directly after device creation.
/// Win8: Unused
LONG initRefCount;
LONG refCount;
/// Refcount of self (library). Used to make the overlay transparent
/// (invisible) to the outside.
LONG myRefCount;
DWORD dwMyThread;
D3DTLVERTEX vertices[4];
LPDIRECT3DTEXTURE9 texTexture;
clock_t timeT;
unsigned int frameCount;
DevState();
void createCleanState();
void releaseData();
void releaseAll();
void draw();
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);
};
/// Vtable offset; see d3d9.h of win-/D3D-API.
/// 3 from IUnknown + 14 in IDirect3D9, 0-based => 16
static const int VTABLE_OFFSET_ID3D_CREATEDEVICE = 16;
/// Vtable offset; see d3d9.h of win-/D3D-API.
/// Offset: 3 from IUnknown + 14 from IDirect3D9 + 4 in IDirect3D9Ex, 0-based => 20
static const int VTABLE_OFFSET_ID3D_CREATEDEVICE_EX = 20;
typedef map< IDirect3DDevice9 *, DevState * > DevMapType;
static DevMapType devMap;
static bool bHooked = false;
DevState::DevState() {
dev = nullptr;
pSB = nullptr;
dwMyThread = 0;
initRefCount = 0;
refCount = 0;
myRefCount = 0;
texTexture = nullptr;
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 = nullptr;
}
}
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 || uiLeft == uiRight)
return;
D3DLOCKED_RECT lr;
if ((x == 0) && (y == 0) && (w == uiWidth) && (h == uiHeight)) {
if (texTexture->LockRect(0, &lr, nullptr, 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< unsigned char * >(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< float >(uiWidth);
float h = static_cast< float >(uiHeight);
float left = static_cast< float >(uiLeft) - 0.5f;
float top = static_cast< float >(uiTop) - 0.5f;
float right = static_cast< float >(uiRight) + 0.5f;
float bottom = static_cast< float >(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 = nullptr;
}
dev->CreateTexture(uiWidth, uiHeight, 1, D3DUSAGE_DYNAMIC, D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT, &texTexture, nullptr);
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 = nullptr;
}
void DevState::draw() {
clock_t t = clock();
float elapsed = static_cast< float >(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() {
if (dwMyThread != 0) {
ods("D3D9: CreateCleanState from other thread.");
}
Stash< DWORD > stashThread(&dwMyThread, GetCurrentThreadId());
if (pSB)
pSB->Release();
pSB = nullptr;
IDirect3DStateBlock9 *pStateBlock = nullptr;
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(nullptr);
dev->SetPixelShader(nullptr);
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();
}
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;
/// Present the overlay.
///
/// If called via IDirect3DDevice9::present() or IDirect3DDevice9Ex::present(),
/// idd will be non-nullptr and ids ill be nullptr.
///
/// If called via IDirect3DSwapChain9::present(), both idd and ids will be
/// non-nullptr.
///
/// The doPresent function expects the following assumptions to be valid:
///
/// - Only one swap chain used at the same time.
/// - Windowed? IDirect3D9SwapChain::present() is used. ("Additional swap chain" is used)
/// - Full screen? IDirect3D9Device::present() is used. (Implicit swap chain for IDirect3D9Device is used)
///
/// It's either/or.
///
/// If doPresent is called multiple times per frame (say, for different swap chains),
/// the overlay will break badly when DevState::draw() is called. FPS counting will be off,
/// different render targets with different sizes will cause a size-renegotiation with Mumble
/// every frame, etc.
static void doPresent(IDirect3DDevice9 *idd, IDirect3DSwapChain9 *ids) {
DevMapType::iterator it = devMap.find(idd);
DevState *ds = it != devMap.end() ? it->second : nullptr;
HRESULT hres;
if (ds && ds->pSB) {
if (ds->dwMyThread != 0) {
ods("D3D9: doPresent from other thread");
}
Stash< DWORD > stashThread(&(ds->dwMyThread), GetCurrentThreadId());
// Get the back buffer.
// If we're called via IDirect3DSwapChain9, acquire it via the swap chain object.
// Otherwise, acquire it via the device itself.
IDirect3DSurface9 *pTarget = nullptr;
if (ids) {
hres = ids->GetBackBuffer(0, D3DBACKBUFFER_TYPE_MONO, &pTarget);
if (FAILED(hres)) {
if (hres == D3DERR_INVALIDCALL) {
ods("D3D9: IDirect3DSwapChain9::GetBackBuffer failed. BackBuffer index equals or exceeds the total "
"number of back buffers");
} else {
ods("D3D9: IDirect3DSwapChain9::GetBackBuffer failed");
}
}
} else {
hres = idd->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &pTarget);
if (FAILED(hres)) {
if (hres == D3DERR_INVALIDCALL) {
ods("D3D9: IDirect3DDevice9::GetBackBuffer failed. BackBuffer index equals or exceeds the total "
"number of back buffers");
} else {
ods("D3D9: IDirect3DDevice9::GetBackBuffer failed");
}
}
}
IDirect3DSurface9 *pRenderTarget = nullptr;
hres = idd->GetRenderTarget(0, &pRenderTarget);
if (FAILED(hres)) {
if (hres == D3DERR_NOTFOUND) {
ods("D3D9: IDirect3DDevice9::GetRenderTarget failed. There is no render target with the specified "
"index");
} else if (hres == D3DERR_INVALIDCALL) {
ods("D3D9: IDirect3DDevice9::GetRenderTarget failed. One of the passed arguments was invalid");
} else {
ods("D3D9: IDirect3DDevice9::GetRenderTarget failed");
}
}
// 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 = nullptr;
idd->CreateStateBlock(D3DSBT_ALL, &pStateBlock);
pStateBlock->Capture();
ds->pSB->Apply();
if (pTarget != pRenderTarget) {
hres = idd->SetRenderTarget(0, pTarget);
if (FAILED(hres)) {
ods("D3D9: IDirect3DDevice9::SetRenderTarget failed");
}
}
// If we're called via IDirect3DSwapChain9::present(), we have to
// get the size of the back buffer and manually set the viewport size
// to match it.
//
// Although the call to IDirect3DDevice9::SetRenderTarget() above is
// documented as updating the device's viewport:
//
// "Setting a new render target will cause the viewport (see Viewports
// and Clipping (Direct3D 9)) to be set to the full size of the new
// render target."
//
// (via https://msdn.microsoft.com/en-us/library/windows/desktop/bb174455(v=vs.85).aspx)
//
// ...this doesn't happen. At least for some programs such as Final Fantasy XIV
// and Battle.net Launcher. For these programs, we get a viewport of 1x1.
//
// The viewport we set here is used in the call below to DevState::draw()
// as the full size of the screen/window.
if (ids) {
D3DPRESENT_PARAMETERS pp;
hres = ids->GetPresentParameters(&pp);
if (FAILED(hres)) {
ods("D3D9: IDirect3DSwapChain9::GetPresentParameters failed");
} else {
if (pp.BackBufferWidth != 0 && pp.BackBufferHeight != 0) {
D3DVIEWPORT9 vp;
vp.X = 0;
vp.Y = 0;
vp.Width = pp.BackBufferWidth;
vp.Height = pp.BackBufferHeight;
vp.MinZ = 0.0f;
vp.MaxZ = 1.0f;
idd->SetViewport(&vp);
}
}
}
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);
}
}
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 = nullptr;
ids->GetDevice(&idd);
if (idd) {
doPresent(idd, ids);
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, nullptr);
// 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, nullptr);
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");
DevMapType::iterator it = devMap.find(idd);
DevState *ds = it != devMap.end() ? it->second : nullptr;
if (ds) {
if (ds->dwMyThread != 0) {
ods("D3D9: myReset from other thread");
}
Stash< DWORD > stashThread(&(ds->dwMyThread), GetCurrentThreadId());
ds->releaseAll();
}
// 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");
DevMapType::iterator it = devMap.find(idd);
DevState *ds = it != devMap.end() ? it->second : nullptr;
if (ds) {
if (ds->dwMyThread) {
ods("D3D9: myResetEx from other thread");
}
Stash< DWORD > stashThread(&(ds->dwMyThread), GetCurrentThreadId());
ds->releaseAll();
}
// 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;
DevMapType::iterator it = devMap.find(idd);
DevState *ds = it != devMap.end() ? it->second : nullptr;
if (ds) {
// AddRef is called very often. Thus, we do not want to always log here.
#ifdef EXTENDED_OVERLAY_DEBUGOUTPUT
ods("D3D9: Using own Refcount implementation for call to AddRef.");
#endif
if (ds->dwMyThread == GetCurrentThreadId()) {
ds->myRefCount++;
} else {
ds->refCount++;
}
return ds->initRefCount + 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();
// 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;
DevMapType::iterator it = devMap.find(idd);
DevState *ds = it != devMap.end() ? it->second : nullptr;
if (ds && ds->dwMyThread == GetCurrentThreadId()) {
// AddRef is called very often. Thus, we do not want to always log here.
#ifdef EXTENDED_OVERLAY_DEBUGOUTPUT
ods("D3D9: Using own Refcount implementation for call to AddRef (Win8).");
#endif
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;
DevMapType::iterator it = devMap.find(idd);
DevState *ds = it != devMap.end() ? it->second : nullptr;
if (ds) {
// Release is called very often. Thus, we do not want to always log here.
#ifdef EXTENDED_OVERLAY_DEBUGOUTPUT
ods("D3D9: Using own Refcount implementation for call to Release.");
#endif
if (ds->dwMyThread == GetCurrentThreadId()) {
ds->myRefCount--;
return ds->initRefCount + ds->refCount;
} else {
ds->refCount--;
}
if (ds->refCount <= 1) {
ds->disconnect();
}
if (ds->refCount >= 0) {
return ds->initRefCount + ds->refCount;
}
ods("D3D9: Final release is following. MyRefs = %d, Tot = %d", ds->myRefCount, ds->refCount);
if (ds->dwMyThread != 0) {
ods("D3D9: finalRelease from other thread");
}
// Codeblock for stashing threadid
{
Stash< DWORD > stashThread(&(ds->dwMyThread), GetCurrentThreadId());
ds->releaseAll();
}
ods("D3D9: Final release of %p. MyRefs = %d Tot = %d", idd, ds->myRefCount, ds->refCount);
devMap.erase(it);
delete ds;
ds = nullptr;
}
// 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;
DevMapType::iterator it = devMap.find(idd);
DevState *ds = it != devMap.end() ? it->second : nullptr;
if (ds) {
// Release is called very often. Thus, we do not want to always log here.
#ifdef EXTENDED_OVERLAY_DEBUGOUTPUT
ods("D3D9: Using own Refcount implementation for call to Release.");
#endif
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);
if (ds->dwMyThread != 0) {
ods("D3D9: finalRelease from other thread");
}
// Codeblock for stashing threadid
{
Stash< DWORD > stashThread(&(ds->dwMyThread), GetCurrentThreadId());
ds->releaseAll();
}
ods("D3D9: Final release of %p. MyRefs = %d Tot = %d", idd, ds->myRefCount, ds->refCount);
devMap.erase(it);
delete ds;
ds = nullptr;
}
}
// 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;
}
static IDirect3DDevice9 *findOriginalDevice(IDirect3DDevice9 *device) {
IDirect3DSwapChain9 *pSwap = nullptr;
device->GetSwapChain(0, &pSwap);
if (pSwap) {
IDirect3DDevice9 *originalDevice = nullptr;
if (SUCCEEDED(pSwap->GetDevice(&originalDevice))) {
if (originalDevice == device) {
// Found the original device. Release responsibility is passed
// to the caller.
} else {
device = findOriginalDevice(originalDevice);
originalDevice->Release();
}
} else {
ods("D3D9: Failed to recurse to find original device. Could not get Device from Swapchain.");
}
pSwap->Release();
} else {
ods("D3D9: Failed to recurse to find original device. Could not get Swapchain.");
}
return device;
}
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.
IDirect3DDevice9 *originalDevice = findOriginalDevice(idd);
if (idd != originalDevice) {
ods("D3D9: Prepatched device, using original. %p => %p", idd, originalDevice);
idd = originalDevice;
}
DevState *ds = new DevState;
ds->dev = idd;
idd->AddRef();
ds->initRefCount = idd->Release();
DevMapType::iterator it = devMap.find(idd);
if (it != devMap.end()) {
ods("Device exists in devMap already - canceling injection into device. Thread prev: %d ; new: %d",
it->second->dwMyThread, GetCurrentThreadId());
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< voidFunc >(myWin8AddRef));
hhRelease.setupInterface(idd, offsetRelease, reinterpret_cast< voidFunc >(myWin8Release));
} else {
hhAddRef.setupInterface(idd, offsetAddref, reinterpret_cast< voidFunc >(myAddRef));
hhRelease.setupInterface(idd, offsetRelease, reinterpret_cast< voidFunc >(myRelease));
}
hhReset.setupInterface(idd, offsetReset, reinterpret_cast< voidFunc >(myReset));
hhPresent.setupInterface(idd, offsetPresent, reinterpret_cast< voidFunc >(myPresent));
IDirect3DSwapChain9 *pSwap = nullptr;
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< voidFunc >(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();
DevMapType::iterator it = devMap.find(idd);
if (it != devMap.end()) {
ods("Device exists in devMap already - canceling injection into device. Thread prev: %d ; new: %d",
it->second->dwMyThread, GetCurrentThreadId());
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< voidFunc >(myWin8AddRef));
hhRelease.setupInterface(idd, offsetRelease, reinterpret_cast< voidFunc >(myWin8Release));
} else {
hhAddRef.setupInterface(idd, offsetAddref, reinterpret_cast< voidFunc >(myAddRef));
hhRelease.setupInterface(idd, offsetRelease, reinterpret_cast< voidFunc >(myRelease));
}
hhReset.setupInterface(idd, offsetReset, reinterpret_cast< voidFunc >(myReset));
hhResetEx.setupInterface(idd, offsetResetEx, reinterpret_cast< voidFunc >(myResetEx));
hhPresent.setupInterface(idd, offsetPresent, reinterpret_cast< voidFunc >(myPresent));
hhPresentEx.setupInterface(idd, offsetPresentEx, reinterpret_cast< voidFunc >(myPresentEx));
IDirect3DSwapChain9 *pSwap = nullptr;
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< voidFunc >(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< voidFunc >(myCreateDevice));
}
static void HookCreateRawEx(voidFunc vfCreate) {
ods("D3D9: Injecting CreateDeviceEx Raw");
hhCreateDeviceEx.setup(vfCreate, reinterpret_cast< voidFunc >(myCreateDeviceEx));
}
static void HookCreate(IDirect3D9 *pD3D) {
ods("D3D9: Injecting CreateDevice");
hhCreateDevice.setupInterface(pD3D, VTABLE_OFFSET_ID3D_CREATEDEVICE, reinterpret_cast< voidFunc >(myCreateDevice));
}
static void HookCreateEx(IDirect3D9Ex *pD3D) {
ods("D3D9Ex: Injecting CreateDevice / CreateDeviceEx");
// Best effort hooking for the non-ex create method. If it was set up
// previously this setup call is safely ignored by the hook.
HookCreate(pD3D);
hhCreateDeviceEx.setupInterface(pD3D, VTABLE_OFFSET_ID3D_CREATEDEVICE_EX,
reinterpret_cast< voidFunc >(myCreateDeviceEx));
}
static 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) {
if (!bHooked) {
hookD3D9(hD3D, preonly);
}
} else {
#ifdef EXTENDED_OVERLAY_DEBUGOUTPUT
ods("D3D9: No D3D9.DLL found as loaded. No hooking at this point.");
#endif
}
bCheckHookActive = false;
}
static void hookD3D9(HMODULE hD3D, bool preonly) {
char procname[MODULEFILEPATH_BUFLEN];
GetModuleFileName(nullptr, procname, ARRAY_NUM_ELEMENTS(procname));
ods("D3D9: hookD3D9 in App '%s'", procname);
// Add a ref to ourselves; we do NOT want to get unloaded directly from this process.
HMODULE hTempSelf = nullptr;
GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, reinterpret_cast< LPCTSTR >(&hookD3D9), &hTempSelf);
bHooked = true;
// Can we use the prepatch data?
wchar_t modulename[MODULEFILEPATH_BUFLEN];
GetModuleFileNameW(hD3D, modulename, ARRAY_NUM_ELEMENTS(modulename));
if (_wcsicmp(d3dd->wcFileName, modulename) == 0) {
// The module seems to match the one we prepared d3dd for.
unsigned char *raw = (unsigned char *) hD3D;
HookCreateRaw((voidFunc)(raw + d3dd->offsetCreate));
if (d3dd->offsetCreateEx) {
HookCreateRawEx((voidFunc)(raw + d3dd->offsetCreateEx));
}
} else if (!preonly) {
ods("D3D9: Interface changed, can't rawpatch. Current: %ls ; Previously: %ls", modulename, d3dd->wcFileName);
pDirect3DCreate9 d3dcreate9 = reinterpret_cast< pDirect3DCreate9 >(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?");
}
pDirect3DCreate9Ex d3dcreate9ex =
reinterpret_cast< pDirect3DCreate9Ex >(GetProcAddress(hD3D, "Direct3DCreate9Ex"));
if (d3dcreate9ex) {
ods("D3D9: Got %p for Direct3DCreate9Ex", d3dcreate9ex);
IDirect3D9Ex **id3d9ex = 0;
HRESULT hr = d3dcreate9ex(D3D_SDK_VERSION, id3d9ex);
if (SUCCEEDED(hr)) {
HookCreateEx(*id3d9ex);
(*id3d9ex)->Release();
} else {
switch (hr) {
case D3DERR_OUTOFVIDEOMEMORY:
ods("D3D11: Direct3DCreate9Ex returned with out of memory error.");
break;
case D3DERR_NOTAVAILABLE:
ods("D3D11: Direct3DCreate9Ex is not available.");
break;
default:
ods("D3D11: Unexpected return result from Direct3DCreate9Ex");
break;
}
}
}
} else {
bHooked = 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) {
GetModuleFileNameW(hD3D, d3dd->wcFileName, ARRAY_NUM_ELEMENTS(d3dd->wcFileName));
std::string d3d9FnName("Direct3DCreate9");
pDirect3DCreate9 d3dcreate9 = reinterpret_cast< pDirect3DCreate9 >(GetProcAddress(hD3D, d3d9FnName.c_str()));
if (!d3dcreate9) {
ods(("D3D9: Library without " + d3d9FnName).c_str());
} else {
if (!IsFnInModule(reinterpret_cast< voidFunc >(d3dcreate9), d3dd->wcFileName, "D3D9", d3d9FnName)) {
ods(("D3D9: " + d3d9FnName + " is not in D3D9 library").c_str());
} else {
IDirect3D9 *id3d9 = d3dcreate9(D3D_SDK_VERSION);
if (id3d9) {
void ***vtbl = (void ***) id3d9;
void *pCreate = (*vtbl)[VTABLE_OFFSET_ID3D_CREATEDEVICE];
if (!IsFnInModule(reinterpret_cast< voidFunc >(pCreate), d3dd->wcFileName, "D3D9",
"CreateDevice")) {
ods("D3D9: CreateDevice is not in D3D9 library");
} else {
size_t fn = reinterpret_cast< size_t >(pCreate);
size_t base = reinterpret_cast< size_t >(hD3D);
d3dd->offsetCreate = fn - base;
ods("D3D9: Successfully found prepatch offset: %p %p %p: %d", hD3D, d3dcreate9, pCreate,
d3dd->offsetCreate);
}
id3d9->Release();
}
}
}
std::string d3d9exFnName("Direct3DCreate9Ex");
pDirect3DCreate9Ex d3dcreate9ex =
reinterpret_cast< pDirect3DCreate9Ex >(GetProcAddress(hD3D, d3d9exFnName.c_str()));
if (!d3dcreate9ex) {
ods(("D3D9: Library without " + d3d9exFnName).c_str());
} else {
if (!IsFnInModule(reinterpret_cast< voidFunc >(d3dcreate9ex), d3dd->wcFileName, "D3D9", d3d9exFnName)) {
ods(("D3D9: " + d3d9exFnName + " is not in D3D9 library").c_str());
} else {
IDirect3D9Ex *id3d9 = nullptr;
d3dcreate9ex(D3D_SDK_VERSION, &id3d9);
if (id3d9) {
void ***vtbl = (void ***) id3d9;
void *pCreateEx = (*vtbl)[VTABLE_OFFSET_ID3D_CREATEDEVICE_EX];
if (!IsFnInModule(reinterpret_cast< voidFunc >(pCreateEx), d3dd->wcFileName, "D3D9",
"CreateDeviceEx")) {
ods("D3D9: CreateDeviceEx is not in D3D9 library");
} else {
size_t fn = reinterpret_cast< size_t >(pCreateEx);
size_t base = reinterpret_cast< size_t >(hD3D);
d3dd->offsetCreateEx = fn - base;
ods("D3D9: Successfully found prepatch ex offset: %p %p %p: %d", hD3D, d3dcreate9ex, pCreateEx,
d3dd->offsetCreateEx);
}
id3d9->Release();
}
}
}
FreeLibrary(hD3D);
}
}