1
0
mirror of https://github.com/narzoul/DDrawCompat synced 2024-12-30 08:55:36 +01:00

Revamping GDI interworking - Part 1

Previous method of GDI interworking does not seem feasible anymore as it is
leaking GDI resources in more complex scenarios (StarCraft). There does not
seem to be a way to prevent all leaks as the ReleaseDC hook does not capture
all released DCs.

New method redirects the individual GDI drawing methods instead by replacing
DCs only temporarily for each operation. Currently only BitBlt is supported
(which seems sufficient for Deadlock 2).

Also, writing to unlocked video surface memory no longer works on Windows 10.
To work around this restriction, the primary surface is temporarily locked
for the duration of each GDI rendering operation.
This commit is contained in:
narzoul 2016-01-04 23:37:58 +01:00
parent 2dc8d4f13b
commit 5a30b072ba
15 changed files with 425 additions and 569 deletions

View File

@ -4,7 +4,7 @@
#include "CompatDirectDraw.h"
#include "CompatDirectDrawSurface.h"
#include "CompatGdiSurface.h"
#include "CompatGdi.h"
#include "CompatPrimarySurface.h"
#include "DDrawProcs.h"
#include "IReleaseNotifier.h"
@ -603,7 +603,7 @@ HRESULT STDMETHODCALLTYPE CompatDirectDrawSurface<TSurface>::Restore(TSurface* T
result = RealPrimarySurface::restore();
if (wasLost)
{
CompatGdiSurface::release();
CompatGdi::releaseSurfaceMemory();
updateSurfaceParams();
}
}
@ -691,7 +691,7 @@ void CompatDirectDrawSurface<TSurface>::updateSurfaceParams()
if (SUCCEEDED(s_origVtable.Lock(s_compatPrimarySurface, nullptr, &desc, DDLOCK_WAIT, nullptr)))
{
s_origVtable.Unlock(s_compatPrimarySurface, nullptr);
CompatGdiSurface::setSurfaceMemory(desc.lpSurface, desc.lPitch);
CompatGdi::setSurfaceMemory(desc.lpSurface, desc.lPitch);
}
g_lockingPrimary = false;
}

47
DDrawCompat/CompatGdi.cpp Normal file
View File

@ -0,0 +1,47 @@
#include "CompatGdi.h"
#include "CompatGdiDcCache.h"
#include "CompatGdiFunctions.h"
namespace
{
CRITICAL_SECTION g_gdiCriticalSection;
}
namespace CompatGdi
{
GdiScopedThreadLock::GdiScopedThreadLock()
{
EnterCriticalSection(&g_gdiCriticalSection);
}
GdiScopedThreadLock::~GdiScopedThreadLock()
{
LeaveCriticalSection(&g_gdiCriticalSection);
}
void installHooks()
{
InitializeCriticalSection(&g_gdiCriticalSection);
if (CompatGdiDcCache::init())
{
CompatGdiFunctions::hookGdiFunctions();
}
}
void releaseSurfaceMemory()
{
GdiScopedThreadLock gdiLock;
CompatGdiDcCache::release();
}
void setSurfaceMemory(void* surfaceMemory, int pitch)
{
GdiScopedThreadLock gdiLock;
const bool wasReleased = CompatGdiDcCache::isReleased();
CompatGdiDcCache::setSurfaceMemory(surfaceMemory, pitch);
if (wasReleased)
{
InvalidateRect(nullptr, nullptr, TRUE);
}
}
}

15
DDrawCompat/CompatGdi.h Normal file
View File

@ -0,0 +1,15 @@
#pragma once
namespace CompatGdi
{
class GdiScopedThreadLock
{
public:
GdiScopedThreadLock();
~GdiScopedThreadLock();
};
void installHooks();
void releaseSurfaceMemory();
void setSurfaceMemory(void* surfaceMemory, int pitch);
};

View File

@ -0,0 +1,74 @@
#include "CompatGdiDc.h"
#include "CompatGdiDcCache.h"
#include "RealPrimarySurface.h"
namespace
{
struct ExcludeClipRectsData
{
HDC compatDc;
POINT origin;
HWND rootWnd;
};
BOOL CALLBACK excludeClipRectsForOverlappingWindows(HWND hwnd, LPARAM lParam)
{
auto excludeClipRectsData = reinterpret_cast<ExcludeClipRectsData*>(lParam);
if (hwnd == excludeClipRectsData->rootWnd)
{
return FALSE;
}
RECT rect = {};
GetWindowRect(hwnd, &rect);
OffsetRect(&rect, -excludeClipRectsData->origin.x, -excludeClipRectsData->origin.y);
ExcludeClipRect(excludeClipRectsData->compatDc, rect.left, rect.top, rect.right, rect.bottom);
return TRUE;
}
}
namespace CompatGdiDc
{
CachedDc getDc(HDC origDc)
{
CachedDc cachedDc = {};
if (!origDc || !RealPrimarySurface::isFullScreen() ||
OBJ_DC != GetObjectType(origDc) ||
DT_RASDISPLAY != GetDeviceCaps(origDc, TECHNOLOGY))
{
return cachedDc;
}
cachedDc = CompatGdiDcCache::getDc();
if (!cachedDc.dc)
{
return cachedDc;
}
HWND hwnd = WindowFromDC(origDc);
if (hwnd)
{
POINT origin = {};
GetDCOrgEx(origDc, &origin);
SetWindowOrgEx(cachedDc.dc, -origin.x, -origin.y, nullptr);
HRGN clipRgn = CreateRectRgn(0, 0, 0, 0);
GetRandomRgn(origDc, clipRgn, SYSRGN);
SelectClipRgn(cachedDc.dc, clipRgn);
RECT r = {};
GetRgnBox(clipRgn, &r);
DeleteObject(clipRgn);
ExcludeClipRectsData excludeClipRectsData = { cachedDc.dc, origin, GetAncestor(hwnd, GA_ROOT) };
EnumThreadWindows(GetCurrentThreadId(), &excludeClipRectsForOverlappingWindows,
reinterpret_cast<LPARAM>(&excludeClipRectsData));
}
return cachedDc;
}
void releaseDc(const CachedDc& cachedDc)
{
CompatGdiDcCache::releaseDc(cachedDc);
}
}

