From 5a30b072ba6f80b3f85438de81cb9e1a9062d840 Mon Sep 17 00:00:00 2001 From: narzoul Date: Mon, 4 Jan 2016 23:37:58 +0100 Subject: [PATCH] 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. --- DDrawCompat/CompatDirectDrawSurface.cpp | 6 +- DDrawCompat/CompatGdi.cpp | 47 +++ DDrawCompat/CompatGdi.h | 15 + DDrawCompat/CompatGdiDc.cpp | 74 ++++ DDrawCompat/CompatGdiDc.h | 15 + DDrawCompat/CompatGdiDcCache.cpp | 107 +++--- DDrawCompat/CompatGdiDcCache.h | 9 +- DDrawCompat/CompatGdiFunctions.cpp | 186 +++++++++ DDrawCompat/CompatGdiFunctions.h | 7 + DDrawCompat/CompatGdiSurface.cpp | 479 ------------------------ DDrawCompat/CompatGdiSurface.h | 9 - DDrawCompat/DDrawCompat.vcxproj | 8 +- DDrawCompat/DDrawCompat.vcxproj.filters | 24 +- DDrawCompat/DllMain.cpp | 4 +- DDrawCompat/RealPrimarySurface.cpp | 4 +- 15 files changed, 425 insertions(+), 569 deletions(-) create mode 100644 DDrawCompat/CompatGdi.cpp create mode 100644 DDrawCompat/CompatGdi.h create mode 100644 DDrawCompat/CompatGdiDc.cpp create mode 100644 DDrawCompat/CompatGdiDc.h create mode 100644 DDrawCompat/CompatGdiFunctions.cpp create mode 100644 DDrawCompat/CompatGdiFunctions.h delete mode 100644 DDrawCompat/CompatGdiSurface.cpp delete mode 100644 DDrawCompat/CompatGdiSurface.h diff --git a/DDrawCompat/CompatDirectDrawSurface.cpp b/DDrawCompat/CompatDirectDrawSurface.cpp index b6dd4f0..c44a9a9 100644 --- a/DDrawCompat/CompatDirectDrawSurface.cpp +++ b/DDrawCompat/CompatDirectDrawSurface.cpp @@ -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::Restore(TSurface* T result = RealPrimarySurface::restore(); if (wasLost) { - CompatGdiSurface::release(); + CompatGdi::releaseSurfaceMemory(); updateSurfaceParams(); } } @@ -691,7 +691,7 @@ void CompatDirectDrawSurface::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; } diff --git a/DDrawCompat/CompatGdi.cpp b/DDrawCompat/CompatGdi.cpp new file mode 100644 index 0000000..b8874a2 --- /dev/null +++ b/DDrawCompat/CompatGdi.cpp @@ -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); + } + } +} diff --git a/DDrawCompat/CompatGdi.h b/DDrawCompat/CompatGdi.h new file mode 100644 index 0000000..dfb2829 --- /dev/null +++ b/DDrawCompat/CompatGdi.h @@ -0,0 +1,15 @@ +#pragma once + +namespace CompatGdi +{ + class GdiScopedThreadLock + { + public: + GdiScopedThreadLock(); + ~GdiScopedThreadLock(); + }; + + void installHooks(); + void releaseSurfaceMemory(); + void setSurfaceMemory(void* surfaceMemory, int pitch); +}; diff --git a/DDrawCompat/CompatGdiDc.cpp b/DDrawCompat/CompatGdiDc.cpp new file mode 100644 index 0000000..59347b2 --- /dev/null +++ b/DDrawCompat/CompatGdiDc.cpp @@ -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(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(&excludeClipRectsData)); + } + + return cachedDc; + } + + void releaseDc(const CachedDc& cachedDc) + { + CompatGdiDcCache::releaseDc(cachedDc); + } +} diff --git a/DDrawCompat/CompatGdiDc.h b/DDrawCompat/CompatGdiDc.h new file mode 100644 index 0000000..f5ff301 --- /dev/null +++ b/DDrawCompat/CompatGdiDc.h @@ -0,0 +1,15 @@ +#pragma once + +#define WIN32_LEAN_AND_MEAN + +#include + +#include "CompatGdiDcCache.h" + +namespace CompatGdiDc +{ + using CompatGdiDcCache::CachedDc; + + CachedDc getDc(HDC origDc); + void releaseDc(const CachedDc& cachedDc); +} diff --git a/DDrawCompat/CompatGdiDcCache.cpp b/DDrawCompat/CompatGdiDcCache.cpp index fa8516d..d3f8bfd 100644 --- a/DDrawCompat/CompatGdiDcCache.cpp +++ b/DDrawCompat/CompatGdiDcCache.cpp @@ -22,27 +22,26 @@ namespace CompatGdiDcCache namespace { using CompatGdiDcCache::SurfaceMemoryDesc; - using CompatGdiDcCache::CompatDc; + using CompatGdiDcCache::CachedDc; - std::map> g_compatDcCaches; - std::vector* g_currentCompatDcCache = nullptr; + std::map> g_caches; + std::vector* 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& compatDcCache); + void releaseCachedDc(CachedDc cachedDc); + void releaseCache(std::vector& 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::s_origVtable.ReleaseDC( - compatDc.surface, compatDc.dc))) + cachedDc.surface, cachedDc.dc))) { LOG_ONCE("Failed to release a cached DC"); Compat::origProcs.ReleaseDDThreadLock(); } - CompatDirectDrawSurface::s_origVtable.Release(compatDc.surface); + CompatDirectDrawSurface::s_origVtable.Release(cachedDc.surface); } - void releaseCompatDcCache(std::vector& compatDcCache) + void releaseCache(std::vector& 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; } } } diff --git a/DDrawCompat/CompatGdiDcCache.h b/DDrawCompat/CompatGdiDcCache.h index 12ca0a3..701592d 100644 --- a/DDrawCompat/CompatGdiDcCache.h +++ b/DDrawCompat/CompatGdiDcCache.h @@ -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); } diff --git a/DDrawCompat/CompatGdiFunctions.cpp b/DDrawCompat/CompatGdiFunctions.cpp new file mode 100644 index 0000000..4a76310 --- /dev/null +++ b/DDrawCompat/CompatGdiFunctions.cpp @@ -0,0 +1,186 @@ +#include +#include +#include + +#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 + +namespace +{ + using CompatGdiDc::CachedDc; + + struct CompatDc : CachedDc + { + CompatDc(const CachedDc& cachedDc) : CachedDc(cachedDc) {} + HGDIOBJ origFont; + HGDIOBJ origBrush; + HGDIOBJ origPen; + }; + + std::unordered_map g_funcNames; + std::vector g_usedCompatDcs; + DWORD* g_usedCompatDcCount = nullptr; + + template + using FuncPtr = Result(WINAPI *)(Params...); + + template + OrigFuncPtr& getOrigFuncPtr() + { + static OrigFuncPtr origFuncPtr = origFunc; + return origFuncPtr; + } + + template + 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 + 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::s_origVtable.Lock( + CompatPrimarySurface::surface, nullptr, &desc, DDLOCK_WAIT, nullptr))) + { + return getOrigFuncPtr()(params...); + } + + Result result = getOrigFuncPtr()(replaceDc(params)...); + GdiFlush(); + + CompatDirectDrawSurface::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 + OrigFuncPtr getCompatGdiFuncPtr(FuncPtr&) + { + return &compatGdiFunc; + } + + FARPROC getProcAddress(HMODULE module, const char* procName) + { + if (!module || !procName) + { + return nullptr; + } + + PIMAGE_DOS_HEADER dosHeader = reinterpret_cast(module); + if (IMAGE_DOS_SIGNATURE != dosHeader->e_magic) { + return nullptr; + } + char* moduleBase = reinterpret_cast(module); + + PIMAGE_NT_HEADERS ntHeader = reinterpret_cast( + reinterpret_cast(dosHeader) + dosHeader->e_lfanew); + if (IMAGE_NT_SIGNATURE != ntHeader->Signature) + { + return nullptr; + } + + PIMAGE_EXPORT_DIRECTORY exportDir = reinterpret_cast( + moduleBase + ntHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress); + + DWORD* rvaOfNames = reinterpret_cast(moduleBase + exportDir->AddressOfNames); + + for (DWORD i = 0; i < exportDir->NumberOfNames; ++i) + { + if (0 == strcmp(procName, moduleBase + rvaOfNames[i])) + { + WORD* nameOrds = reinterpret_cast(moduleBase + exportDir->AddressOfNameOrdinals); + DWORD* rvaOfFunctions = reinterpret_cast(moduleBase + exportDir->AddressOfFunctions); + return reinterpret_cast(moduleBase + rvaOfFunctions[nameOrds[i]]); + } + } + + return nullptr; + } + + template + void hookGdiFunction(const char* moduleName, const char* funcName) + { + OrigFuncPtr& origFuncPtr = getOrigFuncPtr(); + origFuncPtr = reinterpret_cast(getProcAddress(GetModuleHandle(moduleName), funcName)); + OrigFuncPtr newFuncPtr = getCompatGdiFuncPtr(origFuncPtr); + DetourAttach(reinterpret_cast(&origFuncPtr), newFuncPtr); + g_funcNames[origFunc] = funcName; + } +} + +void CompatGdiFunctions::hookGdiFunctions() +{ +#define HOOK_GDI_FUNCTION(module, func) hookGdiFunction(#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 +} diff --git a/DDrawCompat/CompatGdiFunctions.h b/DDrawCompat/CompatGdiFunctions.h new file mode 100644 index 0000000..ce2a0d2 --- /dev/null +++ b/DDrawCompat/CompatGdiFunctions.h @@ -0,0 +1,7 @@ +#pragma once + +class CompatGdiFunctions +{ +public: + static void hookGdiFunctions(); +}; diff --git a/DDrawCompat/CompatGdiSurface.cpp b/DDrawCompat/CompatGdiSurface.cpp deleted file mode 100644 index c43f4ea..0000000 --- a/DDrawCompat/CompatGdiSurface.cpp +++ /dev/null @@ -1,479 +0,0 @@ -#define CINTERFACE -#define WIN32_LEAN_AND_MEAN - -#include - -#include -#include -#include - -#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 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(lParam); - if (WM_WINDOWPOSCHANGED == ret->message) - { - InvalidateRect(nullptr, nullptr, TRUE); - } - else if (WM_ERASEBKGND == ret->message && ret->lResult) - { - HDC origDc = reinterpret_cast(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(compatDc), 0); - releaseCompatDc(compatDc); - } - } - } - } - - return CallNextHookEx(nullptr, nCode, wParam, lParam); - } - - BOOL CALLBACK excludeClipRectsForOverlappingWindows(HWND hwnd, LPARAM lParam) - { - auto excludeClipRectsData = reinterpret_cast(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(&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(module); - if (IMAGE_DOS_SIGNATURE != dosHeader->e_magic) { - return nullptr; - } - char* moduleBase = reinterpret_cast(module); - - PIMAGE_NT_HEADERS ntHeader = reinterpret_cast( - reinterpret_cast(dosHeader) + dosHeader->e_lfanew); - if (IMAGE_NT_SIGNATURE != ntHeader->Signature) - { - return nullptr; - } - - PIMAGE_EXPORT_DIRECTORY exportDir = reinterpret_cast( - moduleBase + ntHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress); - - DWORD* rvaOfNames = reinterpret_cast(moduleBase + exportDir->AddressOfNames); - - for (DWORD i = 0; i < exportDir->NumberOfNames; ++i) - { - if (0 == strcmp(procName, moduleBase + rvaOfNames[i])) - { - WORD* nameOrds = reinterpret_cast(moduleBase + exportDir->AddressOfNameOrdinals); - DWORD* rvaOfFunctions = reinterpret_cast(moduleBase + exportDir->AddressOfFunctions); - return reinterpret_cast(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 - void hookGdiFunction(const char* funcName, FuncPtr& origFuncPtr, FuncPtr newFuncPtr) - { - origFuncPtr = reinterpret_cast(getProcAddress(GetModuleHandle("user32"), funcName)); - DetourAttach(reinterpret_cast(&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(OBJID_CARET), IID_IAccessible, - reinterpret_cast(&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); - } -} diff --git a/DDrawCompat/CompatGdiSurface.h b/DDrawCompat/CompatGdiSurface.h deleted file mode 100644 index 712a242..0000000 --- a/DDrawCompat/CompatGdiSurface.h +++ /dev/null @@ -1,9 +0,0 @@ -#pragma once - -class CompatGdiSurface -{ -public: - static void hookGdi(); - static void release(); - static void setSurfaceMemory(void* surfaceMemory, LONG pitch); -}; diff --git a/DDrawCompat/DDrawCompat.vcxproj b/DDrawCompat/DDrawCompat.vcxproj index 0a1012b..a2188aa 100644 --- a/DDrawCompat/DDrawCompat.vcxproj +++ b/DDrawCompat/DDrawCompat.vcxproj @@ -145,8 +145,10 @@ + + - + @@ -167,8 +169,9 @@ + - + @@ -176,6 +179,7 @@ + diff --git a/DDrawCompat/DDrawCompat.vcxproj.filters b/DDrawCompat/DDrawCompat.vcxproj.filters index b05c9e9..ad2883e 100644 --- a/DDrawCompat/DDrawCompat.vcxproj.filters +++ b/DDrawCompat/DDrawCompat.vcxproj.filters @@ -48,9 +48,6 @@ Header Files - - Header Files - Header Files @@ -69,6 +66,15 @@ Header Files + + Header Files + + + Header Files + + + Header Files + @@ -89,9 +95,6 @@ Source Files - - Source Files - Source Files @@ -110,6 +113,15 @@ Source Files + + Source Files + + + Source Files + + + Source Files + diff --git a/DDrawCompat/DllMain.cpp b/DDrawCompat/DllMain.cpp index 8dbd43d..65253c7 100644 --- a/DDrawCompat/DllMain.cpp +++ b/DDrawCompat/DllMain.cpp @@ -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); } diff --git a/DDrawCompat/RealPrimarySurface.cpp b/DDrawCompat/RealPrimarySurface.cpp index 5fae0cd..9ac50b0 100644 --- a/DDrawCompat/RealPrimarySurface.cpp +++ b/DDrawCompat/RealPrimarySurface.cpp @@ -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::s_origVtable.IsLost(g_frontBuffer); if (isLost) { - CompatGdiSurface::release(); + CompatGdi::releaseSurfaceMemory(); } return isLost; }