forked from dolphin-emu/dolphin
		
	
		
			
	
	
		
			179 lines
		
	
	
		
			5.9 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
		
		
			
		
	
	
			179 lines
		
	
	
		
			5.9 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
|   | // 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(); | ||
|  | } |