15
DDrawCompat/CompatGdiDc.h Normal file
View File

@ -0,0 +1,15 @@
#pragma once
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include "CompatGdiDcCache.h"
namespace CompatGdiDc
{
using CompatGdiDcCache::CachedDc;
CachedDc getDc(HDC origDc);
void releaseDc(const CachedDc& cachedDc);
}

View File

@ -22,27 +22,26 @@ namespace CompatGdiDcCache
namespace
{
using CompatGdiDcCache::SurfaceMemoryDesc;
using CompatGdiDcCache::CompatDc;
using CompatGdiDcCache::CachedDc;
std::map<SurfaceMemoryDesc, std::vector<CompatDc>> g_compatDcCaches;
std::vector<CompatDc>* g_currentCompatDcCache = nullptr;
std::map<SurfaceMemoryDesc, std::vector<CachedDc>> g_caches;
std::vector<CachedDc>* g_currentCache = nullptr;
DWORD g_cacheId = 0;
IDirectDraw7* g_directDraw = nullptr;
void* g_surfaceMemory = nullptr;
LONG g_pitch = 0;
IDirectDrawSurface7* createGdiSurface();
void releaseCompatDc(CompatDc compatDc);
void releaseCompatDcCache(std::vector<CompatDc>& compatDcCache);
void releaseCachedDc(CachedDc cachedDc);
void releaseCache(std::vector<CachedDc>& cache);
void clearAllCaches()
{
for (auto& compatDcCache : g_compatDcCaches)
for (auto& cache : g_caches)
{
releaseCompatDcCache(compatDcCache.second);
releaseCache(cache.second);
}
g_compatDcCaches.clear();
g_caches.clear();
}
IDirectDraw7* createDirectDraw()
@ -65,14 +64,14 @@ namespace
return dd;
}
CompatDc createCompatDc()
CachedDc createCachedDc()
{
CompatDc compatDc = {};
CachedDc cachedDc = {};
IDirectDrawSurface7* surface = createGdiSurface();
if (!surface)
{
return compatDc;
return cachedDc;
}
HDC dc = nullptr;
@ -81,18 +80,17 @@ namespace
{
LOG_ONCE("Failed to create a GDI DC: " << result);
surface->lpVtbl->Release(surface);
return compatDc;
return cachedDc;
}
// Release DD critical section acquired by IDirectDrawSurface7::GetDC to avoid deadlocks
Compat::origProcs.ReleaseDDThreadLock();
compatDc.cacheId = g_cacheId;
compatDc.surfaceMemoryDesc.surfaceMemory = g_surfaceMemory;
compatDc.surfaceMemoryDesc.pitch = g_pitch;
compatDc.dc = dc;
compatDc.surface = surface;
return compatDc;
cachedDc.surfaceMemoryDesc.surfaceMemory = g_surfaceMemory;
cachedDc.surfaceMemoryDesc.pitch = g_pitch;
cachedDc.dc = dc;
cachedDc.surface = surface;
return cachedDc;
}
IDirectDrawSurface7* createGdiSurface()
@ -131,66 +129,65 @@ namespace
{
for (DWORD i = 0; i < Config::gdiDcCacheSize; ++i)
{
CompatDc compatDc = createCompatDc();
if (!compatDc.dc)
CachedDc cachedDc = createCachedDc();
if (!cachedDc.dc)
{
return;
}
g_currentCompatDcCache->push_back(compatDc);
g_currentCache->push_back(cachedDc);
}
}
void releaseCompatDc(CompatDc compatDc)
void releaseCachedDc(CachedDc cachedDc)
{
// Reacquire DD critical section that was temporarily released after IDirectDrawSurface7::GetDC
Compat::origProcs.AcquireDDThreadLock();
if (FAILED(CompatDirectDrawSurface<IDirectDrawSurface7>::s_origVtable.ReleaseDC(
compatDc.surface, compatDc.dc)))
cachedDc.surface, cachedDc.dc)))
{
LOG_ONCE("Failed to release a cached DC");
Compat::origProcs.ReleaseDDThreadLock();
}
CompatDirectDrawSurface<IDirectDrawSurface7>::s_origVtable.Release(compatDc.surface);
CompatDirectDrawSurface<IDirectDrawSurface7>::s_origVtable.Release(cachedDc.surface);
}
void releaseCompatDcCache(std::vector<CompatDc>& compatDcCache)
void releaseCache(std::vector<CachedDc>& cache)
{
for (auto& compatDc : compatDcCache)
for (auto& cachedDc : cache)
{
releaseCompatDc(compatDc);
releaseCachedDc(cachedDc);
}
}
}
namespace CompatGdiDcCache
{
CompatDc getDc()
CachedDc getDc()
{
CompatDc compatDc = {};
if (!g_currentCompatDcCache)
CachedDc cachedDc = {};
if (!g_currentCache)
{
return compatDc;
return cachedDc;
}
if (g_currentCompatDcCache->empty())
if (g_currentCache->empty())
{
LOG_ONCE("Warning: GDI DC cache size is insufficient");
compatDc = createCompatDc();
if (!compatDc.dc)
cachedDc = createCachedDc();
if (!cachedDc.dc)
{
return compatDc;
return cachedDc;
}
}
else
{
compatDc = g_currentCompatDcCache->back();
g_currentCompatDcCache->pop_back();
cachedDc = g_currentCache->back();
g_currentCache->pop_back();
}
compatDc.dcState = SaveDC(compatDc.dc);
return compatDc;
return cachedDc;
}
bool init()
@ -201,31 +198,21 @@ namespace CompatGdiDcCache
bool isReleased()
{
return g_compatDcCaches.empty();
return g_caches.empty();
}
void release()
{
if (g_currentCompatDcCache)
if (g_currentCache)
{
g_currentCompatDcCache = nullptr;
g_currentCache = nullptr;
clearAllCaches();
++g_cacheId;
}
}
void returnDc(const CompatDc& compatDc)
void releaseDc(const CachedDc& cachedDc)
{
RestoreDC(compatDc.dc, compatDc.dcState);
if (compatDc.cacheId != g_cacheId)
{
releaseCompatDc(compatDc);
}
else
{
g_compatDcCaches[compatDc.surfaceMemoryDesc].push_back(compatDc);
}
g_caches[cachedDc.surfaceMemoryDesc].push_back(cachedDc);
}
void setSurfaceMemory(void* surfaceMemory, LONG pitch)
@ -235,20 +222,20 @@ namespace CompatGdiDcCache
if (!surfaceMemory)
{
g_currentCompatDcCache = nullptr;
g_currentCache = nullptr;
return;
}
SurfaceMemoryDesc surfaceMemoryDesc = { surfaceMemory, pitch };
auto it = g_compatDcCaches.find(surfaceMemoryDesc);
if (it == g_compatDcCaches.end())
auto it = g_caches.find(surfaceMemoryDesc);
if (it == g_caches.end())
{
g_currentCompatDcCache = &g_compatDcCaches[surfaceMemoryDesc];
g_currentCache = &g_caches[surfaceMemoryDesc];
fillCurrentCache();
}
else
{
g_currentCompatDcCache = &it->second;
g_currentCache = &it->second;
}
}
}

