1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
|
// Copyright 2008 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <Windows.h>
#include <functional>
#include <optional>
#include <string>
#include <vector>
#include <winternl.h>
#include <fmt/format.h>
#include <fmt/xchar.h>
#include "Common/CommonFuncs.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;
};
void CompatPatchesInstall(LdrWatcher* watcher)
{
watcher->Install({{L"EZFRD64.dll", L"811EZFRD64.DLL"}, [](const LdrDllLoadEvent& event) {
// *EZFRD64 is included 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);
}});
}
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;
}
|