414 lines
11 KiB
C++
414 lines
11 KiB
C++
|
/*++
|
||
|
|
||
|
Copyright (c) 2000 Microsoft Corporation
|
||
|
|
||
|
Module Name:
|
||
|
|
||
|
EmulateDirectDrawSync.cpp
|
||
|
|
||
|
Abstract:
|
||
|
|
||
|
DirectDraw uses per-thread exclusive mode arbitration on NT. On Win9x this
|
||
|
is done per process. What this means is that if an app releases exclusive
|
||
|
mode from a different thread than that which acquired it, it will be in a
|
||
|
permanently bad state.
|
||
|
|
||
|
This shim ensures that the mutex is obtained and released on the same
|
||
|
thread. During DLL_PROCESS_ATTACH, a new thread is started: this thread
|
||
|
manages the acquisition and release of the mutex.
|
||
|
|
||
|
Note we can't get the mutex by catching CreateMutex because it's called
|
||
|
from the dllmain of ddraw.dll: so it wouldn't work on win2k.
|
||
|
|
||
|
Notes:
|
||
|
|
||
|
This is a general purpose shim.
|
||
|
|
||
|
History:
|
||
|
|
||
|
09/11/2000 prashkud Created
|
||
|
10/28/2000 linstev Rewrote to work on win2k
|
||
|
02/23/2001 linstev Modified to handle cases where DirectDraw was used
|
||
|
inside DllMains
|
||
|
|
||
|
--*/
|
||
|
|
||
|
#include "precomp.h"
|
||
|
|
||
|
IMPLEMENT_SHIM_BEGIN(EmulateDirectDrawSync)
|
||
|
#include "ShimHookMacro.h"
|
||
|
|
||
|
APIHOOK_ENUM_BEGIN
|
||
|
APIHOOK_ENUM_ENTRY(WaitForSingleObject)
|
||
|
APIHOOK_ENUM_ENTRY(ReleaseMutex)
|
||
|
APIHOOK_ENUM_ENTRY(CloseHandle)
|
||
|
APIHOOK_ENUM_END
|
||
|
|
||
|
// Enum used to tell our thread what to do
|
||
|
enum {sNone, sWaitForSingleObject, sReleaseMutex};
|
||
|
|
||
|
// Events we use to signal our thread to do work and wait until its done
|
||
|
HANDLE g_hWaitEvent;
|
||
|
HANDLE g_hDoneEvent;
|
||
|
HANDLE g_hThread = NULL;
|
||
|
|
||
|
//
|
||
|
// Parameters that are passed between the caller thread and our thread
|
||
|
// Access is synchronized with a critical section
|
||
|
//
|
||
|
|
||
|
CRITICAL_SECTION g_csSync;
|
||
|
DWORD g_dwWait;
|
||
|
DWORD g_dwWaitRetValue;
|
||
|
DWORD g_dwTime;
|
||
|
BOOL g_bRetValue;
|
||
|
|
||
|
// Store the DirectDraw mutex handle
|
||
|
HANDLE g_hDDMutex = 0;
|
||
|
|
||
|
// Thread tracking data so we can identify degenerate cases
|
||
|
DWORD g_dwMutexOwnerThreadId = 0;
|
||
|
|
||
|
// Find the DirectDraw mutex
|
||
|
DWORD g_dwFindMutexThread = 0;
|
||
|
|
||
|
/*++
|
||
|
|
||
|
Unfortunately we don't get in early enough on Win2k to get the mutex from the
|
||
|
ddraw call to CreateMutex, so we have to make use of a special hack that knows
|
||
|
about the ddraw internals.
|
||
|
|
||
|
Ddraw has an export called GetOLEThunkData. The name is chosen to prevent
|
||
|
people from calling it. It is designed to be used by the test harness. One of
|
||
|
the things it can do, is release the exclusive mode mutex. This is the hack
|
||
|
we're exploiting so we can determine the mutex handle.
|
||
|
|
||
|
--*/
|
||
|
|
||
|
BOOL
|
||
|
FindMutex()
|
||
|
{
|
||
|
typedef VOID (WINAPI *_pfn_GetOLEThunkData)(ULONG_PTR dwOrdinal);
|
||
|
|
||
|
HMODULE hMod;
|
||
|
_pfn_GetOLEThunkData pfnGetOLEThunkData;
|
||
|
|
||
|
hMod = GetModuleHandleA("ddraw");
|
||
|
if (!hMod) {
|
||
|
DPFN( eDbgLevelError, "[FindMutex] DirectDraw not loaded");
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
pfnGetOLEThunkData = (_pfn_GetOLEThunkData) GetProcAddress(hMod, "GetOLEThunkData");
|
||
|
if (!pfnGetOLEThunkData) {
|
||
|
DPFN( eDbgLevelError, "[FindMutex] Failed to get GetOLEThunkData API");
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Now we plan to go and find the mutex by getting Ddraw to call
|
||
|
// ReleaseMutex.
|
||
|
//
|
||
|
|
||
|
EnterCriticalSection(&g_csSync);
|
||
|
|
||
|
//
|
||
|
// Set the mutex to the current thread so it can be picked up in the
|
||
|
// ReleaseMutex hook
|
||
|
//
|
||
|
|
||
|
g_dwFindMutexThread = GetCurrentThreadId();
|
||
|
|
||
|
//
|
||
|
// Call to the hard-coded (in ddraw) ReleaseMutex hack which releases the
|
||
|
// mutex
|
||
|
//
|
||
|
|
||
|
pfnGetOLEThunkData(6);
|
||
|
|
||
|
g_dwFindMutexThread = 0;
|
||
|
|
||
|
LeaveCriticalSection(&g_csSync);
|
||
|
|
||
|
return (g_hDDMutex != 0);
|
||
|
}
|
||
|
|
||
|
/*++
|
||
|
|
||
|
Hook WaitForSingleObject to determine when DirectDraw is testing or acquiring
|
||
|
the mutex. If we haven't got the mutex yet, we attempt to find it using our
|
||
|
hack.
|
||
|
|
||
|
--*/
|
||
|
|
||
|
DWORD
|
||
|
APIHOOK(WaitForSingleObject)(
|
||
|
HANDLE hHandle,
|
||
|
DWORD dwMilliSeconds
|
||
|
)
|
||
|
{
|
||
|
if (g_hThread) {
|
||
|
|
||
|
//
|
||
|
// Hack to find the DirectDraw mutex
|
||
|
//
|
||
|
if (!g_hDDMutex) {
|
||
|
FindMutex();
|
||
|
}
|
||
|
|
||
|
if (g_hDDMutex && (hHandle == g_hDDMutex)) {
|
||
|
|
||
|
//
|
||
|
// Use our thread to acquire the mutex. We synchronize since we're
|
||
|
// accessing globals to communicate with our thread.
|
||
|
//
|
||
|
DWORD dwRet;
|
||
|
|
||
|
EnterCriticalSection(&g_csSync);
|
||
|
|
||
|
// Set globals to communicate with our thread
|
||
|
g_dwTime = dwMilliSeconds;
|
||
|
g_dwWait = sWaitForSingleObject;
|
||
|
|
||
|
if (!ResetEvent(g_hDoneEvent))
|
||
|
{
|
||
|
DPFN( eDbgLevelError, "ResetEvent failed. Cannot continue");
|
||
|
return WAIT_FAILED;
|
||
|
}
|
||
|
|
||
|
// Signal our thread to obtain the mutex
|
||
|
if (!SetEvent(g_hWaitEvent))
|
||
|
{
|
||
|
DPFN( eDbgLevelError, "SetEvent failed. Cannot continue");
|
||
|
return WAIT_FAILED;
|
||
|
}
|
||
|
|
||
|
// Wait until the state of the mutex has been determined
|
||
|
WaitForSingleObject(g_hDoneEvent, INFINITE);
|
||
|
|
||
|
// Code to detect the degenerate
|
||
|
if (g_dwWaitRetValue == WAIT_OBJECT_0) {
|
||
|
g_dwMutexOwnerThreadId = GetCurrentThreadId();
|
||
|
}
|
||
|
|
||
|
dwRet = g_dwWaitRetValue;
|
||
|
|
||
|
LeaveCriticalSection(&g_csSync);
|
||
|
|
||
|
return dwRet;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return ORIGINAL_API(WaitForSingleObject)(hHandle, dwMilliSeconds);
|
||
|
}
|
||
|
|
||
|
/*++
|
||
|
|
||
|
Hook ReleaseMutex and release the mutex on our thread.
|
||
|
|
||
|
--*/
|
||
|
|
||
|
BOOL
|
||
|
APIHOOK(ReleaseMutex)(
|
||
|
HANDLE hMutex
|
||
|
)
|
||
|
{
|
||
|
if (g_hThread && (g_dwFindMutexThread == GetCurrentThreadId())) {
|
||
|
|
||
|
//
|
||
|
// We're using our hack to find the DirectDraw mutex
|
||
|
//
|
||
|
DPFN( eDbgLevelInfo, "DDraw exclusive mode mutex found");
|
||
|
g_hDDMutex = hMutex;
|
||
|
|
||
|
// Don't release it, since we never acquired it
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// First try to release it on the current thread. This will only succeed if
|
||
|
// it was obtained on this thread.
|
||
|
//
|
||
|
|
||
|
BOOL bRet = ORIGINAL_API(ReleaseMutex)(hMutex);
|
||
|
|
||
|
if (!bRet && g_hThread && g_hDDMutex && (hMutex == g_hDDMutex)) {
|
||
|
|
||
|
//
|
||
|
// Use our thread to release the mutex. We synchronize since we're
|
||
|
// accessing globals to communicate with our thread.
|
||
|
//
|
||
|
|
||
|
EnterCriticalSection(&g_csSync);
|
||
|
|
||
|
// Set globals to communicate with our thread
|
||
|
g_dwWait = sReleaseMutex;
|
||
|
|
||
|
if (!ResetEvent(g_hDoneEvent))
|
||
|
{
|
||
|
DPFN( eDbgLevelError, "ResetEvent failed. Cannot continue");
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
// Wait until our thread returns
|
||
|
if (!SetEvent(g_hWaitEvent))
|
||
|
{
|
||
|
DPFN( eDbgLevelError, "SetEvent failed. Cannot continue");
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
// Signal our thread to release the mutex
|
||
|
WaitForSingleObject(g_hDoneEvent, INFINITE);
|
||
|
|
||
|
// Detect degenerate case
|
||
|
if (GetCurrentThreadId() != g_dwMutexOwnerThreadId) {
|
||
|
LOGN( eDbgLevelError, "[ReleaseMutex] DirectDraw synchronization error - correcting");
|
||
|
}
|
||
|
|
||
|
if (g_bRetValue) {
|
||
|
g_dwMutexOwnerThreadId = 0;
|
||
|
}
|
||
|
|
||
|
bRet = g_bRetValue;
|
||
|
|
||
|
LeaveCriticalSection(&g_csSync);
|
||
|
|
||
|
}
|
||
|
|
||
|
return bRet;
|
||
|
}
|
||
|
|
||
|
/*++
|
||
|
|
||
|
Clear our handle in case the app frees ddraw and reloads it.
|
||
|
|
||
|
--*/
|
||
|
|
||
|
BOOL
|
||
|
APIHOOK(CloseHandle)(HANDLE hObject)
|
||
|
{
|
||
|
if (g_hThread && (hObject == g_hDDMutex))
|
||
|
{
|
||
|
DPFN( eDbgLevelInfo, "DDraw exclusive mode mutex closed");
|
||
|
g_hDDMutex = 0;
|
||
|
}
|
||
|
|
||
|
return ORIGINAL_API(CloseHandle)(hObject);
|
||
|
}
|
||
|
|
||
|
/*++
|
||
|
|
||
|
Thread used to do all the mutex operations so we can guarantee that the thread
|
||
|
that acquired the mutex is the same one that releases it.
|
||
|
|
||
|
--*/
|
||
|
|
||
|
VOID
|
||
|
WINAPI
|
||
|
ThreadSyncMutex(
|
||
|
LPVOID /*lpParameter*/
|
||
|
)
|
||
|
{
|
||
|
for (;;) {
|
||
|
// Wait until we need to acquire or release the mutex
|
||
|
WaitForSingleObject(g_hWaitEvent, INFINITE);
|
||
|
|
||
|
if (g_dwWait == sWaitForSingleObject) {
|
||
|
// WaitForSingleobject() has been called on the Mutex object
|
||
|
g_dwWaitRetValue = ORIGINAL_API(WaitForSingleObject)(
|
||
|
g_hDDMutex, g_dwTime);
|
||
|
}
|
||
|
else if (g_dwWait == sReleaseMutex) {
|
||
|
// ReleaseMutex has been called
|
||
|
g_bRetValue = ORIGINAL_API(ReleaseMutex)(g_hDDMutex);
|
||
|
}
|
||
|
|
||
|
g_dwWait = sNone;
|
||
|
|
||
|
if (!ResetEvent(g_hWaitEvent))
|
||
|
{
|
||
|
DPFN( eDbgLevelError, "ResetEvent failed. Cannot continue");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (!SetEvent(g_hDoneEvent))
|
||
|
{
|
||
|
DPFN( eDbgLevelError, "SetEvent failed. Cannot continue");
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*++
|
||
|
|
||
|
Register hooked functions
|
||
|
|
||
|
--*/
|
||
|
|
||
|
BOOL
|
||
|
NOTIFY_FUNCTION(
|
||
|
DWORD fdwReason
|
||
|
)
|
||
|
{
|
||
|
if (fdwReason == SHIM_STATIC_DLLS_INITIALIZED) {
|
||
|
|
||
|
//
|
||
|
// We need the critical section all the time
|
||
|
// Security change - InitializeCriticalSection to
|
||
|
// InitializeCriticalSectionAndSpinCount. The high
|
||
|
// bit of 'spincount' is set to 1 for preallocation.
|
||
|
//
|
||
|
if (InitializeCriticalSectionAndSpinCount(&g_csSync, 0x80000000) == FALSE)
|
||
|
{
|
||
|
DPFN( eDbgLevelError, "Failed to initialize critical section");
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Create Events that will be used for the thread synchronization, i.e
|
||
|
// to synchronize this thread and the one we will be creating ahead. We
|
||
|
// don't clean these up by design. We have to do this stuff here, rather
|
||
|
// than in the process attach, since OpenGL apps and others do DirectX
|
||
|
// stuff in their dllmains.
|
||
|
//
|
||
|
|
||
|
g_hWaitEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
|
||
|
if (!g_hWaitEvent) {
|
||
|
DPFN( eDbgLevelError, "Failed to create Event 1");
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
g_hDoneEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
|
||
|
if (!g_hDoneEvent) {
|
||
|
DPFN( eDbgLevelError, "Failed to create Event 2");
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
// Create our thread
|
||
|
g_hThread = CreateThread(NULL, 0,
|
||
|
(LPTHREAD_START_ROUTINE) ThreadSyncMutex, NULL, 0,
|
||
|
NULL);
|
||
|
|
||
|
if (!g_hThread) {
|
||
|
DPFN( eDbgLevelError, "Failed to create Thread");
|
||
|
return FALSE;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
HOOK_BEGIN
|
||
|
|
||
|
CALL_NOTIFY_FUNCTION
|
||
|
|
||
|
APIHOOK_ENTRY(KERNEL32.DLL, WaitForSingleObject)
|
||
|
APIHOOK_ENTRY(KERNEL32.DLL, ReleaseMutex)
|
||
|
APIHOOK_ENTRY(KERNEL32.DLL, CloseHandle)
|
||
|
|
||
|
HOOK_END
|
||
|
|
||
|
|
||
|
IMPLEMENT_SHIM_END
|
||
|
|