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

248 lines
5.7 KiB
C++

#include <atomic>
#include "Common/ScopedCriticalSection.h"
#include "DDraw/PaletteConverter.h"
#include "DDraw/RealPrimarySurface.h"
#include "DDraw/Surfaces/PrimarySurface.h"
#include "Dll/Procs.h"
#include "Gdi/Caret.h"
#include "Gdi/DcCache.h"
#include "Gdi/DcFunctions.h"
#include "Gdi/Gdi.h"
#include "Gdi/PaintHandlers.h"
#include "Gdi/ScrollFunctions.h"
#include "Gdi/WinProc.h"
namespace
{
std::atomic<int> g_disableEmulationCount = 0;
DWORD g_renderingRefCount = 0;
DWORD g_ddLockFlags = 0;
DWORD g_ddLockThreadRenderingRefCount = 0;
DWORD g_ddLockThreadId = 0;
HANDLE g_ddUnlockBeginEvent = nullptr;
HANDLE g_ddUnlockEndEvent = nullptr;
bool g_isDelayedUnlockPending = false;
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(DWORD lockFlags)
{
DDSURFACEDESC2 desc = {};
desc.dwSize = sizeof(desc);
auto primary(DDraw::PrimarySurface::getPrimary());
if (FAILED(primary->Lock(primary, nullptr, &desc, lockFlags | DDLOCK_WAIT, nullptr)))
{
return false;
}
g_ddLockFlags = lockFlags;
if (0 != lockFlags)
{
EnterCriticalSection(&Gdi::g_gdiCriticalSection);
}
g_ddLockThreadId = GetCurrentThreadId();
Gdi::DcCache::setDdLockThreadId(g_ddLockThreadId);
Gdi::DcCache::setSurfaceMemory(desc.lpSurface, desc.lPitch);
return true;
}
void unlockPrimarySurface()
{
GdiFlush();
auto primary(DDraw::PrimarySurface::getPrimary());
primary->Unlock(primary, nullptr);
if (DDLOCK_READONLY != g_ddLockFlags)
{
DDraw::RealPrimarySurface::invalidate(nullptr);
DDraw::RealPrimarySurface::update();
}
if (0 != g_ddLockFlags)
{
LeaveCriticalSection(&Gdi::g_gdiCriticalSection);
}
g_ddLockFlags = 0;
Dll::g_origProcs.ReleaseDDThreadLock();
}
}
namespace Gdi
{
CRITICAL_SECTION g_gdiCriticalSection;
bool beginGdiRendering(DWORD lockFlags)
{
if (!isEmulationEnabled())
{
return false;
}
Compat::ScopedCriticalSection gdiLock(g_gdiCriticalSection);
if (0 == g_renderingRefCount)
{
LeaveCriticalSection(&g_gdiCriticalSection);
Dll::g_origProcs.AcquireDDThreadLock();
EnterCriticalSection(&g_gdiCriticalSection);
if (!lockPrimarySurface(lockFlags))
{
Dll::g_origProcs.ReleaseDDThreadLock();
return false;
}
}
if (GetCurrentThreadId() == g_ddLockThreadId)
{
++g_ddLockThreadRenderingRefCount;
}
++g_renderingRefCount;
return true;
}
void endGdiRendering()
{
Compat::ScopedCriticalSection gdiLock(g_gdiCriticalSection);
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 disableEmulation()
{
++g_disableEmulationCount;
}
void enableEmulation()
{
--g_disableEmulationCount;
}
void hookWndProc(LPCSTR className, WNDPROC &oldWndProc, WNDPROC newWndProc)
{
HWND hwnd = CreateWindow(className, nullptr, 0, 0, 0, 0, 0, nullptr, nullptr, nullptr, 0);
oldWndProc = reinterpret_cast<WNDPROC>(
SetClassLongPtr(hwnd, GCLP_WNDPROC, reinterpret_cast<LONG>(newWndProc)));
DestroyWindow(hwnd);
}
void installHooks()
{
InitializeCriticalSection(&g_gdiCriticalSection);
if (Gdi::DcCache::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;
}
Gdi::DcFunctions::installHooks();
Gdi::PaintHandlers::installHooks();
Gdi::ScrollFunctions::installHooks();
Gdi::WinProc::installHooks();
Gdi::Caret::installHooks();
}
}
void invalidate(const RECT* rect)
{
if (isEmulationEnabled())
{
EnumWindows(&invalidateWindow, reinterpret_cast<LPARAM>(rect));
}
}
bool isEmulationEnabled()
{
return g_disableEmulationCount <= 0 && DDraw::RealPrimarySurface::isFullScreen();
}
void unhookWndProc(LPCSTR className, WNDPROC oldWndProc)
{
HWND hwnd = CreateWindow(className, nullptr, 0, 0, 0, 0, 0, nullptr, nullptr, nullptr, 0);
SetClassLongPtr(hwnd, GCLP_WNDPROC, reinterpret_cast<LONG>(oldWndProc));
DestroyWindow(hwnd);
}
void uninstallHooks()
{
Gdi::Caret::uninstallHooks();
Gdi::WinProc::uninstallHooks();
Gdi::PaintHandlers::uninstallHooks();
}
void updatePalette(DWORD startingEntry, DWORD count)
{
if (isEmulationEnabled() && DDraw::PrimarySurface::s_palette)
{
Gdi::DcCache::updatePalette(startingEntry, count);
}
}
}