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

321 lines
8.2 KiB
C++

#include "CompatDirectDrawPalette.h"
#include "CompatDirectDrawSurface.h"
#include "CompatGdi.h"
#include "CompatGdiCaret.h"
#include "CompatGdiDcCache.h"
#include "CompatGdiDcFunctions.h"
#include "CompatGdiScrollFunctions.h"
#include "CompatGdiWinProc.h"
#include "CompatPrimarySurface.h"
#include "DDrawProcs.h"
#include "RealPrimarySurface.h"
namespace
{
DWORD g_renderingRefCount = 0;
DWORD g_ddLockThreadRenderingRefCount = 0;
DWORD g_ddLockThreadId = 0;
HANDLE g_ddUnlockBeginEvent = nullptr;
HANDLE g_ddUnlockEndEvent = nullptr;
bool g_isDelayedUnlockPending = false;
bool g_isPaletteUsed = false;
PALETTEENTRY g_usedPaletteEntries[256] = {};
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;
}
BOOL CALLBACK invalidateWindow(HWND hwnd, LPARAM lParam)
{
if (!IsWindowVisible(hwnd))
{
return TRUE;
}
DWORD processId = 0;
GetWindowThreadProcessId(hwnd, &processId);
if (processId != GetCurrentProcessId())
{
return TRUE;
}
if (lParam)
{
POINT origin = {};
ClientToScreen(hwnd, &origin);
RECT rect = *reinterpret_cast<const RECT*>(lParam);
OffsetRect(&rect, -origin.x, -origin.y);
RedrawWindow(hwnd, &rect, nullptr, RDW_ERASE | RDW_FRAME | RDW_INVALIDATE | RDW_ALLCHILDREN);
}
else
{
RedrawWindow(hwnd, nullptr, nullptr, RDW_ERASE | RDW_FRAME | RDW_INVALIDATE | RDW_ALLCHILDREN);
}
return TRUE;
}
bool lockPrimarySurface()
{
Compat::origProcs.AcquireDDThreadLock();
DDSURFACEDESC2 desc = {};
desc.dwSize = sizeof(desc);
if (FAILED(CompatDirectDrawSurface<IDirectDrawSurface7>::s_origVtable.Lock(
CompatPrimarySurface::surface, nullptr, &desc, DDLOCK_WAIT, nullptr)))
{
Compat::origProcs.ReleaseDDThreadLock();
return false;
}
g_ddLockThreadId = GetCurrentThreadId();
CompatGdiDcCache::setDdLockThreadId(g_ddLockThreadId);
CompatGdiDcCache::setSurfaceMemory(desc.lpSurface, desc.lPitch);
return true;
}
/*
Workaround for correctly drawing icons with a transparent background using BitBlt and a monochrome
bitmap mask. Normally black (index 0) and white (index 255) are selected as foreground and background
colors on the target DC so that a BitBlt with the SRCAND ROP would preserve the background pixels and
set the foreground pixels to 0. (Logical AND with 0xFF preserves all bits.)
But if the physical palette contains another, earlier white entry, SRCAND will be performed with the
wrong index (less than 0xFF), erasing some of the bits from all background pixels.
This workaround replaces all unwanted white entries with a similar color.
*/
void replaceDuplicateWhitePaletteEntries(const PALETTEENTRY (&entries)[256])
{
PALETTEENTRY newEntries[256] = {};
memcpy(newEntries, entries, sizeof(entries));
bool isReplacementDone = false;
for (int i = 0; i < 255; ++i)
{
if (0xFF == entries[i].peRed && 0xFF == entries[i].peGreen && 0xFF == entries[i].peBlue)
{
newEntries[i].peRed = 0xFE;
newEntries[i].peGreen = 0xFE;
newEntries[i].peBlue = 0xFE;
isReplacementDone = true;
}
}
if (isReplacementDone)
{
CompatDirectDrawPalette::s_origVtable.SetEntries(
CompatPrimarySurface::palette, 0, 0, 256, newEntries);
}
}
void unlockPrimarySurface()
{
GdiFlush();
CompatDirectDrawSurface<IDirectDrawSurface7>::s_origVtable.Unlock(
CompatPrimarySurface::surface, nullptr);
RealPrimarySurface::update();
Compat::origProcs.ReleaseDDThreadLock();
}
}
namespace CompatGdi
{
CRITICAL_SECTION g_gdiCriticalSection;
std::unordered_map<void*, const char*> g_funcNames;
GdiScopedThreadLock::GdiScopedThreadLock() : m_isLocked(true)
{
EnterCriticalSection(&g_gdiCriticalSection);
}
GdiScopedThreadLock::~GdiScopedThreadLock()
{
unlock();
}
void GdiScopedThreadLock::unlock()
{
if (m_isLocked)
{
LeaveCriticalSection(&g_gdiCriticalSection);
m_isLocked = false;
}
}
bool beginGdiRendering()
{
if (!RealPrimarySurface::isFullScreen())
{
return false;
}
GdiScopedThreadLock gdiLock;
if (0 == g_renderingRefCount)
{
if (!lockPrimarySurface())
{
return false;
}
++g_ddLockThreadRenderingRefCount;
}
else if (GetCurrentThreadId() == g_ddLockThreadId)
{
++g_ddLockThreadRenderingRefCount;
}
if (!g_isPaletteUsed && CompatPrimarySurface::palette)
{
g_isPaletteUsed = true;
ZeroMemory(g_usedPaletteEntries, sizeof(g_usedPaletteEntries));
CompatPrimarySurface::palette->lpVtbl->GetEntries(
CompatPrimarySurface::palette, 0, 0, 256, g_usedPaletteEntries);
}
++g_renderingRefCount;
return true;
}
void endGdiRendering()
{
CompatGdi::GdiScopedThreadLock gdiLock;
if (GetCurrentThreadId() == g_ddLockThreadId)
{
if (1 == g_renderingRefCount)
{
unlockPrimarySurface();
g_ddLockThreadRenderingRefCount = 0;
g_renderingRefCount = 0;
}
else if (1 == g_ddLockThreadRenderingRefCount)
{
g_isDelayedUnlockPending = true;
gdiLock.unlock();
WaitForSingleObject(g_ddUnlockBeginEvent, INFINITE);
unlockPrimarySurface();
g_ddLockThreadRenderingRefCount = 0;
g_renderingRefCount = 0;
SetEvent(g_ddUnlockEndEvent);
}
else
{
--g_ddLockThreadRenderingRefCount;
--g_renderingRefCount;
}
}
else
{
--g_renderingRefCount;
if (1 == g_renderingRefCount && g_isDelayedUnlockPending)
{
SetEvent(g_ddUnlockBeginEvent);
WaitForSingleObject(g_ddUnlockEndEvent, INFINITE);
g_isDelayedUnlockPending = false;
}
}
}
void hookGdiFunction(const char* moduleName, const char* funcName, void*& origFuncPtr, void* newFuncPtr)
{
#ifdef _DEBUG
g_funcNames[origFuncPtr] = funcName;
#endif
FARPROC procAddr = getProcAddress(GetModuleHandle(moduleName), funcName);
if (!procAddr)
{
Compat::Log() << "Failed to load the address of a GDI function: " << funcName;
return;
}
origFuncPtr = procAddr;
if (NO_ERROR != DetourAttach(&origFuncPtr, newFuncPtr))
{
Compat::Log() << "Failed to hook a GDI function: " << funcName;
return;
}
}
void installHooks()
{
InitializeCriticalSection(&g_gdiCriticalSection);
if (CompatGdiDcCache::init())
{
g_ddUnlockBeginEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
g_ddUnlockEndEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
if (!g_ddUnlockBeginEvent || !g_ddUnlockEndEvent)
{
Compat::Log() << "Failed to create the unlock events for GDI";
return;
}
CompatGdiDcFunctions::installHooks();
CompatGdiScrollFunctions::installHooks();
CompatGdiWinProc::installHooks();
CompatGdiCaret::installHooks();
}
}
void invalidate(const RECT* rect)
{
EnumWindows(&invalidateWindow, reinterpret_cast<LPARAM>(rect));
}
void updatePalette()
{
GdiScopedThreadLock gdiLock;
CompatGdiDcCache::clear();
if (g_isPaletteUsed && CompatPrimarySurface::palette)
{
g_isPaletteUsed = false;
PALETTEENTRY usedPaletteEntries[256] = {};
CompatPrimarySurface::palette->lpVtbl->GetEntries(
CompatPrimarySurface::palette, 0, 0, 256, usedPaletteEntries);
if (0 != memcmp(usedPaletteEntries, g_usedPaletteEntries, sizeof(usedPaletteEntries)))
{
replaceDuplicateWhitePaletteEntries(usedPaletteEntries);
invalidate(nullptr);
}
}
}
}