diff --git a/Source/Core/Common/Common.vcxproj b/Source/Core/Common/Common.vcxproj
index ed8a24bbc1..4c8d2f1b61 100644
--- a/Source/Core/Common/Common.vcxproj
+++ b/Source/Core/Common/Common.vcxproj
@@ -125,6 +125,7 @@
+
@@ -163,6 +164,7 @@
+
@@ -179,6 +181,7 @@
+
@@ -197,7 +200,6 @@
-
@@ -231,4 +233,4 @@
-
+
\ No newline at end of file
diff --git a/Source/Core/Common/Common.vcxproj.filters b/Source/Core/Common/Common.vcxproj.filters
index 7729f21960..10aa2c9d36 100644
--- a/Source/Core/Common/Common.vcxproj.filters
+++ b/Source/Core/Common/Common.vcxproj.filters
@@ -253,6 +253,7 @@
GL\GLExtensions
+
@@ -317,10 +318,11 @@
GL\GLInterface
-
+
+
@@ -328,4 +330,4 @@
-
+
\ No newline at end of file
diff --git a/Source/Core/Common/CompatPatches.cpp b/Source/Core/Common/CompatPatches.cpp
new file mode 100644
index 0000000000..f835ac2279
--- /dev/null
+++ b/Source/Core/Common/CompatPatches.cpp
@@ -0,0 +1,274 @@
+// Copyright 2008 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#include
+#include
+#include
+#include
+#include
+
+#include "Common/CommonTypes.h"
+#include "Common/LdrWatcher.h"
+#include "Common/StringUtil.h"
+
+typedef NTSTATUS(NTAPI* PRTL_HEAP_COMMIT_ROUTINE)(IN PVOID Base, IN OUT PVOID* CommitAddress,
+ IN OUT PSIZE_T CommitSize);
+
+typedef struct _RTL_HEAP_PARAMETERS
+{
+ ULONG Length;
+ SIZE_T SegmentReserve;
+ SIZE_T SegmentCommit;
+ SIZE_T DeCommitFreeBlockThreshold;
+ SIZE_T DeCommitTotalFreeThreshold;
+ SIZE_T MaximumAllocationSize;
+ SIZE_T VirtualMemoryThreshold;
+ SIZE_T InitialCommit;
+ SIZE_T InitialReserve;
+ PRTL_HEAP_COMMIT_ROUTINE CommitRoutine;
+ SIZE_T Reserved[2];
+} RTL_HEAP_PARAMETERS, *PRTL_HEAP_PARAMETERS;
+
+typedef PVOID (*RtlCreateHeap_t)(_In_ ULONG Flags, _In_opt_ PVOID HeapBase,
+ _In_opt_ SIZE_T ReserveSize, _In_opt_ SIZE_T CommitSize,
+ _In_opt_ PVOID Lock, _In_opt_ PRTL_HEAP_PARAMETERS Parameters);
+
+static HANDLE WINAPI HeapCreateLow4GB(_In_ DWORD flOptions, _In_ SIZE_T dwInitialSize,
+ _In_ SIZE_T dwMaximumSize)
+{
+ auto ntdll = GetModuleHandleW(L"ntdll");
+ if (!ntdll)
+ return nullptr;
+ auto RtlCreateHeap = reinterpret_cast(GetProcAddress(ntdll, "RtlCreateHeap"));
+ if (!RtlCreateHeap)
+ return nullptr;
+ // These values are arbitrary; just change them if problems are encountered later.
+ uintptr_t target_addr = 0x00200000;
+ size_t max_heap_size = 0x01000000;
+ uintptr_t highest_addr = (1ull << 32) - max_heap_size;
+ void* low_heap = nullptr;
+ for (; !low_heap && target_addr <= highest_addr; target_addr += 0x1000)
+ low_heap = VirtualAlloc((void*)target_addr, max_heap_size, MEM_RESERVE, PAGE_READWRITE);
+ if (!low_heap)
+ return nullptr;
+ return RtlCreateHeap(0, low_heap, 0, 0, nullptr, nullptr);
+}
+
+static bool ModifyProtectedRegion(void* address, size_t size, std::function func)
+{
+ DWORD old_protect;
+ if (!VirtualProtect(address, size, PAGE_READWRITE, &old_protect))
+ return false;
+ func();
+ if (!VirtualProtect(address, size, old_protect, &old_protect))
+ return false;
+ return true;
+}
+
+// Does not do input sanitization - assumes well-behaved input since Ldr has already parsed it.
+class ImportPatcher
+{
+public:
+ ImportPatcher(uintptr_t module_base) : base(module_base)
+ {
+ auto mz = reinterpret_cast(base);
+ auto pe = reinterpret_cast(base + mz->e_lfanew);
+ directories = pe->OptionalHeader.DataDirectory;
+ }
+ template
+ T GetRva(uint32_t rva)
+ {
+ return reinterpret_cast(base + rva);
+ }
+ bool PatchIAT(const char* module_name, const char* function_name, void* value)
+ {
+ auto import_dir = &directories[IMAGE_DIRECTORY_ENTRY_IMPORT];
+ for (auto import_desc = GetRva(import_dir->VirtualAddress);
+ import_desc->OriginalFirstThunk; import_desc++)
+ {
+ auto module = GetRva(import_desc->Name);
+ auto names = GetRva(import_desc->OriginalFirstThunk);
+ auto thunks = GetRva(import_desc->FirstThunk);
+ if (!stricmp(module, module_name))
+ {
+ for (auto name = names; name->u1.Function; name++)
+ {
+ if (!IMAGE_SNAP_BY_ORDINAL(name->u1.Ordinal))
+ {
+ auto import = GetRva(name->u1.AddressOfData);
+ if (!strcmp(import->Name, function_name))
+ {
+ auto index = name - names;
+ return ModifyProtectedRegion(&thunks[index], sizeof(thunks[index]), [=] {
+ thunks[index].u1.Function =
+ reinterpret_cast(value);
+ });
+ }
+ }
+ }
+ // Function not found
+ return false;
+ }
+ }
+ // Module not found
+ return false;
+ }
+
+private:
+ uintptr_t base;
+ PIMAGE_DATA_DIRECTORY directories;
+};
+
+struct UcrtPatchInfo
+{
+ u32 checksum;
+ u32 rva;
+ u32 length;
+};
+
+bool ApplyUcrtPatch(const wchar_t* name, const UcrtPatchInfo& patch)
+{
+ auto module = GetModuleHandleW(name);
+ if (!module)
+ return false;
+ auto pe = (PIMAGE_NT_HEADERS)((uintptr_t)module + ((PIMAGE_DOS_HEADER)module)->e_lfanew);
+ if (pe->OptionalHeader.CheckSum != patch.checksum)
+ return false;
+ void* patch_addr = (void*)((uintptr_t)module + patch.rva);
+ size_t patch_size = patch.length;
+ ModifyProtectedRegion(patch_addr, patch_size, [=] { memset(patch_addr, 0x90, patch_size); });
+ FlushInstructionCache(GetCurrentProcess(), patch_addr, patch_size);
+ return true;
+}
+
+#pragma comment(lib, "version.lib")
+
+struct Version
+{
+ u16 major;
+ u16 minor;
+ u16 build;
+ u16 qfe;
+ Version& operator=(u64&& rhs)
+ {
+ major = static_cast(rhs >> 48);
+ minor = static_cast(rhs >> 32);
+ build = static_cast(rhs >> 16);
+ qfe = static_cast(rhs);
+ return *this;
+ }
+};
+
+static bool GetModulePath(const wchar_t* name, std::wstring* path)
+{
+ auto module = GetModuleHandleW(name);
+ if (module == nullptr)
+ return false;
+ DWORD path_len = MAX_PATH;
+retry:
+ path->resize(path_len);
+ path_len = GetModuleFileNameW(module, const_cast(path->data()),
+ static_cast(path->size()));
+ if (!path_len)
+ return false;
+ auto error = GetLastError();
+ if (error == ERROR_SUCCESS)
+ return true;
+ if (error == ERROR_INSUFFICIENT_BUFFER)
+ goto retry;
+ return false;
+}
+
+static bool GetModuleVersion(const wchar_t* name, Version* version)
+{
+ std::wstring path;
+ if (!GetModulePath(name, &path))
+ return false;
+ DWORD handle;
+ DWORD data_len = GetFileVersionInfoSizeW(path.c_str(), &handle);
+ if (!data_len)
+ return false;
+ std::vector block(data_len);
+ if (!GetFileVersionInfoW(path.c_str(), handle, data_len, block.data()))
+ return false;
+ void* buf;
+ UINT buf_len;
+ if (!VerQueryValueW(block.data(), LR"(\)", &buf, &buf_len))
+ return false;
+ auto info = static_cast(buf);
+ *version = (static_cast(info->dwFileVersionMS) << 32) | info->dwFileVersionLS;
+ return true;
+}
+
+void CompatPatchesInstall(LdrWatcher* watcher)
+{
+ watcher->Install({{L"EZFRD64.dll", L"811EZFRD64.DLL"},
+ [](const LdrDllLoadEvent& event) {
+ // *EZFRD64 is incldued in software packages for cheapo third-party gamepads
+ // (and gamepad adapters). The module cannot handle its heap being above 4GB,
+ // which tends to happen very often on modern Windows.
+ // NOTE: The patch will always be applied, but it will only actually avoid the
+ // crash if applied before module initialization (i.e. called on the Ldr
+ // callout path).
+ auto patcher = ImportPatcher(event.base_address);
+ patcher.PatchIAT("kernel32.dll", "HeapCreate", HeapCreateLow4GB);
+ }});
+ watcher->Install({{L"ucrtbase.dll"},
+ [](const LdrDllLoadEvent& event) {
+ // ucrtbase implements caching between fseek/fread, old versions have a bug
+ // such that some reads return incorrect data. This causes noticable bugs
+ // in dolphin since we use these APIs for reading game images.
+ Version version;
+ if (!GetModuleVersion(event.name.c_str(), &version))
+ return;
+ const u16 fixed_build = 10548;
+ if (version.build >= fixed_build)
+ return;
+ const UcrtPatchInfo patches[] = {
+ // 10.0.10240.16384 (th1.150709-1700)
+ {0xF61ED, 0x6AE7B, 5},
+ // 10.0.10240.16390 (th1_st1.150714-1601)
+ {0xF5ED9, 0x6AE7B, 5},
+ // 10.0.10137.0 (th1.150602-2238)
+ {0xF8B5E, 0x63ED6, 2},
+ };
+ for (const auto& patch : patches)
+ {
+ if (ApplyUcrtPatch(event.name.c_str(), patch))
+ return;
+ }
+ // If we reach here, the version is buggy (afaik) and patching failed
+ auto msg = StringFromFormat(
+ "You are running %S version %d.%d.%d.%d.\n"
+ "An important fix affecting Dolphin was introduced in build %d.\n"
+ "You can use Dolphin, but there will be known bugs.\n"
+ "Please update this file by installing the latest Universal C Runtime.\n",
+ event.name.c_str(), version.major, version.minor, version.build,
+ version.qfe, fixed_build);
+ // Use MessageBox for maximal user annoyance
+ MessageBoxA(nullptr, msg.c_str(), "WARNING: BUGGY UCRT VERSION",
+ MB_ICONEXCLAMATION);
+ }});
+}
+
+int __cdecl EnableCompatPatches()
+{
+ static LdrWatcher watcher;
+ CompatPatchesInstall(&watcher);
+ return 0;
+}
+
+// Create a segment which is recognized by the linker to be part of the CRT
+// initialization. XI* = C startup, XC* = C++ startup. "A" placement is reserved
+// for system use. C startup is before C++.
+// Use last C++ slot in hopes that makes using C++ from this code safe.
+#pragma section(".CRT$XCZ", read)
+
+// Place a symbol in the special segment, make it have C linkage so that
+// referencing it doesn't require ugly decorated names.
+// Use /include:enableCompatPatches linker flag to enable this.
+extern "C" {
+__declspec(allocate(".CRT$XCZ")) decltype(&EnableCompatPatches)
+ enableCompatPatches = EnableCompatPatches;
+};
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
+#include
+#include
+#include
+
+#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(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();
+}
diff --git a/Source/Core/Common/LdrWatcher.h b/Source/Core/Common/LdrWatcher.h
new file mode 100644
index 0000000000..7ec1fdf7c1
--- /dev/null
+++ b/Source/Core/Common/LdrWatcher.h
@@ -0,0 +1,38 @@
+// Copyright 2008 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include
+#include
+#include
+#include
+
+struct LdrDllLoadEvent
+{
+ const std::wstring& name;
+ uintptr_t base_address;
+};
+
+class LdrObserver
+{
+public:
+ std::vector module_names;
+ // NOTE: This may be called from a Ldr callout. While most things are probably fine, try to
+ // keep things as simple as possible, and just queue real work onto something else.
+ std::function action;
+ void* cookie{};
+};
+
+class LdrWatcher
+{
+public:
+ void Install(const LdrObserver& observer);
+ ~LdrWatcher();
+
+private:
+ bool InjectCurrentModules(const LdrObserver& observer);
+ void UninstallAll();
+ std::list observers;
+};
diff --git a/Source/Core/Common/ucrtFreadWorkaround.cpp b/Source/Core/Common/ucrtFreadWorkaround.cpp
deleted file mode 100644
index 80ad855fec..0000000000
--- a/Source/Core/Common/ucrtFreadWorkaround.cpp
+++ /dev/null
@@ -1,96 +0,0 @@
-// Copyright 2014 Dolphin Emulator Project
-// Licensed under GPLv2+
-// Refer to the license.txt file included.
-
-#if defined(_WIN32)
-
-#include
-#include "CommonTypes.h"
-
-struct PatchInfo
-{
- const wchar_t* module_name;
- u32 checksum;
- u32 rva;
- u32 length;
-} static const s_patches[] = {
- // 10.0.10240.16384 (th1.150709-1700)
- {L"ucrtbase.dll", 0xF61ED, 0x6AE7B, 5},
- // 10.0.10240.16390 (th1_st1.150714-1601)
- {L"ucrtbase.dll", 0xF5ED9, 0x6AE7B, 5},
- // 10.0.10137.0 (th1.150602-2238)
- {L"ucrtbase.dll", 0xF8B5E, 0x63ED6, 2},
- // 10.0.10150.0 (th1.150616-1659)
- {L"ucrtbased.dll", 0x1C1915, 0x91905, 5},
-};
-
-bool ApplyPatch(const PatchInfo& patch)
-{
- auto module = GetModuleHandleW(patch.module_name);
- if (module == nullptr)
- {
- return false;
- }
-
- auto ucrtbase_pe = (PIMAGE_NT_HEADERS)((uintptr_t)module + ((PIMAGE_DOS_HEADER)module)->e_lfanew);
- if (ucrtbase_pe->OptionalHeader.CheckSum != patch.checksum)
- {
- return false;
- }
-
- void* patch_addr = (void*)((uintptr_t)module + patch.rva);
- size_t patch_size = patch.length;
-
- DWORD old_protect;
- if (!VirtualProtect(patch_addr, patch_size, PAGE_EXECUTE_READWRITE, &old_protect))
- {
- return false;
- }
-
- memset(patch_addr, 0x90, patch_size);
-
- VirtualProtect(patch_addr, patch_size, old_protect, &old_protect);
-
- FlushInstructionCache(GetCurrentProcess(), patch_addr, patch_size);
-
- return true;
-}
-
-int __cdecl EnableucrtFreadWorkaround()
-{
- // This patches ucrtbase such that fseek will always
- // synchronize the file object's internal buffer.
-
- bool applied_at_least_one = false;
- for (const auto& patch : s_patches)
- {
- if (ApplyPatch(patch))
- {
- applied_at_least_one = true;
- }
- }
-
- /* For forward compat, do not fail if patches don't apply (e.g. version mismatch)
- if (!applied_at_least_one) {
- std::abort();
- }
- //*/
-
- return 0;
-}
-
-// Create a segment which is recognized by the linker to be part of the CRT
-// initialization. XI* = C startup, XC* = C++ startup. "A" placement is reserved
-// for system use. Thus, the earliest we can get is XIB (C startup is before
-// C++).
-#pragma section(".CRT$XIB", read)
-
-// Place a symbol in the special segment, make it have C linkage so that
-// referencing it doesn't require ugly decorated names.
-// Use /include:EnableucrtFreadWorkaround linker flag to enable this.
-extern "C" {
-__declspec(allocate(".CRT$XIB")) decltype(&EnableucrtFreadWorkaround)
- ucrtFreadWorkaround = EnableucrtFreadWorkaround;
-};
-
-#endif
diff --git a/Source/VSProps/Base.props b/Source/VSProps/Base.props
index 335d2537a3..b7d04a6602 100644
--- a/Source/VSProps/Base.props
+++ b/Source/VSProps/Base.props
@@ -157,8 +157,8 @@
-
- ucrtFreadWorkaround
+
+ enableCompatPatches
/NODEFAULTLIB:libcmt %(AdditionalOptions)