View File

@ -14,20 +14,17 @@ namespace CompatGdiDcCache
LONG pitch;
};
struct CompatDc
struct CachedDc
{
DWORD cacheId;
SurfaceMemoryDesc surfaceMemoryDesc;
IDirectDrawSurface7* surface;
HDC origDc;
HDC dc;
int dcState;
};
CompatDc getDc();
CachedDc getDc();
bool init();
bool isReleased();
void release();
void returnDc(const CompatDc& compatDc);
void releaseDc(const CachedDc& cachedDc);
void setSurfaceMemory(void* surfaceMemory, LONG pitch);
}

View File

@ -0,0 +1,186 @@
#include <algorithm>
#include <unordered_map>
#include <vector>
#include "CompatDirectDrawSurface.h"
#include "CompatGdi.h"
#include "CompatGdiDc.h"
#include "CompatGdiFunctions.h"
#include "CompatPrimarySurface.h"
#include "DDrawLog.h"
#include "DDrawScopedThreadLock.h"
#include "RealPrimarySurface.h"
#include <detours.h>
namespace
{
using CompatGdiDc::CachedDc;
struct CompatDc : CachedDc
{
CompatDc(const CachedDc& cachedDc) : CachedDc(cachedDc) {}
HGDIOBJ origFont;
HGDIOBJ origBrush;
HGDIOBJ origPen;
};
std::unordered_map<void*, const char*> g_funcNames;
std::vector<CompatDc> g_usedCompatDcs;
DWORD* g_usedCompatDcCount = nullptr;
template <typename Result, typename... Params>
using FuncPtr = Result(WINAPI *)(Params...);
template <typename OrigFuncPtr, OrigFuncPtr origFunc>
OrigFuncPtr& getOrigFuncPtr()
{
static OrigFuncPtr origFuncPtr = origFunc;
return origFuncPtr;
}
template <typename T>
T replaceDc(T t)
{
return t;
}
HDC replaceDc(HDC dc)
{
auto it = std::find_if(g_usedCompatDcs.begin(), g_usedCompatDcs.end(),
[dc](const CompatDc& compatDc) { return compatDc.dc == dc; });
if (it != g_usedCompatDcs.end())
{
return it->dc;
}
CompatDc compatDc = CompatGdiDc::getDc(dc);
if (!compatDc.dc)
{
return dc;
}
compatDc.origFont = SelectObject(compatDc.dc, GetCurrentObject(dc, OBJ_FONT));
compatDc.origBrush = SelectObject(compatDc.dc, GetCurrentObject(dc, OBJ_BRUSH));
compatDc.origPen = SelectObject(compatDc.dc, GetCurrentObject(dc, OBJ_PEN));
SetTextColor(compatDc.dc, GetTextColor(dc));
SetBkColor(compatDc.dc, GetBkColor(dc));
SetBkMode(compatDc.dc, GetBkMode(dc));
g_usedCompatDcs.push_back(compatDc);
++*g_usedCompatDcCount;
return compatDc.dc;
}
template <typename OrigFuncPtr, OrigFuncPtr origFunc, typename Result, typename... Params>
Result WINAPI compatGdiFunc(Params... params)
{
CompatGdi::GdiScopedThreadLock gdiLock;
Compat::DDrawScopedThreadLock ddLock;
DWORD usedCompatDcCount = 0;
g_usedCompatDcCount = &usedCompatDcCount;
DDSURFACEDESC2 desc = {};
desc.dwSize = sizeof(desc);
if (FAILED(CompatDirectDrawSurface<IDirectDrawSurface7>::s_origVtable.Lock(
CompatPrimarySurface::surface, nullptr, &desc, DDLOCK_WAIT, nullptr)))
{
return getOrigFuncPtr<OrigFuncPtr, origFunc>()(params...);
}
Result result = getOrigFuncPtr<OrigFuncPtr, origFunc>()(replaceDc(params)...);
GdiFlush();
CompatDirectDrawSurface<IDirectDrawSurface7>::s_origVtable.Unlock(
CompatPrimarySurface::surface, nullptr);
if (0 != usedCompatDcCount)
{
RealPrimarySurface::update();
}
for (DWORD i = 0; i < usedCompatDcCount; ++i)
{
CompatDc& compatDc = g_usedCompatDcs.back();
SelectObject(compatDc.dc, compatDc.origFont);
SelectObject(compatDc.dc, compatDc.origBrush);
SelectObject(compatDc.dc, compatDc.origPen);
CompatGdiDc::releaseDc(compatDc);
g_usedCompatDcs.pop_back();
}
return result;
}
template <typename OrigFuncPtr, OrigFuncPtr origFunc, typename Result, typename... Params>
OrigFuncPtr getCompatGdiFuncPtr(FuncPtr<Result, Params...>&)
{
return &compatGdiFunc<OrigFuncPtr, origFunc, Result, Params...>;
}
FARPROC getProcAddress(HMODULE module, const char* procName)
{
if (!module || !procName)
{
return nullptr;
}
PIMAGE_DOS_HEADER dosHeader = reinterpret_cast<PIMAGE_DOS_HEADER>(module);
if (IMAGE_DOS_SIGNATURE != dosHeader->e_magic) {
return nullptr;
}
char* moduleBase = reinterpret_cast<char*>(module);
PIMAGE_NT_HEADERS ntHeader = reinterpret_cast<PIMAGE_NT_HEADERS>(
reinterpret_cast<char*>(dosHeader) + dosHeader->e_lfanew);
if (IMAGE_NT_SIGNATURE != ntHeader->Signature)
{
return nullptr;
}
PIMAGE_EXPORT_DIRECTORY exportDir = reinterpret_cast<PIMAGE_EXPORT_DIRECTORY>(
moduleBase + ntHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
DWORD* rvaOfNames = reinterpret_cast<DWORD*>(moduleBase + exportDir->AddressOfNames);
for (DWORD i = 0; i < exportDir->NumberOfNames; ++i)
{
if (0 == strcmp(procName, moduleBase + rvaOfNames[i]))
{
WORD* nameOrds = reinterpret_cast<WORD*>(moduleBase + exportDir->AddressOfNameOrdinals);
DWORD* rvaOfFunctions = reinterpret_cast<DWORD*>(moduleBase + exportDir->AddressOfFunctions);
return reinterpret_cast<FARPROC>(moduleBase + rvaOfFunctions[nameOrds[i]]);
}
}
return nullptr;
}
template <typename OrigFuncPtr, OrigFuncPtr origFunc>
void hookGdiFunction(const char* moduleName, const char* funcName)
{
OrigFuncPtr& origFuncPtr = getOrigFuncPtr<OrigFuncPtr, origFunc>();
origFuncPtr = reinterpret_cast<OrigFuncPtr>(getProcAddress(GetModuleHandle(moduleName), funcName));
OrigFuncPtr newFuncPtr = getCompatGdiFuncPtr<OrigFuncPtr, origFunc>(origFuncPtr);
DetourAttach(reinterpret_cast<void**>(&origFuncPtr), newFuncPtr);
g_funcNames[origFunc] = funcName;
}
}
void CompatGdiFunctions::hookGdiFunctions()
{
#define HOOK_GDI_FUNCTION(module, func) hookGdiFunction<decltype(&func), &func>(#module, #func);
#define HOOK_GDI_TEXT_FUNCTION(module, func) \
HOOK_GDI_FUNCTION(module, func##A); \
HOOK_GDI_FUNCTION(module, func##W)
DetourTransactionBegin();
HOOK_GDI_FUNCTION(gdi32, BitBlt);
DetourTransactionCommit();
#undef HOOK_GDI_TEXT_FUNCTION
#undef HOOK_GDI_FUNCTION
}

View File

@ -0,0 +1,7 @@
#pragma once
class CompatGdiFunctions
{
public:
static void hookGdiFunctions();
};

View File

@ -1,479 +0,0 @@
#define CINTERFACE
#define WIN32_LEAN_AND_MEAN
#include <unordered_map>
#include <oleacc.h>
#include <Windows.h>
#include <detours.h>
#include "CompatDirectDraw.h"
#include "CompatDirectDrawSurface.h"
#include "CompatGdiDcCache.h"
#include "CompatGdiSurface.h"
#include "CompatPrimarySurface.h"
#include "DDrawLog.h"
#include "DDrawProcs.h"
#include "DDrawScopedThreadLock.h"
#include "RealPrimarySurface.h"
namespace
{
using CompatGdiDcCache::CompatDc;
struct CaretData
{
HWND hwnd;
long left;
long top;
long width;
long height;
bool isDrawn;
};
struct ExcludeClipRectsData
{
HDC compatDc;
POINT clientOrigin;
HWND rootWnd;
};
bool g_suppressGdiHooks = false;
class HookRecursionGuard
{
public:
HookRecursionGuard()
{
g_suppressGdiHooks = true;
}
~HookRecursionGuard()
{
g_suppressGdiHooks = false;
}
};
auto g_origGetDc = &GetDC;
auto g_origGetDcEx = &GetDCEx;
auto g_origGetWindowDc = &GetWindowDC;
auto g_origReleaseDc = &ReleaseDC;
auto g_origBeginPaint = &BeginPaint;
auto g_origEndPaint = &EndPaint;
auto g_origCreateCaret = &CreateCaret;
auto g_origShowCaret = &ShowCaret;
auto g_origHideCaret = &HideCaret;
std::unordered_map<HDC, CompatDc> g_dcToCompatDc;
CaretData g_caret = {};
CRITICAL_SECTION g_gdiCriticalSection;
class GdiScopedThreadLock
{
public:
GdiScopedThreadLock()
{
EnterCriticalSection(&g_gdiCriticalSection);
}
~GdiScopedThreadLock()
{
LeaveCriticalSection(&g_gdiCriticalSection);
}
};
POINT getClientOrigin(HWND hwnd);
HDC getCompatDc(HWND hwnd, HDC origDc, const POINT& origin);
HDC releaseCompatDc(HDC hdc);
LRESULT CALLBACK callWndRetProc(int nCode, WPARAM wParam, LPARAM lParam)
{
if (HC_ACTION == nCode)
{
auto ret = reinterpret_cast<CWPRETSTRUCT*>(lParam);
if (WM_WINDOWPOSCHANGED == ret->message)
{
InvalidateRect(nullptr, nullptr, TRUE);
}
else if (WM_ERASEBKGND == ret->message && ret->lResult)
{
HDC origDc = reinterpret_cast<HDC>(ret->wParam);
GdiScopedThreadLock gdiLock;
if (g_dcToCompatDc.find(origDc) == g_dcToCompatDc.end())
{
HWND hwnd = WindowFromDC(origDc);
POINT origin = {};
ClientToScreen(hwnd, &origin);
HDC compatDc = getCompatDc(hwnd, origDc, origin);
if (compatDc != origDc)
{
SendMessage(hwnd, WM_ERASEBKGND, reinterpret_cast<WPARAM>(compatDc), 0);
releaseCompatDc(compatDc);
}
}
}
}
return CallNextHookEx(nullptr, nCode, wParam, lParam);
}
BOOL CALLBACK excludeClipRectsForOverlappingWindows(HWND hwnd, LPARAM lParam)
{
auto excludeClipRectsData = reinterpret_cast<ExcludeClipRectsData*>(lParam);
if (hwnd == excludeClipRectsData->rootWnd)
{
return FALSE;
}
RECT rect = {};
GetWindowRect(hwnd, &rect);
OffsetRect(&rect, -excludeClipRectsData->clientOrigin.x, -excludeClipRectsData->clientOrigin.y);
ExcludeClipRect(excludeClipRectsData->compatDc, rect.left, rect.top, rect.right, rect.bottom);
return TRUE;
}
POINT getClientOrigin(HWND hwnd)
{
POINT origin = {};
if (hwnd)
{
ClientToScreen(hwnd, &origin);
}
return origin;
}
HDC getCompatDc(HWND hwnd, HDC origDc, const POINT& origin)
{
GdiScopedThreadLock gdiLock;
if (!origDc || !RealPrimarySurface::isFullScreen() || g_suppressGdiHooks)
{
return origDc;
}
HookRecursionGuard recursionGuard;
CompatDc compatDc = CompatGdiDcCache::getDc();
if (!compatDc.dc)
{
return origDc;
}
if (hwnd)
{
SetWindowOrgEx(compatDc.dc, -origin.x, -origin.y, nullptr);
HRGN clipRgn = CreateRectRgn(0, 0, 0, 0);
GetRandomRgn(origDc, clipRgn, SYSRGN);
SelectClipRgn(compatDc.dc, clipRgn);
RECT r = {};
GetRgnBox(clipRgn, &r);
DeleteObject(clipRgn);
ExcludeClipRectsData excludeClipRectsData = { compatDc.dc, origin, GetAncestor(hwnd, GA_ROOT) };
EnumThreadWindows(GetCurrentThreadId(), &excludeClipRectsForOverlappingWindows,
reinterpret_cast<LPARAM>(&excludeClipRectsData));
}
compatDc.origDc = origDc;
g_dcToCompatDc[compatDc.dc] = compatDc;
return compatDc.dc;
}
FARPROC getProcAddress(HMODULE module, const char* procName)
{
if (!module || !procName)
{
return nullptr;
}
PIMAGE_DOS_HEADER dosHeader = reinterpret_cast<PIMAGE_DOS_HEADER>(module);
if (IMAGE_DOS_SIGNATURE != dosHeader->e_magic) {
return nullptr;
}
char* moduleBase = reinterpret_cast<char*>(module);
PIMAGE_NT_HEADERS ntHeader = reinterpret_cast<PIMAGE_NT_HEADERS>(
reinterpret_cast<char*>(dosHeader) + dosHeader->e_lfanew);
if (IMAGE_NT_SIGNATURE != ntHeader->Signature)
{
return nullptr;
}
PIMAGE_EXPORT_DIRECTORY exportDir = reinterpret_cast<PIMAGE_EXPORT_DIRECTORY>(
moduleBase + ntHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
DWORD* rvaOfNames = reinterpret_cast<DWORD*>(moduleBase + exportDir->AddressOfNames);
for (DWORD i = 0; i < exportDir->NumberOfNames; ++i)
{
if (0 == strcmp(procName, moduleBase + rvaOfNames[i]))
{
WORD* nameOrds = reinterpret_cast<WORD*>(moduleBase + exportDir->AddressOfNameOrdinals);
DWORD* rvaOfFunctions = reinterpret_cast<DWORD*>(moduleBase + exportDir->AddressOfFunctions);
return reinterpret_cast<FARPROC>(moduleBase + rvaOfFunctions[nameOrds[i]]);
}
}
return nullptr;
}
POINT getWindowOrigin(HWND hwnd)
{
POINT origin = {};
if (hwnd)
{
RECT windowRect = {};
GetWindowRect(hwnd, &windowRect);
origin.x = windowRect.left;
origin.y = windowRect.top;
}
return origin;
}
template <typename FuncPtr>
void hookGdiFunction(const char* funcName, FuncPtr& origFuncPtr, FuncPtr newFuncPtr)
{
origFuncPtr = reinterpret_cast<FuncPtr>(getProcAddress(GetModuleHandle("user32"), funcName));
DetourAttach(reinterpret_cast<void**>(&origFuncPtr), newFuncPtr);
}
HDC releaseCompatDc(HDC hdc)
{
GdiScopedThreadLock gdiLock;
if (g_suppressGdiHooks)
{
return hdc;
}
HookRecursionGuard recursionGuard;
auto it = g_dcToCompatDc.find(hdc);
if (it != g_dcToCompatDc.end())
{
HDC origDc = it->second.origDc;
CompatGdiDcCache::returnDc(it->second);
g_dcToCompatDc.erase(it);
RealPrimarySurface::update();
return origDc;
}
return hdc;
}
void drawCaret()
{
HDC dc = GetDC(g_caret.hwnd);
PatBlt(dc, g_caret.left, g_caret.top, g_caret.width, g_caret.height, PATINVERT);
ReleaseDC(g_caret.hwnd, dc);
}
LONG getCaretState(IAccessible* accessible)
{
VARIANT varChild = {};
varChild.vt = VT_I4;
varChild.lVal = CHILDID_SELF;
VARIANT varState = {};
accessible->lpVtbl->get_accState(accessible, varChild, &varState);
return varState.lVal;
}
void CALLBACK caretDestroyEvent(
HWINEVENTHOOK /*hWinEventHook*/,
DWORD /*event*/,
HWND hwnd,
LONG idObject,
LONG idChild,
DWORD /*dwEventThread*/,
DWORD /*dwmsEventTime*/)
{
if (OBJID_CARET != idObject || !g_caret.isDrawn || g_caret.hwnd != hwnd)
{
return;
}
IAccessible* accessible = nullptr;
VARIANT varChild = {};
AccessibleObjectFromEvent(hwnd, idObject, idChild, &accessible, &varChild);
if (accessible)
{
if (STATE_SYSTEM_INVISIBLE == getCaretState(accessible))
{
GdiScopedThreadLock gdiLock;
drawCaret();
g_caret.isDrawn = false;
}
accessible->lpVtbl->Release(accessible);
}
}
HDC WINAPI getDc(HWND hWnd)
{
Compat::LogEnter("GetDC", hWnd);
HDC compatDc = getCompatDc(hWnd, g_origGetDc(hWnd), getClientOrigin(hWnd));
Compat::LogLeave("GetDC", hWnd) << compatDc;
return compatDc;
}
HDC WINAPI getDcEx(HWND hWnd, HRGN hrgnClip, DWORD flags)
{
Compat::LogEnter("GetDCEx", hWnd);
HDC compatDc = getCompatDc(hWnd, g_origGetDcEx(hWnd, hrgnClip, flags),
flags & (DCX_WINDOW | DCX_PARENTCLIP) ? getWindowOrigin(hWnd) : getClientOrigin(hWnd));
Compat::LogLeave("GetDCEx", hWnd) << compatDc;
return compatDc;
}
HDC WINAPI getWindowDc(HWND hWnd)
{
Compat::LogEnter("GetWindowDC", hWnd);
HDC compatDc = getCompatDc(hWnd, g_origGetWindowDc(hWnd), getWindowOrigin(hWnd));
Compat::LogLeave("GetWindowDC", hWnd) << compatDc;
return compatDc;
}
int WINAPI releaseDc(HWND hWnd, HDC hDC)
{
Compat::LogEnter("ReleaseDC", hWnd, hDC);
int result = g_origReleaseDc(hWnd, releaseCompatDc(hDC));
Compat::LogLeave("ReleaseDC", hWnd, hDC) << result;
return result;
}
HDC WINAPI beginPaint(HWND hWnd, LPPAINTSTRUCT lpPaint)
{
Compat::LogEnter("BeginPaint", hWnd, lpPaint);
HDC compatDc = getCompatDc(hWnd, g_origBeginPaint(hWnd, lpPaint), getClientOrigin(hWnd));
lpPaint->hdc = compatDc;
Compat::LogLeave("BeginPaint", hWnd, lpPaint) << compatDc;
return compatDc;
}
BOOL WINAPI endPaint(HWND hWnd, const PAINTSTRUCT* lpPaint)
{
Compat::LogEnter("EndPaint", hWnd, lpPaint);
BOOL result = FALSE;
if (lpPaint)
{
PAINTSTRUCT paint = *lpPaint;
paint.hdc = releaseCompatDc(lpPaint->hdc);
result = g_origEndPaint(hWnd, &paint);
}
else
{
result = g_origEndPaint(hWnd, lpPaint);
}
Compat::LogLeave("EndPaint", hWnd, lpPaint) << result;
return result;
}
BOOL WINAPI createCaret(HWND hWnd, HBITMAP hBitmap, int nWidth, int nHeight)
{
BOOL result = g_origCreateCaret(hWnd, hBitmap, nWidth, nHeight);
if (result)
{
GdiScopedThreadLock gdiLock;
if (g_caret.isDrawn)
{
drawCaret();
g_caret.isDrawn = false;
}
g_caret.width = nWidth ? nWidth : GetSystemMetrics(SM_CXBORDER);
g_caret.height = nHeight ? nHeight : GetSystemMetrics(SM_CYBORDER);
}
return result;
}
BOOL WINAPI showCaret(HWND hWnd)
{
BOOL result = g_origShowCaret(hWnd);
GdiScopedThreadLock gdiLock;
if (result && !g_caret.isDrawn)
{
IAccessible* accessible = nullptr;
AccessibleObjectFromWindow(hWnd, static_cast<DWORD>(OBJID_CARET), IID_IAccessible,
reinterpret_cast<void**>(&accessible));
if (accessible)
{
if (0 == getCaretState(accessible))
{
POINT caretPos = {};
GetCaretPos(&caretPos);
g_caret.left = caretPos.x;
g_caret.top = caretPos.y;
g_caret.hwnd = hWnd;
drawCaret();
g_caret.isDrawn = true;
}
accessible->lpVtbl->Release(accessible);
}
}
return result;
}
BOOL WINAPI hideCaret(HWND hWnd)
{
BOOL result = g_origHideCaret(hWnd);
GdiScopedThreadLock gdiLock;
if (result && g_caret.isDrawn)
{
drawCaret();
g_caret.isDrawn = false;
}
return result;
}
}
void CompatGdiSurface::hookGdi()
{
static bool alreadyHooked = false;
if (alreadyHooked)
{
return;
}
InitializeCriticalSection(&g_gdiCriticalSection);
if (CompatGdiDcCache::init())
{
DetourTransactionBegin();
hookGdiFunction("GetDC", g_origGetDc, &getDc);
hookGdiFunction("GetDCEx", g_origGetDcEx, &getDcEx);
hookGdiFunction("GetWindowDC", g_origGetWindowDc, &getWindowDc);
hookGdiFunction("ReleaseDC", g_origReleaseDc, &releaseDc);
hookGdiFunction("BeginPaint", g_origBeginPaint, &beginPaint);
hookGdiFunction("EndPaint", g_origEndPaint, &endPaint);
hookGdiFunction("CreateCaret", g_origCreateCaret, &createCaret);
hookGdiFunction("ShowCaret", g_origShowCaret, &showCaret);
hookGdiFunction("HideCaret", g_origHideCaret, &hideCaret);
DetourTransactionCommit();
DWORD threadId = GetCurrentThreadId();
SetWindowsHookEx(WH_CALLWNDPROCRET, callWndRetProc, nullptr, threadId);
SetWinEventHook(EVENT_OBJECT_DESTROY, EVENT_OBJECT_DESTROY,
nullptr, &caretDestroyEvent, 0, threadId, WINEVENT_OUTOFCONTEXT);
}
alreadyHooked = true;
}
void CompatGdiSurface::release()
{
GdiScopedThreadLock gdiLock;
CompatGdiDcCache::release();
}
void CompatGdiSurface::setSurfaceMemory(void* surfaceMemory, LONG pitch)
{
GdiScopedThreadLock gdiLock;
const bool wasReleased = CompatGdiDcCache::isReleased();
CompatGdiDcCache::setSurfaceMemory(surfaceMemory, pitch);
if (wasReleased)
{
InvalidateRect(nullptr, nullptr, TRUE);
}
}

View File

@ -1,9 +0,0 @@
#pragma once
class CompatGdiSurface
{
public:
static void hookGdi();
static void release();
static void setSurfaceMemory(void* surfaceMemory, LONG pitch);
};

View File

@ -145,8 +145,10 @@
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="CompatDirectDrawPalette.h" />
<ClInclude Include="CompatGdi.h" />
<ClInclude Include="CompatGdiDc.h" />
<ClInclude Include="CompatGdiDcCache.h" />
<ClInclude Include="CompatGdiSurface.h" />
<ClInclude Include="CompatGdiFunctions.h" />
<ClInclude Include="Config.h" />
<ClInclude Include="DDrawProcs.h" />
<ClInclude Include="CompatDirectDraw.h" />
@ -167,8 +169,9 @@
<ClCompile Include="CompatDirectDraw.cpp" />
<ClCompile Include="CompatDirectDrawPalette.cpp" />
<ClCompile Include="CompatDirectDrawSurface.cpp" />
<ClCompile Include="CompatGdi.cpp" />
<ClCompile Include="CompatGdiDcCache.cpp" />
<ClCompile Include="CompatGdiSurface.cpp" />
<ClCompile Include="CompatGdiFunctions.cpp" />
<ClCompile Include="CompatVtable.cpp" />
<ClCompile Include="DDrawLog.cpp" />
<ClCompile Include="DllMain.cpp" />
@ -176,6 +179,7 @@
<ClCompile Include="DDrawProcs.cpp" />
<ClCompile Include="IReleaseNotifier.cpp" />
<ClCompile Include="RealPrimarySurface.cpp" />
<ClCompile Include="CompatGdiDc.cpp" />
<ClCompile Include="UnmodifiedDDrawProcs.cpp" />
</ItemGroup>
<ItemGroup>

View File

@ -48,9 +48,6 @@
<ClInclude Include="CompatDirectDrawPalette.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="CompatGdiSurface.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="RealPrimarySurface.h">
<Filter>Header Files</Filter>
</ClInclude>
@ -69,6 +66,15 @@
<ClInclude Include="CompatGdiDcCache.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="CompatGdiFunctions.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="CompatGdiDc.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="CompatGdi.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="DllMain.cpp">
@ -89,9 +95,6 @@
<ClCompile Include="CompatPrimarySurface.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="CompatGdiSurface.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="IReleaseNotifier.cpp">
<Filter>Source Files</Filter>
</ClCompile>
@ -110,6 +113,15 @@
<ClCompile Include="CompatGdiDcCache.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="CompatGdiFunctions.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="CompatGdiDc.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="CompatGdi.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<None Include="DDrawCompat.def">

View File

@ -3,7 +3,7 @@
#include "CompatDirectDraw.h"
#include "CompatDirectDrawSurface.h"
#include "CompatDirectDrawPalette.h"
#include "CompatGdiSurface.h"
#include "CompatGdi.h"
#include "CompatVtable.h"
#include "DDrawProcs.h"
@ -95,7 +95,7 @@ namespace
hookDirectDrawPalette(*dd);
Compat::Log() << "Installing GDI hooks";
CompatGdiSurface::hookGdi();
CompatGdi::installHooks();
dd->lpVtbl->Release(dd);
}

View File

@ -2,7 +2,7 @@
#include "CompatDirectDraw.h"
#include "CompatDirectDrawSurface.h"
#include "CompatGdiSurface.h"
#include "CompatGdi.h"
#include "CompatPrimarySurface.h"
#include "Config.h"
#include "DDrawProcs.h"
@ -306,7 +306,7 @@ bool RealPrimarySurface::isLost()
DDERR_SURFACELOST == CompatDirectDrawSurface<IDirectDrawSurface7>::s_origVtable.IsLost(g_frontBuffer);
if (isLost)
{
CompatGdiSurface::release();
CompatGdi::releaseSurfaceMemory();
}
return isLost;
}