// Copyright 2008 Dolphin Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include "Common/LdrWatcher.h" #include #include #include #include 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(Context); LdrObserverRun(*observer, data.BaseDllName, reinterpret_cast(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( GetProcAddress(ntdll, "LdrRegisterDllNotification")); if (!LdrRegisterDllNotification) return false; LdrUnregisterDllNotification = reinterpret_cast( 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(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(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(); }