From c5f84eb27d70ba336310111fd2e501bf620551d0 Mon Sep 17 00:00:00 2001 From: kizer Date: Tue, 31 May 2022 00:54:29 +0900 Subject: [PATCH] Add option to use global import hook instead of MinHook for OpenProcess (#860) --- Dalamud.Boot/Dalamud.Boot.vcxproj | 1 + Dalamud.Boot/Dalamud.Boot.vcxproj.filters | 3 + Dalamud.Boot/bootconfig.h | 33 ++- Dalamud.Boot/dllmain.cpp | 5 +- Dalamud.Boot/hooks.cpp | 146 +++++++++++ Dalamud.Boot/hooks.h | 94 ++++++- Dalamud.Boot/logging.cpp | 12 + Dalamud.Boot/pch.h | 2 + Dalamud.Boot/utils.cpp | 301 +++++++++++++--------- Dalamud.Boot/utils.h | 123 ++++++++- Dalamud.Boot/xivfixes.cpp | 217 ++++++++++++++-- Dalamud.Boot/xivfixes.h | 1 + lib/CoreCLR/boot.cpp | 2 +- 13 files changed, 775 insertions(+), 165 deletions(-) create mode 100644 Dalamud.Boot/hooks.cpp diff --git a/Dalamud.Boot/Dalamud.Boot.vcxproj b/Dalamud.Boot/Dalamud.Boot.vcxproj index 7b9c52f46..458969974 100644 --- a/Dalamud.Boot/Dalamud.Boot.vcxproj +++ b/Dalamud.Boot/Dalamud.Boot.vcxproj @@ -121,6 +121,7 @@ NotUsing NotUsing + NotUsing NotUsing diff --git a/Dalamud.Boot/Dalamud.Boot.vcxproj.filters b/Dalamud.Boot/Dalamud.Boot.vcxproj.filters index b4e788664..594264a3a 100644 --- a/Dalamud.Boot/Dalamud.Boot.vcxproj.filters +++ b/Dalamud.Boot/Dalamud.Boot.vcxproj.filters @@ -67,6 +67,9 @@ MinHook + + Dalamud.Boot DLL + diff --git a/Dalamud.Boot/bootconfig.h b/Dalamud.Boot/bootconfig.h index 667369212..661052fe8 100644 --- a/Dalamud.Boot/bootconfig.h +++ b/Dalamud.Boot/bootconfig.h @@ -3,8 +3,23 @@ #include "utils.h" namespace bootconfig { - inline bool is_wait_messagebox() { - return utils::get_env(L"DALAMUD_WAIT_MESSAGEBOX"); + enum WaitMessageboxFlags : int { + None = 0, + BeforeInitialize = 1 << 0, + BeforeDalamudEntrypoint = 1 << 1, + }; + + inline WaitMessageboxFlags wait_messagebox() { + return static_cast(utils::get_env(L"DALAMUD_WAIT_MESSAGEBOX")); + } + + enum DotNetOpenProcessHookMode : int { + ImportHooks = 0, + DirectHook = 1, + }; + + inline DotNetOpenProcessHookMode dotnet_openprocess_hook_mode() { + return static_cast(utils::get_env(L"DALAMUD_DOTNET_OPENPROCESS_HOOKMODE")); } inline bool is_show_console() { @@ -20,6 +35,18 @@ namespace bootconfig { } inline bool is_veh_full() { - return utils::get_env("DALAMUD_IS_VEH_FULL"); + return utils::get_env(L"DALAMUD_IS_VEH_FULL"); + } + + inline bool gamefix_is_enabled(const wchar_t* name) { + static const auto list = utils::get_env_list(L"DALAMUD_GAMEFIX_LIST"); + for (const auto& item : list) + if (item == name) + return true; + return false; + } + + inline std::vector gamefix_unhookdll_list() { + return utils::get_env_list(L"DALAMUD_UNHOOK_DLLS"); } } diff --git a/Dalamud.Boot/dllmain.cpp b/Dalamud.Boot/dllmain.cpp index b31ae0d23..7bccd08e7 100644 --- a/Dalamud.Boot/dllmain.cpp +++ b/Dalamud.Boot/dllmain.cpp @@ -27,7 +27,7 @@ DllExport DWORD WINAPI Initialize(LPVOID lpParam, HANDLE hMainThreadContinue) { logging::print("Dalamud.Boot Injectable, (c) 2021 XIVLauncher Contributors"); logging::print("Built at: " __DATE__ "@" __TIME__); - if (bootconfig::is_wait_messagebox()) + if (bootconfig::wait_messagebox() & bootconfig::WaitMessageboxFlags::BeforeInitialize) MessageBoxW(nullptr, L"Press OK to continue", L"Dalamud Boot", MB_OK); logging::print("Applying fixes..."); @@ -81,6 +81,9 @@ DllExport DWORD WINAPI Initialize(LPVOID lpParam, HANDLE hMainThreadContinue) { // ============================== Dalamud ==================================== // + if (bootconfig::wait_messagebox() & bootconfig::WaitMessageboxFlags::BeforeDalamudEntrypoint) + MessageBoxW(nullptr, L"Press OK to continue", L"Dalamud Boot", MB_OK); + logging::print("Initializing Dalamud..."); entrypoint_fn(lpParam, hMainThreadContinue); logging::print("Done!"); diff --git a/Dalamud.Boot/hooks.cpp b/Dalamud.Boot/hooks.cpp new file mode 100644 index 000000000..367dbde47 --- /dev/null +++ b/Dalamud.Boot/hooks.cpp @@ -0,0 +1,146 @@ +#include "pch.h" + +#include "hooks.h" + +#include "logging.h" + +enum { + LDR_DLL_NOTIFICATION_REASON_LOADED = 1, + LDR_DLL_NOTIFICATION_REASON_UNLOADED = 2, +}; + +struct LDR_DLL_UNLOADED_NOTIFICATION_DATA { + ULONG Flags; //Reserved. + const UNICODE_STRING* FullDllName; //The full path name of the DLL module. + const UNICODE_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. +}; + +struct LDR_DLL_LOADED_NOTIFICATION_DATA { + ULONG Flags; //Reserved. + const UNICODE_STRING* FullDllName; //The full path name of the DLL module. + const UNICODE_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. +}; + +union LDR_DLL_NOTIFICATION_DATA { + LDR_DLL_LOADED_NOTIFICATION_DATA Loaded; + LDR_DLL_UNLOADED_NOTIFICATION_DATA Unloaded; +}; + +using PLDR_DLL_NOTIFICATION_FUNCTION = VOID CALLBACK(_In_ ULONG NotificationReason, _In_ const LDR_DLL_NOTIFICATION_DATA* NotificationData, _In_opt_ PVOID Context); + +static const auto LdrRegisterDllNotification = utils::loaded_module(GetModuleHandleW(L"ntdll.dll")).get_exported_function("LdrRegisterDllNotification"); +static const auto LdrUnregisterDllNotification = utils::loaded_module(GetModuleHandleW(L"ntdll.dll")).get_exported_function("LdrUnregisterDllNotification"); + +hooks::getprocaddress_singleton_import_hook::getprocaddress_singleton_import_hook() + : m_pfnGetProcAddress(GetProcAddress) + , m_thunk([this](HMODULE hModule, LPCSTR lpProcName) { return get_proc_address_handler(hModule, lpProcName); }) { +} + +hooks::getprocaddress_singleton_import_hook::~getprocaddress_singleton_import_hook() { + LdrUnregisterDllNotification(m_ldrDllNotificationCookie); +} + +std::shared_ptr hooks::getprocaddress_singleton_import_hook::set_handler(std::wstring dllName, std::string functionName, void* pfnDetour) { + const auto hModule = GetModuleHandleW(dllName.c_str()); + if (!hModule) + throw std::out_of_range("Specified DLL is not found."); + + const auto pfn = m_pfnGetProcAddress(hModule, functionName.c_str()); + if (!pfn) + throw std::out_of_range("Could not find the specified function."); + + auto& target = m_targetFns[hModule][functionName]; + if (target) + throw std::runtime_error("Specified function has already been hooked."); + + target = pfnDetour; + m_dllNameMap[hModule] = unicode::convert(dllName); + for (const auto& mod : utils::loaded_module::all_modules()) + hook_module(mod); + + return { pfn,[pThis = this->shared_from_this(), hModule, functionName](void*) { + auto& modFns = pThis->m_targetFns[hModule]; + auto& hooks = pThis->m_hooks[hModule]; + modFns.erase(functionName); + hooks.erase(functionName); + if (modFns.empty()) { + pThis->m_targetFns.erase(hModule); + pThis->m_hooks.erase(hModule); + pThis->m_dllNameMap.erase(hModule); + } + } }; +} + +std::shared_ptr hooks::getprocaddress_singleton_import_hook::get_instance() { + static std::weak_ptr s_instance; + std::shared_ptr res; + + res = s_instance.lock(); + if (res) + return res; + + static std::mutex m_mtx; + const auto lock = std::lock_guard(m_mtx); + res = s_instance.lock(); + if (res) + return res; + + s_instance = res = std::make_shared(); + res->initialize(); + return res; +} + +void hooks::getprocaddress_singleton_import_hook::initialize() { + m_getProcAddressHandler = set_handler(L"kernel32.dll", "GetProcAddress", m_thunk.get_thunk()); + + LdrRegisterDllNotification(0, [](ULONG notiReason, const LDR_DLL_NOTIFICATION_DATA* pData, void* context) { + if (notiReason == LDR_DLL_NOTIFICATION_REASON_LOADED) { + const auto dllName = unicode::convert(pData->Loaded.FullDllName->Buffer); + logging::print(R"({} "{}" has been loaded at 0x{:X} ~ 0x{:X} (0x{:X}); finding import table items to hook.)", + LogTag, dllName, + reinterpret_cast(pData->Loaded.DllBase), + reinterpret_cast(pData->Loaded.DllBase) + pData->Loaded.SizeOfImage, + pData->Loaded.SizeOfImage); + reinterpret_cast(context)->hook_module(utils::loaded_module(pData->Loaded.DllBase)); + } else if (notiReason == LDR_DLL_NOTIFICATION_REASON_UNLOADED) { + const auto dllName = unicode::convert(pData->Unloaded.FullDllName->Buffer); + logging::print(R"({} "{}" has been unloaded.)", LogTag, dllName); + } + }, this, &m_ldrDllNotificationCookie); +} + +FARPROC hooks::getprocaddress_singleton_import_hook::get_proc_address_handler(HMODULE hModule, LPCSTR lpProcName) { + if (const auto it1 = m_targetFns.find(hModule); it1 != m_targetFns.end()) { + if (const auto it2 = it1->second.find(lpProcName); it2 != it1->second.end()) { + logging::print(R"({} Redirecting GetProcAddress("{}", "{}"))", LogTag, m_dllNameMap[hModule], lpProcName); + + return reinterpret_cast(it2->second); + } + } + return this->m_pfnGetProcAddress(hModule, lpProcName); +} + +void hooks::getprocaddress_singleton_import_hook::hook_module(const utils::loaded_module& mod) { + if (mod.is_current_process()) + return; + + const auto path = unicode::convert(mod.path().wstring()); + + for (const auto& [hModule, targetFns] : m_targetFns) { + for (const auto& [targetFn, pfnThunk] : targetFns) { + const auto& dllName = m_dllNameMap[hModule]; + if (void* pGetProcAddressImport; mod.find_imported_function_pointer(dllName.c_str(), targetFn.c_str(), 0, pGetProcAddressImport)) { + auto& hook = m_hooks[hModule][targetFn][mod]; + if (!hook) { + logging::print("{} Hooking {}!{} imported by {}", LogTag, dllName, targetFn, unicode::convert(mod.path().wstring())); + + hook.emplace(static_cast(pGetProcAddressImport), pfnThunk); + } + } + } + } +} diff --git a/Dalamud.Boot/hooks.h b/Dalamud.Boot/hooks.h index 3ad907495..547b18fb7 100644 --- a/Dalamud.Boot/hooks.h +++ b/Dalamud.Boot/hooks.h @@ -1,15 +1,21 @@ #pragma once #include +#include #include "utils.h" namespace hooks { + class base_untyped_hook { + public: + virtual ~base_untyped_hook() = default; + }; + template class base_hook; template - class base_hook { + class base_hook : public base_untyped_hook { using TFn = TReturn(TArgs...); private: @@ -22,8 +28,6 @@ namespace hooks { , m_thunk(m_pfnOriginal) { } - virtual ~base_hook() = default; - virtual void set_detour(std::function fn) { if (!fn) m_thunk.set_target(m_pfnOriginal); @@ -61,7 +65,7 @@ namespace hooks { } import_hook(const char* pcszDllName, const char* pcszFunctionName, int hintOrOrdinal) - : import_hook(utils::get_imported_function_pointer(GetModuleHandleW(nullptr), pcszDllName, pcszFunctionName, hintOrOrdinal)) { + : import_hook(utils::loaded_module::current_process().get_imported_function_pointer(pcszDllName, pcszFunctionName, hintOrOrdinal)) { } ~import_hook() override { @@ -119,4 +123,86 @@ namespace hooks { return CallWindowProcW(Base::get_original(), hwnd, msg, wParam, lParam); } }; + + class untyped_import_hook : public base_untyped_hook { + void** const m_ppfnImportTableItem; + void* const m_pfnOriginalImport; + + public: + untyped_import_hook(void** ppfnImportTableItem, void* pThunk) + : m_pfnOriginalImport(*ppfnImportTableItem) + , m_ppfnImportTableItem(ppfnImportTableItem) { + + const utils::memory_tenderizer tenderizer(ppfnImportTableItem, sizeof * ppfnImportTableItem, PAGE_READWRITE); + *ppfnImportTableItem = pThunk; + } + + ~untyped_import_hook() override { + MEMORY_BASIC_INFORMATION mbi{}; + VirtualQuery(m_ppfnImportTableItem, &mbi, sizeof mbi); + if (mbi.State != MEM_COMMIT) + return; + + const utils::memory_tenderizer tenderizer(m_ppfnImportTableItem, sizeof * m_ppfnImportTableItem, PAGE_READWRITE); + *m_ppfnImportTableItem = m_pfnOriginalImport; + } + }; + + class getprocaddress_singleton_import_hook : public std::enable_shared_from_this { + static inline const char* LogTag = "[global_import_hook]"; + + decltype(GetProcAddress)* const m_pfnGetProcAddress; + + utils::thunk m_thunk; + std::shared_ptr m_getProcAddressHandler; + + void* m_ldrDllNotificationCookie{}; + std::map m_dllNameMap; + std::map> m_targetFns; + std::map>>> m_hooks; + + public: + getprocaddress_singleton_import_hook(); + ~getprocaddress_singleton_import_hook(); + + std::shared_ptr set_handler(std::wstring dllName, std::string functionName, void* pfnDetour); + + static std::shared_ptr get_instance(); + + private: + void initialize(); + + FARPROC get_proc_address_handler(HMODULE hModule, LPCSTR lpProcName); + + void hook_module(const utils::loaded_module& mod); + }; + + template + class global_import_hook; + + template + class global_import_hook : public base_untyped_hook { + using TFn = TReturn(TArgs...); + utils::thunk m_thunk; + std::shared_ptr m_singleImportHook; + + public: + global_import_hook(std::wstring dllName, std::string functionName) + : m_thunk(nullptr) { + + m_singleImportHook = getprocaddress_singleton_import_hook::get_instance()->set_handler(dllName, functionName, m_thunk.get_thunk()); + m_thunk.set_target(reinterpret_cast(m_singleImportHook.get())); + } + + virtual void set_detour(std::function fn) { + if (!fn) + m_thunk.set_target(reinterpret_cast(m_singleImportHook.get())); + else + m_thunk.set_target(std::move(fn)); + } + + virtual TReturn call_original(TArgs... args) { + return reinterpret_cast(m_singleImportHook.get())(std::forward(args)...); + } + }; } diff --git a/Dalamud.Boot/logging.cpp b/Dalamud.Boot/logging.cpp index e2ec89d17..fdd6df014 100644 --- a/Dalamud.Boot/logging.cpp +++ b/Dalamud.Boot/logging.cpp @@ -55,6 +55,18 @@ void logging::start_file_logging(const std::filesystem::path& path) { if (s_hLogFile) return; + try { + if (exists(path) && file_size(path) > 1048576) { + auto oldPath = std::filesystem::path(path); + oldPath.replace_extension(".log.old"); + if (exists(oldPath)) + remove(oldPath); + rename(path, oldPath); + } + } catch (...) { + // whatever + } + const auto h = CreateFile(path.wstring().c_str(), GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, diff --git a/Dalamud.Boot/pch.h b/Dalamud.Boot/pch.h index f217f439b..6fb270aae 100644 --- a/Dalamud.Boot/pch.h +++ b/Dalamud.Boot/pch.h @@ -19,6 +19,7 @@ #include #include #include +#include // MSVC Compiler Intrinsic #include @@ -26,6 +27,7 @@ // C++ Standard Libraries #include #include +#include #include #include #include diff --git a/Dalamud.Boot/utils.cpp b/Dalamud.Boot/utils.cpp index f7551b149..14576a603 100644 --- a/Dalamud.Boot/utils.cpp +++ b/Dalamud.Boot/utils.cpp @@ -2,6 +2,167 @@ #include "utils.h" +std::filesystem::path utils::loaded_module::path() const { + std::wstring buf(MAX_PATH, L'\0'); + for (;;) { + if (const auto len = GetModuleFileNameExW(GetCurrentProcess(), m_hModule, &buf[0], static_cast(buf.size())); len != buf.size()) { + if (buf.empty()) + throw std::runtime_error(std::format("Failed to resolve module path: Win32 error {}", GetLastError())); + buf.resize(len); + return buf; + } + + if (buf.size() * 2 < PATHCCH_MAX_CCH) + buf.resize(buf.size() * 2); + else if (auto p = std::filesystem::path(buf); exists(p)) + return p; + else + throw std::runtime_error("Failed to resolve module path: no amount of buffer size would fit the data"); + } +} + +bool utils::loaded_module::owns_address(const void* pAddress) const { + const auto pcAddress = reinterpret_cast(pAddress); + const auto pcModule = reinterpret_cast(m_hModule); + return pcModule <= pcAddress && pcAddress <= pcModule + (is_pe64() ? nt_header64().OptionalHeader.SizeOfImage : nt_header32().OptionalHeader.SizeOfImage); +} + +std::span utils::loaded_module::section_headers() const { + const auto& dosHeader = ref_as(0); + const auto& ntHeader32 = ref_as(dosHeader.e_lfanew); + // Since this does not refer to OptionalHeader32/64 else than its offset, we can use either. + return { IMAGE_FIRST_SECTION(&ntHeader32), ntHeader32.FileHeader.NumberOfSections }; +} + +IMAGE_SECTION_HEADER& utils::loaded_module::section_header(const char* pcszSectionName) const { + for (auto& section : section_headers()) { + if (strncmp(reinterpret_cast(section.Name), pcszSectionName, IMAGE_SIZEOF_SHORT_NAME) == 0) + return section; + } + + throw std::out_of_range(std::format("Section [{}] not found", pcszSectionName)); +} + +std::span utils::loaded_module::section(size_t index) const { + auto& sectionHeader = section_headers()[index]; + return { address(sectionHeader.VirtualAddress), sectionHeader.Misc.VirtualSize }; +} + +std::span utils::loaded_module::section(const char* pcszSectionName) const { + auto& sectionHeader = section_header(pcszSectionName); + return { address(sectionHeader.VirtualAddress), sectionHeader.Misc.VirtualSize }; +} + +template +static bool find_imported_function_pointer_helper(const char* pcBaseAddress, const IMAGE_IMPORT_DESCRIPTOR& desc, const IMAGE_DATA_DIRECTORY& dir, std::string_view reqFunc, uint32_t hintOrOrdinal, void*& ppFunctionAddress) { + const auto importLookupsOversizedSpan = std::span(reinterpret_cast(&pcBaseAddress[desc.OriginalFirstThunk]), (dir.Size - desc.OriginalFirstThunk) / sizeof TEntryType); + const auto importAddressesOversizedSpan = std::span(reinterpret_cast(&pcBaseAddress[desc.FirstThunk]), (dir.Size - desc.FirstThunk) / sizeof TEntryType); + + for (size_t i = 0, i_ = (std::min)(importLookupsOversizedSpan.size(), importAddressesOversizedSpan.size()); i < i_ && importLookupsOversizedSpan[i] && importAddressesOversizedSpan[i]; i++) { + const auto& importLookup = importLookupsOversizedSpan[i]; + const auto& importAddress = importAddressesOversizedSpan[i]; + const auto& importByName = *reinterpret_cast(&pcBaseAddress[importLookup]); + + // Is this entry importing by ordinals? A lot of socket functions are the case. + if (IMAGE_SNAP_BY_ORDINAL32(importLookup)) { + + // Is this the entry? + if (!hintOrOrdinal || IMAGE_ORDINAL32(importLookup) != hintOrOrdinal) + continue; + + // Is this entry not importing by ordinals, and are we using hint exclusively to find the entry? + } else if (reqFunc.empty()) { + + // Is this the entry? + if (importByName.Hint != hintOrOrdinal) + continue; + + } else { + + // Name must be contained in this directory. + auto currFunc = std::string_view(importByName.Name, (std::min)(&pcBaseAddress[dir.Size] - importByName.Name, reqFunc.size())); + currFunc = currFunc.substr(0, strnlen(currFunc.data(), currFunc.size())); + + // Is this the entry? (Case sensitive) + if (reqFunc != currFunc) + continue; + } + + // Found the entry; return the address of the pointer to the target function. + ppFunctionAddress = const_cast(reinterpret_cast(&importAddress)); + return true; + } + + return false; +} + +bool utils::loaded_module::find_imported_function_pointer(const char* pcszDllName, const char* pcszFunctionName, uint32_t hintOrOrdinal, void*& ppFunctionAddress) const { + const auto requestedDllName = std::string_view(pcszDllName, strlen(pcszDllName)); + const auto requestedFunctionName = pcszFunctionName ? std::string_view(pcszFunctionName, strlen(pcszFunctionName)) : std::string_view(); + const auto& directory = data_directory(IMAGE_DIRECTORY_ENTRY_IMPORT); + ppFunctionAddress = nullptr; + + // This span might be too long in terms of meaningful data; it only serves to prevent accessing memory outsides boundaries. + for (const auto& importDescriptor : span_as(directory.VirtualAddress, directory.Size / sizeof IMAGE_IMPORT_DESCRIPTOR)) { + + // Having all zero values signals the end of the table. We didn't find anything. + if (!importDescriptor.OriginalFirstThunk && !importDescriptor.TimeDateStamp && !importDescriptor.ForwarderChain && !importDescriptor.FirstThunk) + return false; + + // Skip invalid entries, just in case. + if (!importDescriptor.Name || !importDescriptor.OriginalFirstThunk) + continue; + + // Name must be contained in this directory. + if (importDescriptor.Name < directory.VirtualAddress) + continue; + auto currentDllName = std::string_view(address_as(importDescriptor.Name), (std::min)(directory.Size - importDescriptor.Name, requestedDllName.size())); + currentDllName = currentDllName.substr(0, strnlen(currentDllName.data(), currentDllName.size())); + + // Is this entry about the DLL that we're looking for? (Case insensitive) + if (requestedDllName.size() != currentDllName.size() || _strcmpi(requestedDllName.data(), currentDllName.data())) + continue; + + if (is_pe64()) { + if (find_imported_function_pointer_helper(address(), importDescriptor, directory, requestedFunctionName, hintOrOrdinal, ppFunctionAddress)) + return true; + } else { + if (find_imported_function_pointer_helper(address(), importDescriptor, directory, requestedFunctionName, hintOrOrdinal, ppFunctionAddress)) + return true; + } + } + + // Found nothing. + return false; +} + +void* utils::loaded_module::get_imported_function_pointer(const char* pcszDllName, const char* pcszFunctionName, uint32_t hintOrOrdinal) const { + if (void* ppImportTableItem{}; find_imported_function_pointer(pcszDllName, pcszFunctionName, hintOrOrdinal, ppImportTableItem)) + return ppImportTableItem; + + throw std::runtime_error("Failed to find import for kernel32!OpenProcess."); +} + +utils::loaded_module utils::loaded_module::current_process() { + return { GetModuleHandleW(nullptr) }; +} + +std::vector utils::loaded_module::all_modules() { + std::vector hModules(128); + for (DWORD dwNeeded{}; EnumProcessModules(GetCurrentProcess(), &hModules[0], static_cast(std::span(hModules).size_bytes()), &dwNeeded) && hModules.size() < dwNeeded;) + hModules.resize(hModules.size() + 128); + + std::vector modules; + modules.reserve(hModules.size()); + for (const auto hModule : hModules) { + if (!hModule) + break; + modules.emplace_back(hModule); + } + + return modules; +} + utils::signature_finder& utils::signature_finder::look_in(const void* pFirst, size_t length) { if (length) m_ranges.emplace_back(std::span(reinterpret_cast(pFirst), length)); @@ -9,21 +170,8 @@ utils::signature_finder& utils::signature_finder::look_in(const void* pFirst, si return *this; } -utils::signature_finder& utils::signature_finder::look_in(const void* pFirst, const void* pLast) { - return look_in(pFirst, reinterpret_cast(pLast) - reinterpret_cast(pFirst)); -} - -utils::signature_finder& utils::signature_finder::look_in(HMODULE hModule, const char* sectionName) { - const auto pcBaseAddress = reinterpret_cast(hModule); - const auto& dosHeader = *reinterpret_cast(&pcBaseAddress[0]); - const auto& ntHeader32 = *reinterpret_cast(&pcBaseAddress[dosHeader.e_lfanew]); - // Since this does not refer to OptionalHeader32/64 else than its offset, we can use either. - const auto sections = std::span(IMAGE_FIRST_SECTION(&ntHeader32), ntHeader32.FileHeader.NumberOfSections); - for (const auto& section : sections) { - if (strncmp(reinterpret_cast(section.Name), sectionName, IMAGE_SIZEOF_SHORT_NAME) == 0) - look_in(pcBaseAddress + section.VirtualAddress, section.Misc.VirtualSize); - } - return *this; +utils::signature_finder& utils::signature_finder::look_in(const loaded_module& m, const char* sectionName) { + return look_in(m.section(sectionName)); } utils::signature_finder& utils::signature_finder::look_for(std::string_view pattern, std::string_view mask, char cExactMatch, char cWildcard) { @@ -229,107 +377,6 @@ std::shared_ptr utils::allocate_executable_heap(size_t len) { }; } -template -static bool find_imported_function_pointer_helper(const char* pcBaseAddress, const IMAGE_IMPORT_DESCRIPTOR& desc, const IMAGE_DATA_DIRECTORY& dir, std::string_view reqFunc, uint32_t hintOrOrdinal, void*& ppFunctionAddress) { - const auto importLookupsOversizedSpan = std::span(reinterpret_cast(&pcBaseAddress[desc.OriginalFirstThunk]), (dir.Size - desc.OriginalFirstThunk) / sizeof TEntryType); - const auto importAddressesOversizedSpan = std::span(reinterpret_cast(&pcBaseAddress[desc.FirstThunk]), (dir.Size - desc.FirstThunk) / sizeof TEntryType); - - for (size_t i = 0, i_ = (std::min)(importLookupsOversizedSpan.size(), importAddressesOversizedSpan.size()); i < i_ && importLookupsOversizedSpan[i] && importAddressesOversizedSpan[i]; i++) { - const auto& importLookup = importLookupsOversizedSpan[i]; - const auto& importAddress = importAddressesOversizedSpan[i]; - const auto& importByName = *reinterpret_cast(&pcBaseAddress[importLookup]); - - // Is this entry importing by ordinals? A lot of socket functions are the case. - if (IMAGE_SNAP_BY_ORDINAL32(importLookup)) { - - // Is this the entry? - if (!hintOrOrdinal || IMAGE_ORDINAL32(importLookup) != hintOrOrdinal) - continue; - - // Is this entry not importing by ordinals, and are we using hint exclusively to find the entry? - } else if (reqFunc.empty()) { - - // Is this the entry? - if (importByName.Hint != hintOrOrdinal) - continue; - - } else { - - // Name must be contained in this directory. - auto currFunc = std::string_view(importByName.Name, (std::min)(&pcBaseAddress[dir.Size] - importByName.Name, reqFunc.size())); - currFunc = currFunc.substr(0, strnlen(currFunc.data(), currFunc.size())); - - // Is this the entry? (Case sensitive) - if (reqFunc != currFunc) - continue; - } - - // Found the entry; return the address of the pointer to the target function. - ppFunctionAddress = const_cast(reinterpret_cast(&importAddress)); - return true; - } - - return false; -} - -bool utils::find_imported_function_pointer(HMODULE hModule, const char* pcszDllName, const char* pcszFunctionName, uint32_t hintOrOrdinal, void*& ppFunctionAddress) { - const auto requestedDllName = std::string_view(pcszDllName, strlen(pcszDllName)); - const auto requestedFunctionName = pcszFunctionName ? std::string_view(pcszFunctionName, strlen(pcszFunctionName)) : std::string_view(); - - ppFunctionAddress = nullptr; - - const auto pcBaseAddress = reinterpret_cast(hModule); - const auto& dosHeader = *reinterpret_cast(&pcBaseAddress[0]); - const auto& ntHeader32 = *reinterpret_cast(&pcBaseAddress[dosHeader.e_lfanew]); - const auto& ntHeader64 = *reinterpret_cast(&pcBaseAddress[dosHeader.e_lfanew]); - const auto bPE32 = ntHeader32.OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC; - const auto pDirectory = bPE32 - ? &ntHeader32.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT] - : &ntHeader64.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]; - - // There should always be an import directory, but the world may break down anytime nowadays. - if (!pDirectory) - return false; - - // This span might be too long in terms of meaningful data; it only serves to prevent accessing memory outsides boundaries. - const auto importDescriptorsOversizedSpan = std::span(reinterpret_cast(&pcBaseAddress[pDirectory->VirtualAddress]), pDirectory->Size / sizeof IMAGE_IMPORT_DESCRIPTOR); - for (const auto& importDescriptor : importDescriptorsOversizedSpan) { - - // Having all zero values signals the end of the table. We didn't find anything. - if (!importDescriptor.OriginalFirstThunk && !importDescriptor.TimeDateStamp && !importDescriptor.ForwarderChain && !importDescriptor.FirstThunk) - return false; - - // Skip invalid entries, just in case. - if (!importDescriptor.Name || !importDescriptor.OriginalFirstThunk) - continue; - - // Name must be contained in this directory. - if (importDescriptor.Name < pDirectory->VirtualAddress) - continue; - auto currentDllName = std::string_view(&pcBaseAddress[importDescriptor.Name], (std::min)(pDirectory->Size - importDescriptor.Name, requestedDllName.size())); - currentDllName = currentDllName.substr(0, strnlen(currentDllName.data(), currentDllName.size())); - - // Is this entry about the DLL that we're looking for? (Case insensitive) - if (requestedDllName.size() != currentDllName.size() || _strcmpi(requestedDllName.data(), currentDllName.data())) - continue; - - if (bPE32 && find_imported_function_pointer_helper(pcBaseAddress, importDescriptor, *pDirectory, requestedFunctionName, hintOrOrdinal, ppFunctionAddress)) - return true; - else if (!bPE32 && find_imported_function_pointer_helper(pcBaseAddress, importDescriptor, *pDirectory, requestedFunctionName, hintOrOrdinal, ppFunctionAddress)) - return true; - } - - // Found nothing. - return false; -} - -void* utils::get_imported_function_pointer(HMODULE hModule, const char* pcszDllName, const char* pcszFunctionName, uint32_t hintOrOrdinal) { - if (void* ppImportTableItem{}; find_imported_function_pointer(GetModuleHandleW(nullptr), pcszDllName, pcszFunctionName, hintOrOrdinal, ppImportTableItem)) - return ppImportTableItem; - - throw std::runtime_error("Failed to find import for kernel32!OpenProcess."); -} - std::shared_ptr utils::create_thunk(void* pfnFunction, void* pThis, uint64_t placeholderValue) { const auto pcBaseFn = reinterpret_cast(pfnFunction); auto sourceCode = std::vector(pcBaseFn, pcBaseFn + 256); @@ -378,7 +425,16 @@ std::wstring utils::get_env(const wchar_t* pcwzName) { template<> std::string utils::get_env(const wchar_t* pcwzName) { - return unicode::convert(get_env(pcwzName)); + return unicode::convert(get_env(pcwzName)); +} + +template<> +int utils::get_env(const wchar_t* pcwzName) { + auto env = get_env(pcwzName); + const auto trimmed = trim(std::wstring_view(env)); + if (trimmed.empty()) + return 0; + return std::wcstol(&trimmed[0], nullptr, 0); } template<> @@ -396,6 +452,17 @@ bool utils::get_env(const wchar_t* pcwzName) { || trimmed == L"y"; } +template<> +std::vector utils::get_env_list(const wchar_t* pcszName) { + const auto src = utils::get_env(pcszName); + auto res = utils::split(src, L","); + for (auto& s : res) + s = utils::trim(s); + if (res.size() == 1 && res[0].empty()) + return {}; + return res; +} + bool utils::is_running_on_linux() { if (get_env(L"XL_WINEONLINUX")) return true; diff --git a/Dalamud.Boot/utils.h b/Dalamud.Boot/utils.h index 6a56809ca..87dfe7447 100644 --- a/Dalamud.Boot/utils.h +++ b/Dalamud.Boot/utils.h @@ -10,14 +10,70 @@ #include "unicode.h" namespace utils { + class loaded_module { + HMODULE m_hModule; + public: + loaded_module() : m_hModule(nullptr) {} + loaded_module(const void* hModule) : m_hModule(reinterpret_cast(const_cast(hModule))) {} + loaded_module(void* hModule) : m_hModule(reinterpret_cast(hModule)) {} + loaded_module(size_t hModule) : m_hModule(reinterpret_cast(hModule)) {} + + std::filesystem::path path() const; + + bool is_current_process() const { return m_hModule == GetModuleHandleW(nullptr); } + bool owns_address(const void* pAddress) const; + + operator HMODULE() const { + return m_hModule; + } + + size_t address_int() const { return reinterpret_cast(m_hModule); } + size_t image_size() const { return is_pe64() ? nt_header64().OptionalHeader.SizeOfImage : nt_header32().OptionalHeader.SizeOfImage; } + char* address(size_t offset = 0) const { return reinterpret_cast(m_hModule) + offset; } + template T* address_as(size_t offset) const { return reinterpret_cast(address(offset)); } + template std::span span_as(size_t offset, size_t count) const { return std::span(reinterpret_cast(address(offset)), count); } + template T& ref_as(size_t offset) const { return *reinterpret_cast(address(offset)); } + + IMAGE_DOS_HEADER& dos_header() const { return ref_as(0); } + IMAGE_NT_HEADERS32& nt_header32() const { return ref_as(dos_header().e_lfanew); } + IMAGE_NT_HEADERS64& nt_header64() const { return ref_as(dos_header().e_lfanew); } + bool is_pe64() const { return nt_header32().OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC; } + + std::span data_directories() const { return is_pe64() ? nt_header64().OptionalHeader.DataDirectory : nt_header32().OptionalHeader.DataDirectory; } + IMAGE_DATA_DIRECTORY& data_directory(size_t index) const { return data_directories()[index]; } + + std::span section_headers() const; + IMAGE_SECTION_HEADER& section_header(const char* pcszSectionName) const; + std::span section(size_t index) const; + std::span section(const char* pcszSectionName) const; + + template TFn* get_exported_function(const char* pcszFunctionName) { + const auto pAddress = GetProcAddress(m_hModule, pcszFunctionName); + if (!pAddress) + throw std::out_of_range(std::format("Exported function \"{}\" not found.", pcszFunctionName)); + return reinterpret_cast(pAddress); + } + + bool find_imported_function_pointer(const char* pcszDllName, const char* pcszFunctionName, uint32_t hintOrOrdinal, void*& ppFunctionAddress) const; + void* get_imported_function_pointer(const char* pcszDllName, const char* pcszFunctionName, uint32_t hintOrOrdinal) const; + template TFn** get_imported_function_pointer(const char* pcszDllName, const char* pcszFunctionName, uint32_t hintOrOrdinal) { return reinterpret_cast(get_imported_function_pointer(pcszDllName, pcszFunctionName, hintOrOrdinal)); } + + static loaded_module current_process(); + static std::vector all_modules(); + }; + class signature_finder { std::vector> m_ranges; std::vector m_patterns; public: signature_finder& look_in(const void* pFirst, size_t length); - signature_finder& look_in(const void* pFirst, const void* pLast); - signature_finder& look_in(HMODULE hModule, const char* sectionName); + signature_finder& look_in(const loaded_module& m, const char* sectionName); + + template + signature_finder& look_in(std::span s) { + return look_in(s.data(), s.size()); + } signature_finder& look_for(std::string_view pattern, std::string_view mask, char cExactMatch = 'x', char cWildcard = '.'); signature_finder& look_for(std::string_view pattern, char wildcardMask); @@ -54,18 +110,12 @@ namespace utils { template memory_tenderizer(std::span s, DWORD dwNewProtect) : memory_tenderizer(&s[0], s.size(), dwNewProtect) {} + template + memory_tenderizer(std::span s, DWORD dwNewProtect) : memory_tenderizer(&s[0], s.size(), dwNewProtect) {} + ~memory_tenderizer(); }; - bool find_imported_function_pointer(HMODULE hModule, const char* pcszDllName, const char* pcszFunctionName, uint32_t hintOrOrdinal, void*& ppFunctionAddress); - - void* get_imported_function_pointer(HMODULE hModule, const char* pcszDllName, const char* pcszFunctionName, uint32_t hintOrOrdinal); - - template - TFn** get_imported_function_pointer(HMODULE hModule, const char* pcszDllName, const char* pcszFunctionName, uint32_t hintOrOrdinal) { - return reinterpret_cast(get_imported_function_pointer(hModule, pcszDllName, pcszFunctionName, hintOrOrdinal)); - } - std::shared_ptr allocate_executable_heap(size_t len); template @@ -115,7 +165,7 @@ namespace utils { } }; - template + template> std::basic_string_view trim(std::basic_string_view view, bool left = true, bool right = true) { if (left) { while (!view.empty() && (view.front() < 255 && std::isspace(view.front()))) @@ -128,6 +178,39 @@ namespace utils { return view; } + template, class TAlloc = std::allocator> + std::basic_string trim(std::basic_string view, bool left = true, bool right = true) { + return std::basic_string(trim(std::basic_string_view(view), left, right)); + } + + template, class TAlloc = std::allocator> + [[nodiscard]] std::vector> split(const std::basic_string& str, const std::basic_string_view& delimiter, size_t maxSplit = SIZE_MAX) { + std::vector> result; + if (delimiter.empty()) { + for (size_t i = 0; i < str.size(); ++i) + result.push_back(str.substr(i, 1)); + } else { + size_t previousOffset = 0, offset; + while (maxSplit && (offset = str.find(delimiter, previousOffset)) != std::string::npos) { + result.push_back(str.substr(previousOffset, offset - previousOffset)); + previousOffset = offset + delimiter.length(); + --maxSplit; + } + result.push_back(str.substr(previousOffset)); + } + return result; + } + + template, class TAlloc = std::allocator> + [[nodiscard]] std::vector> split(const std::basic_string& str, const std::basic_string& delimiter, size_t maxSplit = SIZE_MAX) { + return split(str, std::basic_string_view(delimiter), maxSplit); + } + + template, class TAlloc = std::allocator> + [[nodiscard]] std::vector> split(const std::basic_string& str, const TElem* pcszDelimiter, size_t maxSplit = SIZE_MAX) { + return split(str, std::basic_string_view(pcszDelimiter), maxSplit); + } + template T get_env(const wchar_t* pcwzName) { static_assert(false); @@ -139,6 +222,9 @@ namespace utils { template<> std::string get_env(const wchar_t* pcwzName); + template<> + int get_env(const wchar_t* pcwzName); + template<> bool get_env(const wchar_t* pcwzName); @@ -147,6 +233,19 @@ namespace utils { return get_env(unicode::convert(pcszName).c_str()); } + template + std::vector get_env_list(const wchar_t* pcwzName) { + static_assert(false); + } + + template<> + std::vector get_env_list(const wchar_t* pcwzName); + + template + std::vector get_env_list(const char* pcszName) { + return get_env_list(unicode::convert(pcszName).c_str()); + } + bool is_running_on_linux(); std::filesystem::path get_module_path(HMODULE hModule); diff --git a/Dalamud.Boot/xivfixes.cpp b/Dalamud.Boot/xivfixes.cpp index 0f537e27d..718df2249 100644 --- a/Dalamud.Boot/xivfixes.cpp +++ b/Dalamud.Boot/xivfixes.cpp @@ -2,10 +2,128 @@ #include "xivfixes.h" +#include "bootconfig.h" #include "hooks.h" #include "logging.h" #include "utils.h" +void xivfixes::unhook_dll(bool bApply) { + static const auto LogTag = "[xivfixes:unhook_dll]"; + static const auto LogTagW = L"[xivfixes:unhook_dll]"; + + const auto targetDllNames = bootconfig::gamefix_unhookdll_list(); + + if (!bApply) + return; + + const auto mods = utils::loaded_module::all_modules(); + for (const auto& mod : mods) { + std::filesystem::path path; + try { + path = mod.path(); + logging::print(L"{} Module 0x{:X} ~ 0x{:X} (0x{:X}): \"{}\"", LogTagW, mod.address_int(), mod.address_int() + mod.image_size(), mod.image_size(), path.wstring()); + } catch (const std::exception& e) { + logging::print("{} Module 0x{:X}: Failed to resolve path: {}", LogTag, mod.address_int(), e.what()); + continue; + } + + const auto moduleName = unicode::convert(path.filename().wstring()); + + std::vector buf; + std::string formatBuf; + try { + const auto& sectionHeader = mod.section_header(".text"); + const auto section = mod.span_as(sectionHeader.VirtualAddress, sectionHeader.Misc.VirtualSize); + auto hFsDllRaw = CreateFileW(path.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, 0, nullptr); + if (hFsDllRaw == INVALID_HANDLE_VALUE) { + logging::print("{} Module loaded in current process but could not open file: Win32 error {}", LogTag, GetLastError()); + continue; + } + auto hFsDll = std::unique_ptr(hFsDllRaw, &CloseHandle); + + buf.resize(section.size()); + SetFilePointer(hFsDll.get(), sectionHeader.PointerToRawData, nullptr, FILE_CURRENT); + if (DWORD read{}; ReadFile(hFsDll.get(), &buf[0], static_cast(buf.size()), &read, nullptr)) { + if (read < section.size_bytes()) { + logging::print(L"{} ReadFile: read {} bytes < requested {} bytes", LogTagW, read, section.size_bytes()); + continue; + } + } else { + logging::print(L"{} ReadFile: Win32 error {}", LogTagW, GetLastError()); + continue; + } + + auto doRestore = false; + for (const auto& targetDllName : targetDllNames) { + if (0 == _wcsicmp(path.filename().wstring().c_str(), targetDllName.c_str())) { + doRestore = true; + break; + } + } + + std::optional tenderizer; + for (size_t i = 0, instructionLength = 1, printed = 0; i < buf.size(); i += instructionLength) { + if (section[i] == buf[i]) { + instructionLength = 1; + continue; + } + + const auto rva = sectionHeader.VirtualAddress + i; + nmd_x86_instruction instruction{}; + if (!nmd_x86_decode(§ion[i], section.size() - i, &instruction, NMD_X86_MODE_64, NMD_X86_DECODER_FLAGS_ALL)) { + instructionLength = 1; + if (printed < 64) { + logging::print("{} {}+0x{:0X}: dd {:02X}", LogTag, moduleName, rva, static_cast(section[i])); + printed++; + } + } else { + instructionLength = instruction.length; + if (printed < 64) { + formatBuf.resize(128); + nmd_x86_format(&instruction, &formatBuf[0], reinterpret_cast(§ion[i]), NMD_X86_FORMAT_FLAGS_DEFAULT | NMD_X86_FORMAT_FLAGS_BYTES); + formatBuf.resize(strnlen(&formatBuf[0], formatBuf.size())); + + const auto& directory = mod.data_directory(IMAGE_DIRECTORY_ENTRY_EXPORT); + const auto& exportDirectory = mod.ref_as(directory.VirtualAddress); + const auto names = mod.span_as(exportDirectory.AddressOfNames, exportDirectory.NumberOfNames); + const auto ordinals = mod.span_as(exportDirectory.AddressOfNameOrdinals, exportDirectory.NumberOfNames); + const auto functions = mod.span_as(exportDirectory.AddressOfFunctions, exportDirectory.NumberOfFunctions); + + std::string resolvedExportName; + for (auto j = 0; j < names.size(); ++j) { + if (ordinals[j] > functions.size()) + continue; + + const auto rva = functions[ordinals[j]]; + if (rva == §ion[i] - mod.address()) { + resolvedExportName = std::format("[export:{}]", mod.address_as(names[j])); + break; + } + } + + logging::print("{} {}+0x{:0X}{}: {}", LogTag, moduleName, rva, resolvedExportName, formatBuf); + printed++; + } + } + + if (doRestore) { + if (!tenderizer) + tenderizer.emplace(section, PAGE_EXECUTE_READWRITE); + memcpy(§ion[i], &buf[i], instructionLength); + } + } + + if (tenderizer) + logging::print("{} Verification and overwriting complete.", LogTag); + else if (doRestore) + logging::print("{} Verification complete. Overwriting was not required.", LogTag); + + } catch (const std::exception& e) { + logging::print("{} Error: {}", LogTag, e.what()); + } + } +} + using TFnGetInputDeviceManager = void* (); static TFnGetInputDeviceManager* GetGetInputDeviceManager(HWND hwnd) { static TFnGetInputDeviceManager* pCached = nullptr; @@ -18,7 +136,7 @@ static TFnGetInputDeviceManager* GetGetInputDeviceManager(HWND hwnd) { WNDCLASSEXA wcx{}; GetClassInfoExA(g_hGameInstance, szClassName, &wcx); const auto match = utils::signature_finder() - .look_in(g_hGameInstance, ".text") + .look_in(utils::loaded_module(g_hGameInstance), ".text") .look_for_hex("41 81 fe 19 02 00 00 0f 87 ?? ?? 00 00 0f 84 ?? ?? 00 00") .find_one(); @@ -40,6 +158,11 @@ void xivfixes::prevent_devicechange_crashes(bool bApply) { static std::optional s_hookWndProc; if (bApply) { + if (!bootconfig::gamefix_is_enabled(L"prevent_devicechange_crashes")) { + logging::print("{} Turned off via environment variable.", LogTag); + return; + } + s_hookCreateWindowExA.emplace("user32.dll", "CreateWindowExA", 0); s_hookCreateWindowExA->set_detour([](DWORD dwExStyle, LPCSTR lpClassName, LPCSTR lpWindowName, DWORD dwStyle, int X, int Y, int nWidth, int nHeight, HWND hWndParent, HMENU hMenu, HINSTANCE hInstance, LPVOID lpParam)->HWND { const auto hWnd = s_hookCreateWindowExA->call_original(dwExStyle, lpClassName, lpWindowName, dwStyle, X, Y, nWidth, nHeight, hWndParent, hMenu, hInstance, lpParam); @@ -49,7 +172,7 @@ void xivfixes::prevent_devicechange_crashes(bool bApply) { || 0 != strcmp(lpClassName, "FFXIVGAME")) return hWnd; - logging::print("{} CreateWindow(0x{:08X}, \"{}\", \"{}\", 0x{:08X}, {}, {}, {}, {}, 0x{:X}, 0x{:X}, 0x{:X}, 0x{:X}) called; unhooking CreateWindowExA and hooking WndProc.", + logging::print(R"({} CreateWindow(0x{:08X}, "{}", "{}", 0x{:08X}, {}, {}, {}, {}, 0x{:X}, 0x{:X}, 0x{:X}, 0x{:X}) called; unhooking CreateWindowExA and hooking WndProc.)", LogTag, dwExStyle, lpClassName, lpWindowName, dwStyle, X, Y, nWidth, nHeight, reinterpret_cast(hWndParent), reinterpret_cast(hMenu), reinterpret_cast(hInstance), reinterpret_cast(lpParam)); s_hookCreateWindowExA.reset(); @@ -70,24 +193,34 @@ void xivfixes::prevent_devicechange_crashes(bool bApply) { }); logging::print("{} Enable", LogTag); - } else { - logging::print("{} Disable", LogTag); - s_hookCreateWindowExA.reset(); + } else { + if (s_hookCreateWindowExA) { + logging::print("{} Disable CreateWindowExA", LogTag); + s_hookCreateWindowExA.reset(); + } // This will effectively revert any other WndProc alterations, including Dalamud. - s_hookWndProc.reset(); + if (s_hookWndProc) { + logging::print("{} Disable WndProc", LogTag); + s_hookWndProc.reset(); + } } } void xivfixes::disable_game_openprocess_access_check(bool bApply) { static const char* LogTag = "[xivfixes:disable_game_openprocess_access_check]"; - static std::optional> hook; + static std::optional> s_hook; if (bApply) { - hook.emplace("kernel32.dll", "OpenProcess", 0); - hook->set_detour([](DWORD dwDesiredAccess, BOOL bInheritHandle, DWORD dwProcessId)->HANDLE { - logging::print("{} OpenProcess(0{:08X}, {}, {}) was invoked by thread {}.", LogTag, dwDesiredAccess, bInheritHandle, dwProcessId, GetCurrentThreadId()); + if (!bootconfig::gamefix_is_enabled(L"disable_game_openprocess_access_check")) { + logging::print("{} Turned off via environment variable.", LogTag); + return; + } + + s_hook.emplace("kernel32.dll", "OpenProcess", 0); + s_hook->set_detour([](DWORD dwDesiredAccess, BOOL bInheritHandle, DWORD dwProcessId)->HANDLE { + logging::print("{} OpenProcess(0x{:08X}, {}, {}) was invoked by thread {}.", LogTag, dwDesiredAccess, bInheritHandle, dwProcessId, GetCurrentThreadId()); if (dwProcessId == GetCurrentProcessId()) { // Prevent game from feeling unsafe that it restarts @@ -98,45 +231,75 @@ void xivfixes::disable_game_openprocess_access_check(bool bApply) { } } - return hook->call_original(dwDesiredAccess, bInheritHandle, dwProcessId); + return s_hook->call_original(dwDesiredAccess, bInheritHandle, dwProcessId); }); logging::print("{} Enable", LogTag); } else { - logging::print("{} Disable", LogTag); - hook.reset(); + if (s_hook) { + logging::print("{} Disable OpenProcess", LogTag); + s_hook.reset(); + } } } void xivfixes::redirect_openprocess(bool bApply) { static const char* LogTag = "[xivfixes:redirect_openprocess]"; - static std::optional> hook; + static std::shared_ptr s_hook; if (bApply) { - hook.emplace(OpenProcess); - hook->set_detour([](DWORD dwDesiredAccess, BOOL bInheritHandle, DWORD dwProcessId)->HANDLE { - if (dwProcessId == GetCurrentProcessId()) { - logging::print("{} OpenProcess(0{:08X}, {}, {}) was invoked by thread {}. Redirecting to DuplicateHandle.", LogTag, dwDesiredAccess, bInheritHandle, dwProcessId, GetCurrentThreadId()); + if (!bootconfig::gamefix_is_enabled(L"redirect_openprocess")) { + logging::print("{} Turned off via environment variable.", LogTag); + return; + } - if (HANDLE res; DuplicateHandle(GetCurrentProcess(), GetCurrentProcess(), GetCurrentProcess(), &res, dwDesiredAccess, bInheritHandle, 0)) - return res; + if (bootconfig::dotnet_openprocess_hook_mode() == bootconfig::ImportHooks) { + auto hook = std::make_shared>(L"kernel32.dll", "OpenProcess"); + hook->set_detour([hook = hook.get()](DWORD dwDesiredAccess, BOOL bInheritHandle, DWORD dwProcessId)->HANDLE { + if (dwProcessId == GetCurrentProcessId()) { + logging::print("{} OpenProcess(0x{:08X}, {}, {}) was invoked by thread {}. Redirecting to DuplicateHandle.", LogTag, dwDesiredAccess, bInheritHandle, dwProcessId, GetCurrentThreadId()); - return {}; - } - return hook->call_original(dwDesiredAccess, bInheritHandle, dwProcessId); - }); + if (HANDLE res; DuplicateHandle(GetCurrentProcess(), GetCurrentProcess(), GetCurrentProcess(), &res, dwDesiredAccess, bInheritHandle, 0)) + return res; - logging::print("{} Enable", LogTag); + return {}; + } + return hook->call_original(dwDesiredAccess, bInheritHandle, dwProcessId); + }); + s_hook = std::dynamic_pointer_cast(std::move(hook)); + + logging::print("{} Enable via import_hook", LogTag); + + } else { + auto hook = std::make_shared>(OpenProcess); + hook->set_detour([hook = hook.get()](DWORD dwDesiredAccess, BOOL bInheritHandle, DWORD dwProcessId)->HANDLE { + if (dwProcessId == GetCurrentProcessId()) { + logging::print("{} OpenProcess(0x{:08X}, {}, {}) was invoked by thread {}. Redirecting to DuplicateHandle.", LogTag, dwDesiredAccess, bInheritHandle, dwProcessId, GetCurrentThreadId()); + + if (HANDLE res; DuplicateHandle(GetCurrentProcess(), GetCurrentProcess(), GetCurrentProcess(), &res, dwDesiredAccess, bInheritHandle, 0)) + return res; + + return {}; + } + return hook->call_original(dwDesiredAccess, bInheritHandle, dwProcessId); + }); + s_hook = std::dynamic_pointer_cast(std::move(hook)); + + logging::print("{} Enable via direct_hook", LogTag); + } } else { - logging::print("{} Disable", LogTag); - hook.reset(); + if (s_hook) { + logging::print("{} Disable OpenProcess", LogTag); + s_hook.reset(); + } } } void xivfixes::apply_all(bool bApply) { for (const auto& [taskName, taskFunction] : std::initializer_list> { + { "unhook_dll", &unhook_dll }, { "prevent_devicechange_crashes", &prevent_devicechange_crashes }, { "disable_game_openprocess_access_check", &disable_game_openprocess_access_check }, { "redirect_openprocess", &redirect_openprocess }, diff --git a/Dalamud.Boot/xivfixes.h b/Dalamud.Boot/xivfixes.h index c4a6f3666..97227cd57 100644 --- a/Dalamud.Boot/xivfixes.h +++ b/Dalamud.Boot/xivfixes.h @@ -1,6 +1,7 @@ #pragma once namespace xivfixes { + void unhook_dll(bool bApply); void prevent_devicechange_crashes(bool bApply); void disable_game_openprocess_access_check(bool bApply); void redirect_openprocess(bool bApply); diff --git a/lib/CoreCLR/boot.cpp b/lib/CoreCLR/boot.cpp index ebb81a0ac..01b15811d 100644 --- a/lib/CoreCLR/boot.cpp +++ b/lib/CoreCLR/boot.cpp @@ -110,7 +110,7 @@ int InitializeClrAndGetEntryPoint( logging::print("Loading coreclr... "); if ((result = g_clr->load_runtime(runtimeconfig_path, &runtime_parameters)) != 0) { - logging::print("Failed to load coreclr (err={})", result); + logging::print("Failed to load coreclr (err=0x{:08X})", static_cast(result)); return result; } logging::print("Done!");