mirror of
https://github.com/narzoul/DDrawCompat
synced 2024-12-30 08:55:36 +01:00
Fixes the cursor remaining confined if the app crashes while exiting. Tested with Sacred Gold.
374 lines
12 KiB
C++
374 lines
12 KiB
C++
#include <string>
|
|
|
|
#include <Windows.h>
|
|
#include <DbgHelp.h>
|
|
#include <ShellScalingApi.h>
|
|
#include <timeapi.h>
|
|
#include <Uxtheme.h>
|
|
|
|
#include <Common/Hook.h>
|
|
#include <Common/Log.h>
|
|
#include <Common/Path.h>
|
|
#include <Common/ScopedCriticalSection.h>
|
|
#include <Common/Time.h>
|
|
#include <Config/Parser.h>
|
|
#include <Config/Settings/CrashDump.h>
|
|
#include <Config/Settings/DesktopResolution.h>
|
|
#include <Config/Settings/DpiAwareness.h>
|
|
#include <Config/Settings/FullscreenMode.h>
|
|
#include <D3dDdi/Hooks.h>
|
|
#include <DDraw/DirectDraw.h>
|
|
#include <DDraw/Hooks.h>
|
|
#include <DDraw/LogUsedResourceFormat.h>
|
|
#include <Direct3d/Hooks.h>
|
|
#include <Dll/Dll.h>
|
|
#include <Gdi/Gdi.h>
|
|
#include <Gdi/GuiThread.h>
|
|
#include <Gdi/PresentationWindow.h>
|
|
#include <Gdi/VirtualScreen.h>
|
|
#include <Input/Input.h>
|
|
#include <Overlay/Steam.h>
|
|
#include <Win32/DisplayMode.h>
|
|
#include <Win32/DpiAwareness.h>
|
|
#include <Win32/MemoryManagement.h>
|
|
#include <Win32/Registry.h>
|
|
#include <Win32/Thread.h>
|
|
#include <Win32/Version.h>
|
|
#include <Win32/Winmm.h>
|
|
|
|
HRESULT WINAPI SetAppCompatData(DWORD, DWORD);
|
|
|
|
namespace
|
|
{
|
|
const DWORD DISABLE_MAX_WINDOWED_MODE = 12;
|
|
|
|
Compat::CriticalSection g_crashDumpCs;
|
|
std::filesystem::path g_crashDumpPath;
|
|
|
|
template <typename Result, typename... Params>
|
|
using FuncPtr = Result(WINAPI*)(Params...);
|
|
|
|
template <FARPROC(Dll::Procs::* origFunc)>
|
|
const char* getFuncName();
|
|
|
|
#define DEFINE_FUNC_NAME(func) template <> const char* getFuncName<&Dll::Procs::func>() { return #func; }
|
|
VISIT_PUBLIC_DDRAW_PROCS(DEFINE_FUNC_NAME)
|
|
#undef DEFINE_FUNC_NAME
|
|
|
|
void installHooks();
|
|
void onDirectDrawCreate(GUID* lpGUID, LPDIRECTDRAW* lplpDD, IUnknown* pUnkOuter);
|
|
void onDirectDrawCreate(GUID* lpGUID, LPVOID* lplpDD, REFIID iid, IUnknown* pUnkOuter);
|
|
|
|
template <FARPROC(Dll::Procs::* origFunc), typename OrigFuncPtrType, typename FirstParam, typename... Params>
|
|
HRESULT WINAPI directDrawFunc(FirstParam firstParam, Params... params)
|
|
{
|
|
LOG_FUNC(getFuncName<origFunc>(), firstParam, params...);
|
|
installHooks();
|
|
if constexpr (&Dll::Procs::DirectDrawCreate == origFunc || &Dll::Procs::DirectDrawCreateEx == origFunc)
|
|
{
|
|
DDraw::DirectDraw::suppressEmulatedDirectDraw(firstParam);
|
|
}
|
|
HRESULT result = reinterpret_cast<OrigFuncPtrType>(Dll::g_origProcs.*origFunc)(firstParam, params...);
|
|
if constexpr (&Dll::Procs::DirectDrawCreate == origFunc || &Dll::Procs::DirectDrawCreateEx == origFunc)
|
|
{
|
|
if (SUCCEEDED(result))
|
|
{
|
|
onDirectDrawCreate(firstParam, params...);
|
|
}
|
|
}
|
|
return LOG_RESULT(result);
|
|
}
|
|
|
|
void installHooks()
|
|
{
|
|
if (!Dll::g_isHooked)
|
|
{
|
|
Dll::g_isHooked = true;
|
|
DDraw::SuppressResourceFormatLogs suppressResourceFormatLogs;
|
|
Overlay::Steam::installHooks();
|
|
LOG_INFO << "Installing display mode hooks";
|
|
Win32::DisplayMode::installHooks();
|
|
LOG_INFO << "Installing registry hooks";
|
|
Win32::Registry::installHooks();
|
|
LOG_INFO << "Installing Direct3D driver hooks";
|
|
D3dDdi::installHooks();
|
|
Gdi::VirtualScreen::init();
|
|
|
|
CompatPtr<IDirectDraw> dd;
|
|
HRESULT result = CALL_ORIG_PROC(DirectDrawCreate)(nullptr, &dd.getRef(), nullptr);
|
|
if (FAILED(result))
|
|
{
|
|
LOG_INFO << "ERROR: Failed to create a DirectDraw object for hooking: " << Compat::hex(result);
|
|
return;
|
|
}
|
|
|
|
CompatPtr<IDirectDraw7> dd7;
|
|
result = CALL_ORIG_PROC(DirectDrawCreateEx)(
|
|
nullptr, reinterpret_cast<void**>(&dd7.getRef()), IID_IDirectDraw7, nullptr);
|
|
if (FAILED(result))
|
|
{
|
|
LOG_INFO << "ERROR: Failed to create a DirectDraw object for hooking: " << Compat::hex(result);
|
|
return;
|
|
}
|
|
|
|
CompatVtable<IDirectDrawVtbl>::s_origVtable = *dd.get()->lpVtbl;
|
|
result = dd->SetCooperativeLevel(dd, nullptr, DDSCL_NORMAL);
|
|
if (SUCCEEDED(result))
|
|
{
|
|
CompatVtable<IDirectDraw7Vtbl>::s_origVtable = *dd7.get()->lpVtbl;
|
|
dd7->SetCooperativeLevel(dd7, nullptr, DDSCL_NORMAL);
|
|
}
|
|
if (FAILED(result))
|
|
{
|
|
LOG_INFO << "ERROR: Failed to set the cooperative level for hooking: " << Compat::hex(result);
|
|
return;
|
|
}
|
|
|
|
LOG_INFO << "Installing DirectDraw hooks";
|
|
DDraw::installHooks(dd7);
|
|
LOG_INFO << "Installing Direct3D hooks";
|
|
Direct3d::installHooks(dd, dd7);
|
|
LOG_INFO << "Installing GDI hooks";
|
|
Gdi::installHooks();
|
|
Compat::closeDbgEng();
|
|
LOG_INFO << "Finished installing hooks";
|
|
}
|
|
}
|
|
|
|
unsigned WINAPI installHooksThreadProc(LPVOID /*lpParameter*/)
|
|
{
|
|
installHooks();
|
|
return 0;
|
|
}
|
|
|
|
bool isOtherDDrawWrapperLoaded()
|
|
{
|
|
const auto currentDllPath(Compat::getModulePath(Dll::g_currentModule));
|
|
const auto ddrawDllPath(Compat::replaceFilename(currentDllPath, "ddraw.dll"));
|
|
const auto dciman32DllPath(Compat::replaceFilename(currentDllPath, "dciman32.dll"));
|
|
|
|
return (!Compat::isEqual(currentDllPath, ddrawDllPath) && GetModuleHandleW(ddrawDllPath.c_str())) ||
|
|
(!Compat::isEqual(currentDllPath, dciman32DllPath) && GetModuleHandleW(dciman32DllPath.c_str()));
|
|
}
|
|
|
|
void onDirectDrawCreate(GUID* lpGUID, LPDIRECTDRAW* lplpDD, IUnknown* /*pUnkOuter*/)
|
|
{
|
|
return DDraw::DirectDraw::onCreate(lpGUID, *CompatPtr<IDirectDraw7>::from(*lplpDD));
|
|
}
|
|
|
|
void onDirectDrawCreate(GUID* lpGUID, LPVOID* lplpDD, REFIID /*iid*/, IUnknown* /*pUnkOuter*/)
|
|
{
|
|
return DDraw::DirectDraw::onCreate(lpGUID, *CompatPtr<IDirectDraw7>::from(static_cast<IDirectDraw7*>(*lplpDD)));
|
|
}
|
|
|
|
void printEnvironmentVariable(const char* var)
|
|
{
|
|
LOG_INFO << "Environment variable " << var << " = \"" << Dll::getEnvVar(var) << '"';
|
|
}
|
|
|
|
HRESULT WINAPI setAppCompatData(DWORD param1, DWORD param2)
|
|
{
|
|
LOG_FUNC("SetAppCompatData", param1, param2);
|
|
if (DISABLE_MAX_WINDOWED_MODE == param1)
|
|
{
|
|
return LOG_RESULT(S_OK);
|
|
}
|
|
return LOG_RESULT(CALL_ORIG_PROC(SetAppCompatData)(param1, param2));
|
|
}
|
|
|
|
LPTOP_LEVEL_EXCEPTION_FILTER WINAPI setUnhandledExceptionFilter(LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter)
|
|
{
|
|
LOG_FUNC("SetUnhandledExceptionFilter", Compat::funcPtrToStr(lpTopLevelExceptionFilter));
|
|
LOG_ONCE("Suppressed new unhandled exception filter: " << Compat::funcPtrToStr(lpTopLevelExceptionFilter));
|
|
return LOG_RESULT(nullptr);
|
|
}
|
|
|
|
LONG WINAPI unhandledExceptionFilter(EXCEPTION_POINTERS* ExceptionInfo)
|
|
{
|
|
Compat::ScopedCriticalSection lock(g_crashDumpCs);
|
|
BOOL result = FALSE;
|
|
DWORD error = 0;
|
|
HANDLE dumpFile = INVALID_HANDLE_VALUE;
|
|
|
|
LOG_INFO << "Terminating application due to unhandled exception: "
|
|
<< Compat::hex(ExceptionInfo->ExceptionRecord->ExceptionCode);
|
|
|
|
const auto writeDump = reinterpret_cast<decltype(&MiniDumpWriteDump)>(
|
|
GetProcAddress(GetModuleHandle("dbghelp"), "MiniDumpWriteDump"));
|
|
|
|
if (writeDump)
|
|
{
|
|
dumpFile = CreateFileW(g_crashDumpPath.native().c_str(),
|
|
GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
|
|
if (INVALID_HANDLE_VALUE == dumpFile)
|
|
{
|
|
error = GetLastError();
|
|
}
|
|
else
|
|
{
|
|
|
|
MINIDUMP_EXCEPTION_INFORMATION mei = {};
|
|
mei.ThreadId = GetCurrentThreadId();
|
|
mei.ExceptionPointers = ExceptionInfo;
|
|
result = writeDump(GetCurrentProcess(), GetCurrentProcessId(), dumpFile,
|
|
static_cast<MINIDUMP_TYPE>(Config::crashDump.get()), &mei, nullptr, nullptr);
|
|
if (!result)
|
|
{
|
|
error = GetLastError();
|
|
}
|
|
CloseHandle(dumpFile);
|
|
}
|
|
}
|
|
|
|
if (result)
|
|
{
|
|
LOG_INFO << "Crash dump has been written to: " << g_crashDumpPath.native().c_str();
|
|
}
|
|
else if (!writeDump)
|
|
{
|
|
LOG_INFO << "Failed to load procedure MiniDumpWriteDump to create a crash dump";
|
|
}
|
|
else if (INVALID_HANDLE_VALUE == dumpFile)
|
|
{
|
|
LOG_INFO << "Failed to create crash dump file: " << Compat::hex(error);
|
|
}
|
|
else
|
|
{
|
|
LOG_INFO << "Failed to write crash dump: " << Compat::hex(error);
|
|
}
|
|
|
|
TerminateProcess(GetCurrentProcess(), 0);
|
|
return EXCEPTION_CONTINUE_SEARCH;
|
|
}
|
|
}
|
|
|
|
#define LOAD_ORIG_PROC(proc) \
|
|
Dll::g_origProcs.proc = Compat::getProcAddress(origModule, #proc);
|
|
|
|
#define HOOK_DDRAW_PROC(proc) \
|
|
Dll::g_newProcs.proc = reinterpret_cast<FARPROC>( \
|
|
static_cast<decltype(&proc)>(&directDrawFunc<&Dll::Procs::proc, decltype(&proc)>)); \
|
|
Compat::hookFunction( \
|
|
reinterpret_cast<void*&>(Dll::g_origProcs.proc), \
|
|
static_cast<decltype(&proc)>(&directDrawFunc<&Dll::Procs::proc, decltype(&proc)>), \
|
|
#proc);
|
|
|
|
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
|
|
{
|
|
static bool skipDllMain = false;
|
|
if (skipDllMain)
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
if (fdwReason == DLL_PROCESS_ATTACH)
|
|
{
|
|
Dll::g_currentModule = hinstDLL;
|
|
if (isOtherDDrawWrapperLoaded())
|
|
{
|
|
skipDllMain = true;
|
|
return TRUE;
|
|
}
|
|
|
|
auto processPath(Compat::getModulePath(nullptr));
|
|
LOG_INFO << "Process path: " << processPath.u8string();
|
|
|
|
auto currentDllPath(Compat::getModulePath(hinstDLL));
|
|
LOG_INFO << "Loading DDrawCompat " << (lpvReserved ? "statically" : "dynamically") << " from " << currentDllPath.u8string();
|
|
printEnvironmentVariable("__COMPAT_LAYER");
|
|
|
|
Config::Parser::loadAllConfigFiles(processPath);
|
|
Compat::Log::initLogging(processPath, Config::logLevel.get());
|
|
|
|
if (Config::Settings::CrashDump::OFF != Config::crashDump.get())
|
|
{
|
|
g_crashDumpPath = processPath;
|
|
if (Compat::isEqual(g_crashDumpPath.extension(), ".exe"))
|
|
{
|
|
g_crashDumpPath.replace_extension();
|
|
}
|
|
g_crashDumpPath.replace_filename(L"DDrawCompat-" + g_crashDumpPath.filename().native());
|
|
g_crashDumpPath += ".dmp";
|
|
|
|
HOOK_FUNCTION(kernel32, SetUnhandledExceptionFilter, setUnhandledExceptionFilter);
|
|
LOG_INFO << "Installing unhandled exception filter for automatic crash dumps";
|
|
auto prevFilter = CALL_ORIG_FUNC(SetUnhandledExceptionFilter)(&unhandledExceptionFilter);
|
|
if (prevFilter)
|
|
{
|
|
LOG_INFO << "Replaced previous unhandled exception filter: " << Compat::funcPtrToStr(prevFilter);
|
|
}
|
|
}
|
|
|
|
auto systemPath(Compat::getSystemPath());
|
|
if (Compat::isEqual(currentDllPath.parent_path(), systemPath))
|
|
{
|
|
LOG_INFO << "DDrawCompat cannot be installed in the Windows system directory";
|
|
return FALSE;
|
|
}
|
|
|
|
auto origDDrawModulePath = systemPath / "ddraw.dll";
|
|
Dll::g_origDDrawModule = LoadLibraryW(origDDrawModulePath.c_str());
|
|
if (!Dll::g_origDDrawModule)
|
|
{
|
|
LOG_INFO << "ERROR: Failed to load system ddraw.dll from " << systemPath.u8string();
|
|
return FALSE;
|
|
}
|
|
|
|
Dll::pinModule(Dll::g_origDDrawModule);
|
|
Dll::pinModule(Dll::g_currentModule);
|
|
|
|
HMODULE origModule = Dll::g_origDDrawModule;
|
|
VISIT_DDRAW_PROCS(LOAD_ORIG_PROC);
|
|
|
|
Dll::g_origDciman32Module = LoadLibraryW((systemPath / "dciman32.dll").c_str());
|
|
if (Dll::g_origDciman32Module)
|
|
{
|
|
origModule = Dll::g_origDciman32Module;
|
|
VISIT_DCIMAN32_PROCS(LOAD_ORIG_PROC);
|
|
}
|
|
|
|
Dll::g_jmpTargetProcs = Dll::g_origProcs;
|
|
Overlay::Steam::init(origDDrawModulePath.c_str());
|
|
|
|
VISIT_PUBLIC_DDRAW_PROCS(HOOK_DDRAW_PROC);
|
|
Compat::hookFunction(reinterpret_cast<void*&>(Dll::g_origProcs.SetAppCompatData),
|
|
static_cast<decltype(&SetAppCompatData)>(&setAppCompatData), "SetAppCompatData");
|
|
|
|
Input::installHooks();
|
|
Win32::MemoryManagement::installHooks();
|
|
Win32::Thread::installHooks();
|
|
Win32::Version::installHooks();
|
|
Win32::Winmm::installHooks();
|
|
Compat::closeDbgEng();
|
|
|
|
CALL_ORIG_FUNC(timeBeginPeriod)(1);
|
|
Win32::DpiAwareness::init();
|
|
SetThemeAppProperties(0);
|
|
Time::init();
|
|
Win32::Thread::applyConfig();
|
|
|
|
if (Config::Settings::FullscreenMode::EXCLUSIVE == Config::fullscreenMode.get())
|
|
{
|
|
CALL_ORIG_PROC(SetAppCompatData)(DISABLE_MAX_WINDOWED_MODE, 1);
|
|
}
|
|
|
|
if (Config::Settings::DesktopResolution::DESKTOP != Config::desktopResolution.get())
|
|
{
|
|
Dll::createThread(&installHooksThreadProc, nullptr, THREAD_PRIORITY_TIME_CRITICAL);
|
|
}
|
|
|
|
LOG_INFO << "DDrawCompat loaded successfully";
|
|
}
|
|
else if (fdwReason == DLL_PROCESS_DETACH)
|
|
{
|
|
CALL_ORIG_FUNC(ClipCursor)(nullptr);
|
|
LOG_INFO << "DDrawCompat detached successfully";
|
|
}
|
|
else if (fdwReason == DLL_THREAD_DETACH)
|
|
{
|
|
Gdi::dllThreadDetach();
|
|
}
|
|
|
|
return TRUE;
|
|
}
|