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

511 lines
13 KiB
C++

#include <algorithm>
#include <map>
#include <tuple>
#include <Windows.h>
#include <winternl.h>
#include <Common/BitSet.h>
#include <Common/Hook.h>
#include <Common/Log.h>
#include <Common/Path.h>
#include <Common/Rect.h>
#include <Config/Settings/TerminateHotKey.h>
#include <Dll/Dll.h>
#include <DDraw/RealPrimarySurface.h>
#include <Gdi/GuiThread.h>
#include <Gdi/PresentationWindow.h>
#include <Input/Input.h>
#include <Overlay/ConfigWindow.h>
#include <Overlay/Steam.h>
#include <Overlay/Window.h>
#include <Win32/DisplayMode.h>
namespace
{
struct DInputMouseHookData
{
HOOKPROC origHookProc;
LPARAM origHookStruct;
MSLLHOOKSTRUCT hookStruct;
DWORD dpiScale;
};
struct HotKeyData
{
std::function<void(void*)> action;
void* context;
bool onKeyDown;
};
HANDLE g_bmpArrow = nullptr;
SIZE g_bmpArrowSize = {};
Overlay::Control* g_capture = nullptr;
POINT g_cursorPos = {};
POINT g_origCursorPos = { MAXLONG, MAXLONG };
HWND g_cursorWindow = nullptr;
std::map<Input::HotKey, HotKeyData> g_hotKeys;
RECT g_monitorRect = {};
HHOOK g_keyboardHook = nullptr;
HHOOK g_mouseHook = nullptr;
BitSet<VK_LBUTTON, VK_OEM_CLEAR> g_keyState;
DInputMouseHookData g_dinputMouseHookData = {};
decltype(&PhysicalToLogicalPointForPerMonitorDPI) g_physicalToLogicalPointForPerMonitorDPI = nullptr;
LRESULT CALLBACK lowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam);
LRESULT CALLBACK lowLevelMouseProc(int nCode, WPARAM wParam, LPARAM lParam);
POINT physicalToLogicalPoint(POINT pt, DWORD dpiScale);
LRESULT CALLBACK cursorWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
LOG_FUNC("cursorWindowProc", Compat::WindowMessageStruct(hwnd, uMsg, wParam, lParam));
switch (uMsg)
{
case WM_PAINT:
{
PAINTSTRUCT ps = {};
BeginPaint(hwnd, &ps);
HDC dc = CreateCompatibleDC(nullptr);
HGDIOBJ origBmp = SelectObject(dc, g_bmpArrow);
RECT wr = {};
GetWindowRect(hwnd, &wr);
CALL_ORIG_FUNC(StretchBlt)(ps.hdc, 0, 0, wr.right - wr.left, wr.bottom - wr.top,
dc, 0, 0, g_bmpArrowSize.cx, g_bmpArrowSize.cy, SRCCOPY);
SelectObject(dc, origBmp);
DeleteDC(dc);
EndPaint(hwnd, &ps);
return 0;
}
case WM_WINDOWPOSCHANGED:
DDraw::RealPrimarySurface::scheduleOverlayUpdate();
break;
}
return CALL_ORIG_FUNC(DefWindowProcA)(hwnd, uMsg, wParam, lParam);
}
LRESULT WINAPI dinputCallNextHookEx(HHOOK hhk, int nCode, WPARAM wParam, LPARAM lParam)
{
if (lParam == reinterpret_cast<LPARAM>(&g_dinputMouseHookData.hookStruct))
{
lParam = g_dinputMouseHookData.origHookStruct;
}
return CallNextHookEx(hhk, nCode, wParam, lParam);
}
LRESULT CALLBACK dinputLowLevelMouseProc(int nCode, WPARAM wParam, LPARAM lParam)
{
if (HC_ACTION == nCode)
{
auto& data = g_dinputMouseHookData;
data.origHookStruct = lParam;
data.hookStruct = *reinterpret_cast<MSLLHOOKSTRUCT*>(lParam);
if (WM_MOUSEMOVE == wParam)
{
data.hookStruct.pt = physicalToLogicalPoint(data.hookStruct.pt, data.dpiScale);
}
else
{
CALL_ORIG_FUNC(GetCursorPos)(&data.hookStruct.pt);
}
lParam = reinterpret_cast<LPARAM>(&g_dinputMouseHookData.hookStruct);
}
return g_dinputMouseHookData.origHookProc(nCode, wParam, lParam);
}
DWORD getDpiScaleForCursorPos()
{
POINT cp = {};
CALL_ORIG_FUNC(GetCursorPos)(&cp);
return Win32::DisplayMode::getMonitorInfo(cp).dpiScale;
}
LRESULT CALLBACK lowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)
{
if (HC_ACTION == nCode &&
(WM_KEYDOWN == wParam || WM_KEYUP == wParam || WM_SYSKEYDOWN == wParam || WM_SYSKEYUP == wParam))
{
auto llHook = reinterpret_cast<const KBDLLHOOKSTRUCT*>(lParam);
if (static_cast<int>(llHook->vkCode) >= g_keyState.getMin() &&
static_cast<int>(llHook->vkCode) <= g_keyState.getMax())
{
if (WM_KEYDOWN == wParam || WM_SYSKEYDOWN == wParam)
{
g_keyState.set(llHook->vkCode);
}
else
{
g_keyState.reset(llHook->vkCode);
}
}
DWORD pid = 0;
GetWindowThreadProcessId(GetForegroundWindow(), &pid);
if (GetCurrentProcessId() == pid)
{
for (auto& hotkey : g_hotKeys)
{
if (hotkey.first.vk == llHook->vkCode && Input::areModifierKeysDown(hotkey.first.modifiers))
{
if (hotkey.second.onKeyDown == (WM_KEYDOWN == wParam || WM_SYSKEYDOWN == wParam))
{
hotkey.second.action(hotkey.second.context);
}
return 1;
}
}
auto steamWindow = Overlay::Steam::getWindow();
if (steamWindow)
{
PostMessage(steamWindow, wParam, llHook->vkCode, 0);
}
}
}
return CallNextHookEx(nullptr, nCode, wParam, lParam);
}
LRESULT CALLBACK lowLevelMouseProc(int nCode, WPARAM wParam, LPARAM lParam)
{
if (HC_ACTION == nCode)
{
auto& llHook = *reinterpret_cast<MSLLHOOKSTRUCT*>(lParam);
if (WM_MOUSEMOVE == wParam)
{
if (MAXLONG == g_origCursorPos.y)
{
if (llHook.flags & LLMHF_INJECTED)
{
if (MAXLONG == g_origCursorPos.x)
{
g_origCursorPos.x = llHook.pt.x;
}
else
{
g_origCursorPos.y = llHook.pt.y;
}
}
return 1;
}
POINT cp = g_cursorPos;
cp.x += llHook.pt.x - g_origCursorPos.x;
cp.y += llHook.pt.y - g_origCursorPos.y;
cp.x = std::min(std::max(g_monitorRect.left, cp.x), g_monitorRect.right);
cp.y = std::min(std::max(g_monitorRect.top, cp.y), g_monitorRect.bottom);
g_cursorPos = cp;
DDraw::RealPrimarySurface::scheduleOverlayUpdate();
}
if (!g_capture->isEnabled())
{
return 1;
}
auto cp = Input::getRelativeCursorPos();
switch (wParam)
{
case WM_LBUTTONDOWN:
g_capture->onLButtonDown(cp);
break;
case WM_LBUTTONUP:
g_capture->onLButtonUp(cp);
break;
case WM_MOUSEMOVE:
g_capture->onMouseMove(cp);
break;
case WM_MOUSEWHEEL:
g_capture->onMouseWheel(cp, HIWORD(llHook.mouseData));
break;
}
return 1;
}
return CallNextHookEx(nullptr, nCode, wParam, lParam);
}
void onTerminate(void* /*context*/)
{
LOG_INFO << "Terminating application via TerminateHotKey";
TerminateProcess(GetCurrentProcess(), 0);
}
POINT physicalToLogicalPoint(POINT pt, DWORD dpiScale)
{
if (g_physicalToLogicalPointForPerMonitorDPI)
{
g_physicalToLogicalPointForPerMonitorDPI(nullptr, &pt);
return pt;
}
return { MulDiv(pt.x, 100, dpiScale), MulDiv(pt.y, 100, dpiScale) };
}
void resetKeyboardHook()
{
Gdi::GuiThread::execute([]()
{
if (g_keyboardHook)
{
UnhookWindowsHookEx(g_keyboardHook);
}
g_keyState.reset();
g_keyboardHook = CALL_ORIG_FUNC(SetWindowsHookExA)(
WH_KEYBOARD_LL, &lowLevelKeyboardProc, Dll::g_currentModule, 0);
if (!g_keyboardHook)
{
LOG_ONCE("ERROR: Failed to install low level keyboard hook, error code: " << GetLastError());
}
});
}
void resetMouseHook()
{
Gdi::GuiThread::execute([]()
{
if (g_mouseHook)
{
UnhookWindowsHookEx(g_mouseHook);
}
g_origCursorPos = { MAXLONG, MAXLONG };
g_mouseHook = CALL_ORIG_FUNC(SetWindowsHookExA)(
WH_MOUSE_LL, &lowLevelMouseProc, Dll::g_currentModule, 0);
if (g_mouseHook)
{
INPUT inputs[2] = {};
inputs[0].mi.dy = 1;
inputs[0].mi.dwFlags = MOUSEEVENTF_MOVE;
inputs[1].mi.dx = 1;
inputs[1].mi.dwFlags = MOUSEEVENTF_MOVE;
SendInput(2, inputs, sizeof(INPUT));
}
else
{
LOG_ONCE("ERROR: Failed to install low level mouse hook, error code: " << GetLastError());
}
});
}
BOOL WINAPI setCursorPos(int X, int Y)
{
LOG_FUNC("SetCursorPos", X, Y);
auto result = CALL_ORIG_FUNC(SetCursorPos)(X, Y);
if (result && g_mouseHook)
{
resetMouseHook();
}
return LOG_RESULT(result);
}
HHOOK setWindowsHookEx(int idHook, HOOKPROC lpfn, HINSTANCE hmod, DWORD dwThreadId,
decltype(&SetWindowsHookExA) origSetWindowsHookEx)
{
if (hmod && (WH_KEYBOARD_LL == idHook || WH_MOUSE_LL == idHook))
{
auto moduleName = Compat::getModulePath(hmod).stem().string();
if (WH_KEYBOARD_LL == idHook && 0 == _stricmp(moduleName.c_str(), "acgenral"))
{
// Disable the IgnoreAltTab shim
return nullptr;
}
if (WH_MOUSE_LL == idHook &&
(0 == _stricmp(moduleName.c_str(), "dinput") || 0 == _stricmp(moduleName.c_str(), "dinput8")))
{
g_dinputMouseHookData.origHookProc = lpfn;
if (!g_physicalToLogicalPointForPerMonitorDPI)
{
g_dinputMouseHookData.dpiScale = getDpiScaleForCursorPos();
}
lpfn = dinputLowLevelMouseProc;
Compat::hookIatFunction(hmod, "CallNextHookEx", dinputCallNextHookEx);
}
}
HHOOK result = origSetWindowsHookEx(idHook, lpfn, hmod, dwThreadId);
if (result)
{
if (WH_KEYBOARD_LL == idHook)
{
resetKeyboardHook();
}
else if (WH_MOUSE_LL == idHook && g_mouseHook)
{
resetMouseHook();
}
}
return result;
}
HHOOK WINAPI setWindowsHookExA(int idHook, HOOKPROC lpfn, HINSTANCE hmod, DWORD dwThreadId)
{
LOG_FUNC("SetWindowsHookExA", idHook, lpfn, hmod, Compat::hex(dwThreadId));
return LOG_RESULT(setWindowsHookEx(idHook, lpfn, hmod, dwThreadId, CALL_ORIG_FUNC(SetWindowsHookExA)));
}
HHOOK WINAPI setWindowsHookExW(int idHook, HOOKPROC lpfn, HINSTANCE hmod, DWORD dwThreadId)
{
LOG_FUNC("SetWindowsHookExW", idHook, lpfn, hmod, Compat::hex(dwThreadId));
return LOG_RESULT(setWindowsHookEx(idHook, lpfn, hmod, dwThreadId, CALL_ORIG_FUNC(SetWindowsHookExW)));
}
auto toTuple(const Input::HotKey& hotKey)
{
return std::make_tuple(hotKey.vk, hotKey.modifiers);
}
}
namespace Input
{
bool operator<(const HotKey& lhs, const HotKey& rhs)
{
return toTuple(lhs) < toTuple(rhs);
}
Overlay::Control* getCapture()
{
return g_capture;
}
Overlay::Window* getCaptureWindow()
{
return g_capture ? static_cast<Overlay::Window*>(&g_capture->getRoot()) : nullptr;
}
HWND getCursorWindow()
{
return g_cursorWindow;
}
POINT getRelativeCursorPos()
{
auto captureWindow = Input::getCaptureWindow();
if (!captureWindow)
{
return {};
}
const RECT rect = captureWindow->getRect();
const int scaleFactor = captureWindow->getScaleFactor();
auto cp = g_cursorPos;
cp.x /= scaleFactor;
cp.y /= scaleFactor;
cp.x -= rect.left;
cp.y -= rect.top;
return cp;
}
void installHooks()
{
g_bmpArrow = CALL_ORIG_FUNC(LoadImageA)(Dll::g_currentModule, "BMP_ARROW", IMAGE_BITMAP, 0, 0, 0);
BITMAP bm = {};
GetObject(g_bmpArrow, sizeof(bm), &bm);
g_bmpArrowSize = { bm.bmWidth, bm.bmHeight };
g_physicalToLogicalPointForPerMonitorDPI = reinterpret_cast<decltype(&PhysicalToLogicalPointForPerMonitorDPI)>(
GetProcAddress(GetModuleHandle("user32"), "PhysicalToLogicalPointForPerMonitorDPI"));
HOOK_FUNCTION(user32, SetCursorPos, setCursorPos);
HOOK_FUNCTION(user32, SetWindowsHookExA, setWindowsHookExA);
HOOK_FUNCTION(user32, SetWindowsHookExW, setWindowsHookExW);
registerHotKey(Config::terminateHotKey.get(), onTerminate, nullptr, false);
}
bool isKeyDown(int vk)
{
switch (vk)
{
case VK_SHIFT:
return g_keyState.test(VK_LSHIFT) || g_keyState.test(VK_RSHIFT);
case VK_CONTROL:
return g_keyState.test(VK_LCONTROL) || g_keyState.test(VK_RCONTROL);
case VK_MENU:
return g_keyState.test(VK_LMENU) || g_keyState.test(VK_RMENU);
}
if (vk >= g_keyState.getMin() && vk <= g_keyState.getMax())
{
return g_keyState.test(vk);
}
return false;
}
void registerHotKey(const HotKey& hotKey, std::function<void(void*)> action, void* context, bool onKeyDown)
{
if (0 != hotKey.vk)
{
g_hotKeys[hotKey] = { action, context, onKeyDown };
if (!g_keyboardHook)
{
resetKeyboardHook();
}
}
}
void setCapture(Overlay::Control* control)
{
if (control && !control->isVisible())
{
control = nullptr;
}
g_capture = control;
if (control)
{
auto window = getCaptureWindow();
g_monitorRect = Win32::DisplayMode::getMonitorInfo(window->getWindow()).rcMonitor;
if (!g_mouseHook)
{
g_cursorWindow = Gdi::PresentationWindow::create(window->getWindow());
CALL_ORIG_FUNC(SetWindowLongA)(g_cursorWindow, GWL_WNDPROC, reinterpret_cast<LONG>(&cursorWindowProc));
CALL_ORIG_FUNC(SetLayeredWindowAttributes)(g_cursorWindow, RGB(0xFF, 0xFF, 0xFF), 0, LWA_COLORKEY);
g_cursorPos = { (g_monitorRect.left + g_monitorRect.right) / 2, (g_monitorRect.top + g_monitorRect.bottom) / 2 };
CALL_ORIG_FUNC(SetWindowPos)(g_cursorWindow, DDraw::RealPrimarySurface::getTopmost(),
g_cursorPos.x, g_cursorPos.y, g_bmpArrowSize.cx, g_bmpArrowSize.cy,
SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOSENDCHANGING | SWP_SHOWWINDOW);
g_capture->onMouseMove(getRelativeCursorPos());
resetMouseHook();
}
}
else if (g_mouseHook)
{
UnhookWindowsHookEx(g_mouseHook);
g_mouseHook = nullptr;
Gdi::GuiThread::destroyWindow(g_cursorWindow);
g_cursorWindow = nullptr;
}
}
void updateCursor()
{
Gdi::GuiThread::execute([]()
{
if (g_cursorWindow)
{
auto scaleFactor = Gdi::GuiThread::getConfigWindow()->getScaleFactor();
CALL_ORIG_FUNC(SetWindowPos)(g_cursorWindow, DDraw::RealPrimarySurface::getTopmost(),
g_cursorPos.x, g_cursorPos.y, g_bmpArrowSize.cx * scaleFactor, g_bmpArrowSize.cy * scaleFactor,
SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOSENDCHANGING);
}
});
}
}