summaryrefslogtreecommitdiff
path: root/Source/Core/Common/CompatPatches.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/CompatPatches.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/CompatPatches.cpp')
-rw-r--r--Source/Core/Common/CompatPatches.cpp274
1 files changed, 274 insertions, 0 deletions
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 <Windows.h>
+#include <functional>
+#include <string>
+#include <vector>
+#include <winternl.h>
+
+#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<RtlCreateHeap_t>(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<void()> 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<PIMAGE_DOS_HEADER>(base);
+ auto pe = reinterpret_cast<PIMAGE_NT_HEADERS>(base + mz->e_lfanew);
+ directories = pe->OptionalHeader.DataDirectory;
+ }
+ template <typename T>
+ T GetRva(uint32_t rva)
+ {
+ return reinterpret_cast<T>(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<PIMAGE_IMPORT_DESCRIPTOR>(import_dir->VirtualAddress);
+ import_desc->OriginalFirstThunk; import_desc++)
+ {
+ auto module = GetRva<const char*>(import_desc->Name);
+ auto names = GetRva<PIMAGE_THUNK_DATA>(import_desc->OriginalFirstThunk);
+ auto thunks = GetRva<PIMAGE_THUNK_DATA>(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<PIMAGE_IMPORT_BY_NAME>(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<decltype(thunks[index].u1.Function)>(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<u16>(rhs >> 48);
+ minor = static_cast<u16>(rhs >> 32);
+ build = static_cast<u16>(rhs >> 16);
+ qfe = static_cast<u16>(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<wchar_t*>(path->data()),
+ static_cast<DWORD>(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<u8> 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<VS_FIXEDFILEINFO*>(buf);
+ *version = (static_cast<u64>(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;
+};