summaryrefslogtreecommitdiff
path: root/Source/Core/Common/LdrWatcher.cpp
diff options
context:
space:
mode:
authorShawn Hoffman <godisgovernment@gmail.com>2017-06-08 22:15:42 -0700
committerShawn Hoffman <godisgovernment@gmail.com>2017-06-15 21:21:01 -0700
commit1210c74955f1d680eb639317d33f6324b57ec8d3 (patch)
tree9d05fbc27105be9ddb506691d6906a31c3ba0ac4 /Source/Core/Common/LdrWatcher.cpp
parent5a34546698d4b1cb8cc37173a5ebb25b50ce3a4f (diff)
[windows]: Apply compatibility patches to problematic gamepad vibration drivers.
Move ucrtFreadWorkaround to use the new LdrWatcher.
Diffstat (limited to 'Source/Core/Common/LdrWatcher.cpp')
-rw-r--r--Source/Core/Common/LdrWatcher.cpp178
1 files changed, 178 insertions, 0 deletions
diff --git a/Source/Core/Common/LdrWatcher.cpp b/Source/Core/Common/LdrWatcher.cpp
new file mode 100644
index 0000000000..8fe1ee5f72
--- /dev/null
+++ b/Source/Core/Common/LdrWatcher.cpp
@@ -0,0 +1,178 @@
+// Copyright 2008 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#include <Windows.h>
+#include <TlHelp32.h>
+#include <string>
+#include <winternl.h>
+
+#include "Common/LdrWatcher.h"
+
+typedef struct _LDR_DLL_LOADED_NOTIFICATION_DATA
+{
+ ULONG Flags; // Reserved.
+ PCUNICODE_STRING FullDllName; // The full path name of the DLL module.
+ PCUNICODE_STRING BaseDllName; // The base file name of the DLL module.
+ PVOID DllBase; // A pointer to the base address for the DLL in memory.
+ ULONG SizeOfImage; // The size of the DLL image, in bytes.
+} LDR_DLL_LOADED_NOTIFICATION_DATA, *PLDR_DLL_LOADED_NOTIFICATION_DATA;
+
+typedef struct _LDR_DLL_UNLOADED_NOTIFICATION_DATA
+{
+ ULONG Flags; // Reserved.
+ PCUNICODE_STRING FullDllName; // The full path name of the DLL module.
+ PCUNICODE_STRING BaseDllName; // The base file name of the DLL module.
+ PVOID DllBase; // A pointer to the base address for the DLL in memory.
+ ULONG SizeOfImage; // The size of the DLL image, in bytes.
+} LDR_DLL_UNLOADED_NOTIFICATION_DATA, *PLDR_DLL_UNLOADED_NOTIFICATION_DATA;
+
+typedef union _LDR_DLL_NOTIFICATION_DATA
+{
+ LDR_DLL_LOADED_NOTIFICATION_DATA Loaded;
+ LDR_DLL_UNLOADED_NOTIFICATION_DATA Unloaded;
+} LDR_DLL_NOTIFICATION_DATA, *PLDR_DLL_NOTIFICATION_DATA;
+typedef const LDR_DLL_NOTIFICATION_DATA* PCLDR_DLL_NOTIFICATION_DATA;
+
+#define LDR_DLL_NOTIFICATION_REASON_LOADED (1)
+#define LDR_DLL_NOTIFICATION_REASON_UNLOADED (2)
+
+typedef VOID NTAPI LDR_DLL_NOTIFICATION_FUNCTION(_In_ ULONG NotificationReason,
+ _In_ PCLDR_DLL_NOTIFICATION_DATA NotificationData,
+ _In_opt_ PVOID Context);
+typedef LDR_DLL_NOTIFICATION_FUNCTION* PLDR_DLL_NOTIFICATION_FUNCTION;
+
+typedef NTSTATUS(NTAPI* LdrRegisterDllNotification_t)(
+ _In_ ULONG Flags, _In_ PLDR_DLL_NOTIFICATION_FUNCTION NotificationFunction,
+ _In_opt_ PVOID Context, _Out_ PVOID* Cookie);
+
+typedef NTSTATUS(NTAPI* LdrUnregisterDllNotification_t)(_In_ PVOID Cookie);
+
+static void LdrObserverRun(const LdrObserver& observer, PCUNICODE_STRING module_name,
+ uintptr_t base_address)
+{
+ for (auto& needle : observer.module_names)
+ {
+ // Like RtlCompareUnicodeString, but saves dynamically resolving it.
+ // NOTE: Does not compare null terminator.
+ auto compare_length = module_name->Length / sizeof(wchar_t);
+ if (!_wcsnicmp(needle.c_str(), module_name->Buffer, compare_length))
+ observer.action({needle, base_address});
+ }
+}
+
+static VOID DllNotificationCallback(ULONG NotificationReason,
+ PCLDR_DLL_NOTIFICATION_DATA NotificationData, PVOID Context)
+{
+ if (NotificationReason != LDR_DLL_NOTIFICATION_REASON_LOADED)
+ return;
+ auto& data = NotificationData->Loaded;
+ auto observer = static_cast<const LdrObserver*>(Context);
+ LdrObserverRun(*observer, data.BaseDllName, reinterpret_cast<uintptr_t>(data.DllBase));
+}
+
+// This only works on Vista+. On lower platforms, it will be a no-op.
+class LdrDllNotifier
+{
+public:
+ static LdrDllNotifier& GetInstance()
+ {
+ static LdrDllNotifier notifier;
+ return notifier;
+ };
+ void Install(LdrObserver* observer);
+ void Uninstall(LdrObserver* observer);
+
+private:
+ LdrDllNotifier();
+ bool Init();
+ LdrRegisterDllNotification_t LdrRegisterDllNotification{};
+ LdrUnregisterDllNotification_t LdrUnregisterDllNotification{};
+ bool initialized{};
+};
+
+LdrDllNotifier::LdrDllNotifier()
+{
+ initialized = Init();
+}
+
+bool LdrDllNotifier::Init()
+{
+ auto ntdll = GetModuleHandleW(L"ntdll");
+ if (!ntdll)
+ return false;
+ LdrRegisterDllNotification = reinterpret_cast<decltype(LdrRegisterDllNotification)>(
+ GetProcAddress(ntdll, "LdrRegisterDllNotification"));
+ if (!LdrRegisterDllNotification)
+ return false;
+ LdrUnregisterDllNotification = reinterpret_cast<decltype(LdrUnregisterDllNotification)>(
+ GetProcAddress(ntdll, "LdrUnregisterDllNotification"));
+ if (!LdrUnregisterDllNotification)
+ return false;
+ return true;
+}
+
+void LdrDllNotifier::Install(LdrObserver* observer)
+{
+ if (!initialized)
+ return;
+ void* cookie{};
+ if (!NT_SUCCESS(LdrRegisterDllNotification(0, DllNotificationCallback,
+ static_cast<PVOID>(observer), &cookie)))
+ cookie = {};
+ observer->cookie = cookie;
+ return;
+}
+
+void LdrDllNotifier::Uninstall(LdrObserver* observer)
+{
+ if (!initialized)
+ return;
+ LdrUnregisterDllNotification(observer->cookie);
+ observer->cookie = {};
+ return;
+}
+
+LdrWatcher::~LdrWatcher()
+{
+ UninstallAll();
+}
+
+// Needed for RtlInitUnicodeString
+#pragma comment(lib, "ntdll")
+
+bool LdrWatcher::InjectCurrentModules(const LdrObserver& observer)
+{
+ // Use TlHelp32 instead of psapi functions to reduce dolphin's dependency on psapi
+ // (revisit this when Win7 support is dropped).
+ HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, GetCurrentProcessId());
+ if (snapshot == INVALID_HANDLE_VALUE)
+ return false;
+ MODULEENTRY32 entry;
+ entry.dwSize = sizeof(entry);
+ for (BOOL rv = Module32First(snapshot, &entry); rv == TRUE; rv = Module32Next(snapshot, &entry))
+ {
+ UNICODE_STRING module_name;
+ RtlInitUnicodeString(&module_name, entry.szModule);
+ LdrObserverRun(observer, &module_name, reinterpret_cast<uintptr_t>(entry.modBaseAddr));
+ }
+ CloseHandle(snapshot);
+ return true;
+}
+
+void LdrWatcher::Install(const LdrObserver& observer)
+{
+ observers.emplace_back(observer);
+ auto& new_observer = observers.back();
+ // Register for notifications before looking at the list of current modules.
+ // This ensures none are missed, but there is a tiny chance some will be seen twice.
+ LdrDllNotifier::GetInstance().Install(&new_observer);
+ InjectCurrentModules(new_observer);
+}
+
+void LdrWatcher::UninstallAll()
+{
+ for (auto& observer : observers)
+ LdrDllNotifier::GetInstance().Uninstall(&observer);
+ observers.clear();
+}