mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-12 10:17:22 +01:00
Add option to use global import hook instead of MinHook for OpenProcess (#860)
This commit is contained in:
parent
d1e69ed6fd
commit
c5f84eb27d
13 changed files with 775 additions and 165 deletions
|
|
@ -121,6 +121,7 @@
|
|||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="hooks.cpp" />
|
||||
<ClCompile Include="logging.cpp">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
|
||||
|
|
|
|||
|
|
@ -67,6 +67,9 @@
|
|||
<ClCompile Include="..\lib\TsudaKageyu-minhook\src\trampoline.c">
|
||||
<Filter>MinHook</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="hooks.cpp">
|
||||
<Filter>Dalamud.Boot DLL</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="..\lib\CoreCLR\CoreCLR.h">
|
||||
|
|
|
|||
|
|
@ -3,8 +3,23 @@
|
|||
#include "utils.h"
|
||||
|
||||
namespace bootconfig {
|
||||
inline bool is_wait_messagebox() {
|
||||
return utils::get_env<bool>(L"DALAMUD_WAIT_MESSAGEBOX");
|
||||
enum WaitMessageboxFlags : int {
|
||||
None = 0,
|
||||
BeforeInitialize = 1 << 0,
|
||||
BeforeDalamudEntrypoint = 1 << 1,
|
||||
};
|
||||
|
||||
inline WaitMessageboxFlags wait_messagebox() {
|
||||
return static_cast<WaitMessageboxFlags>(utils::get_env<int>(L"DALAMUD_WAIT_MESSAGEBOX"));
|
||||
}
|
||||
|
||||
enum DotNetOpenProcessHookMode : int {
|
||||
ImportHooks = 0,
|
||||
DirectHook = 1,
|
||||
};
|
||||
|
||||
inline DotNetOpenProcessHookMode dotnet_openprocess_hook_mode() {
|
||||
return static_cast<DotNetOpenProcessHookMode>(utils::get_env<int>(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<bool>("DALAMUD_IS_VEH_FULL");
|
||||
return utils::get_env<bool>(L"DALAMUD_IS_VEH_FULL");
|
||||
}
|
||||
|
||||
inline bool gamefix_is_enabled(const wchar_t* name) {
|
||||
static const auto list = utils::get_env_list<std::wstring>(L"DALAMUD_GAMEFIX_LIST");
|
||||
for (const auto& item : list)
|
||||
if (item == name)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
inline std::vector<std::wstring> gamefix_unhookdll_list() {
|
||||
return utils::get_env_list<std::wstring>(L"DALAMUD_UNHOOK_DLLS");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ DllExport DWORD WINAPI Initialize(LPVOID lpParam, HANDLE hMainThreadContinue) {
|
|||
logging::print<logging::I>("Dalamud.Boot Injectable, (c) 2021 XIVLauncher Contributors");
|
||||
logging::print<logging::I>("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<logging::I>("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<logging::I>("Initializing Dalamud...");
|
||||
entrypoint_fn(lpParam, hMainThreadContinue);
|
||||
logging::print<logging::I>("Done!");
|
||||
|
|
|
|||
146
Dalamud.Boot/hooks.cpp
Normal file
146
Dalamud.Boot/hooks.cpp
Normal file
|
|
@ -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<NTSTATUS(NTAPI)(ULONG Flags, PLDR_DLL_NOTIFICATION_FUNCTION NotificationFunction, PVOID Context, PVOID* Cookie)>("LdrRegisterDllNotification");
|
||||
static const auto LdrUnregisterDllNotification = utils::loaded_module(GetModuleHandleW(L"ntdll.dll")).get_exported_function<NTSTATUS(NTAPI)(PVOID Cookie)>("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<void> 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<std::string>(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> hooks::getprocaddress_singleton_import_hook::get_instance() {
|
||||
static std::weak_ptr<hooks::getprocaddress_singleton_import_hook> s_instance;
|
||||
std::shared_ptr<hooks::getprocaddress_singleton_import_hook> 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<getprocaddress_singleton_import_hook>();
|
||||
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<std::string>(pData->Loaded.FullDllName->Buffer);
|
||||
logging::print<logging::I>(R"({} "{}" has been loaded at 0x{:X} ~ 0x{:X} (0x{:X}); finding import table items to hook.)",
|
||||
LogTag, dllName,
|
||||
reinterpret_cast<size_t>(pData->Loaded.DllBase),
|
||||
reinterpret_cast<size_t>(pData->Loaded.DllBase) + pData->Loaded.SizeOfImage,
|
||||
pData->Loaded.SizeOfImage);
|
||||
reinterpret_cast<getprocaddress_singleton_import_hook*>(context)->hook_module(utils::loaded_module(pData->Loaded.DllBase));
|
||||
} else if (notiReason == LDR_DLL_NOTIFICATION_REASON_UNLOADED) {
|
||||
const auto dllName = unicode::convert<std::string>(pData->Unloaded.FullDllName->Buffer);
|
||||
logging::print<logging::I>(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<logging::I>(R"({} Redirecting GetProcAddress("{}", "{}"))", LogTag, m_dllNameMap[hModule], lpProcName);
|
||||
|
||||
return reinterpret_cast<FARPROC>(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<std::string>(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<logging::I>("{} Hooking {}!{} imported by {}", LogTag, dllName, targetFn, unicode::convert<std::string>(mod.path().wstring()));
|
||||
|
||||
hook.emplace(static_cast<void**>(pGetProcAddressImport), pfnThunk);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,15 +1,21 @@
|
|||
#pragma once
|
||||
|
||||
#include <limits>
|
||||
#include <map>
|
||||
|
||||
#include "utils.h"
|
||||
|
||||
namespace hooks {
|
||||
class base_untyped_hook {
|
||||
public:
|
||||
virtual ~base_untyped_hook() = default;
|
||||
};
|
||||
|
||||
template<typename>
|
||||
class base_hook;
|
||||
|
||||
template<typename TReturn, typename ... TArgs>
|
||||
class base_hook<TReturn(TArgs...)> {
|
||||
class base_hook<TReturn(TArgs...)> : 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<TFn> 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<TFn>(GetModuleHandleW(nullptr), pcszDllName, pcszFunctionName, hintOrOrdinal)) {
|
||||
: import_hook(utils::loaded_module::current_process().get_imported_function_pointer<TFn>(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<getprocaddress_singleton_import_hook> {
|
||||
static inline const char* LogTag = "[global_import_hook]";
|
||||
|
||||
decltype(GetProcAddress)* const m_pfnGetProcAddress;
|
||||
|
||||
utils::thunk<decltype(GetProcAddress)> m_thunk;
|
||||
std::shared_ptr<void> m_getProcAddressHandler;
|
||||
|
||||
void* m_ldrDllNotificationCookie{};
|
||||
std::map<HMODULE, std::string> m_dllNameMap;
|
||||
std::map<HMODULE, std::map<std::string, void*>> m_targetFns;
|
||||
std::map<HMODULE, std::map<std::string, std::map<HMODULE, std::optional<untyped_import_hook>>>> m_hooks;
|
||||
|
||||
public:
|
||||
getprocaddress_singleton_import_hook();
|
||||
~getprocaddress_singleton_import_hook();
|
||||
|
||||
std::shared_ptr<void> set_handler(std::wstring dllName, std::string functionName, void* pfnDetour);
|
||||
|
||||
static std::shared_ptr<getprocaddress_singleton_import_hook> get_instance();
|
||||
|
||||
private:
|
||||
void initialize();
|
||||
|
||||
FARPROC get_proc_address_handler(HMODULE hModule, LPCSTR lpProcName);
|
||||
|
||||
void hook_module(const utils::loaded_module& mod);
|
||||
};
|
||||
|
||||
template<typename>
|
||||
class global_import_hook;
|
||||
|
||||
template<typename TReturn, typename ... TArgs>
|
||||
class global_import_hook<TReturn(TArgs...)> : public base_untyped_hook {
|
||||
using TFn = TReturn(TArgs...);
|
||||
utils::thunk<TFn> m_thunk;
|
||||
std::shared_ptr<void> 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<TFn*>(m_singleImportHook.get()));
|
||||
}
|
||||
|
||||
virtual void set_detour(std::function<TFn> fn) {
|
||||
if (!fn)
|
||||
m_thunk.set_target(reinterpret_cast<TFn*>(m_singleImportHook.get()));
|
||||
else
|
||||
m_thunk.set_target(std::move(fn));
|
||||
}
|
||||
|
||||
virtual TReturn call_original(TArgs... args) {
|
||||
return reinterpret_cast<TFn*>(m_singleImportHook.get())(std::forward<TArgs>(args)...);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
#include <Shlobj.h>
|
||||
#include <TlHelp32.h>
|
||||
#include <Dbt.h>
|
||||
#include <SubAuth.h>
|
||||
|
||||
// MSVC Compiler Intrinsic
|
||||
#include <intrin.h>
|
||||
|
|
@ -26,6 +27,7 @@
|
|||
// C++ Standard Libraries
|
||||
#include <cassert>
|
||||
#include <cstdio>
|
||||
#include <deque>
|
||||
#include <filesystem>
|
||||
#include <format>
|
||||
#include <fstream>
|
||||
|
|
|
|||
|
|
@ -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<DWORD>(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<const char*>(pAddress);
|
||||
const auto pcModule = reinterpret_cast<const char*>(m_hModule);
|
||||
return pcModule <= pcAddress && pcAddress <= pcModule + (is_pe64() ? nt_header64().OptionalHeader.SizeOfImage : nt_header32().OptionalHeader.SizeOfImage);
|
||||
}
|
||||
|
||||
std::span<IMAGE_SECTION_HEADER> utils::loaded_module::section_headers() const {
|
||||
const auto& dosHeader = ref_as<IMAGE_DOS_HEADER>(0);
|
||||
const auto& ntHeader32 = ref_as<IMAGE_NT_HEADERS32>(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<const char*>(section.Name), pcszSectionName, IMAGE_SIZEOF_SHORT_NAME) == 0)
|
||||
return section;
|
||||
}
|
||||
|
||||
throw std::out_of_range(std::format("Section [{}] not found", pcszSectionName));
|
||||
}
|
||||
|
||||
std::span<char> utils::loaded_module::section(size_t index) const {
|
||||
auto& sectionHeader = section_headers()[index];
|
||||
return { address(sectionHeader.VirtualAddress), sectionHeader.Misc.VirtualSize };
|
||||
}
|
||||
|
||||
std::span<char> utils::loaded_module::section(const char* pcszSectionName) const {
|
||||
auto& sectionHeader = section_header(pcszSectionName);
|
||||
return { address(sectionHeader.VirtualAddress), sectionHeader.Misc.VirtualSize };
|
||||
}
|
||||
|
||||
template<typename TEntryType>
|
||||
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<const TEntryType*>(&pcBaseAddress[desc.OriginalFirstThunk]), (dir.Size - desc.OriginalFirstThunk) / sizeof TEntryType);
|
||||
const auto importAddressesOversizedSpan = std::span(reinterpret_cast<const TEntryType*>(&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<const IMAGE_IMPORT_BY_NAME*>(&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<size_t>)(&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<void*>(reinterpret_cast<const void*>(&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<IMAGE_IMPORT_DESCRIPTOR>(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<char>(importDescriptor.Name), (std::min<size_t>)(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<uint64_t>(address(), importDescriptor, directory, requestedFunctionName, hintOrOrdinal, ppFunctionAddress))
|
||||
return true;
|
||||
} else {
|
||||
if (find_imported_function_pointer_helper<uint32_t>(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> utils::loaded_module::all_modules() {
|
||||
std::vector<HMODULE> hModules(128);
|
||||
for (DWORD dwNeeded{}; EnumProcessModules(GetCurrentProcess(), &hModules[0], static_cast<DWORD>(std::span(hModules).size_bytes()), &dwNeeded) && hModules.size() < dwNeeded;)
|
||||
hModules.resize(hModules.size() + 128);
|
||||
|
||||
std::vector<loaded_module> 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<const char*>(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<const char*>(pLast) - reinterpret_cast<const char*>(pFirst));
|
||||
}
|
||||
|
||||
utils::signature_finder& utils::signature_finder::look_in(HMODULE hModule, const char* sectionName) {
|
||||
const auto pcBaseAddress = reinterpret_cast<char*>(hModule);
|
||||
const auto& dosHeader = *reinterpret_cast<const IMAGE_DOS_HEADER*>(&pcBaseAddress[0]);
|
||||
const auto& ntHeader32 = *reinterpret_cast<const IMAGE_NT_HEADERS32*>(&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<const char*>(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<void> utils::allocate_executable_heap(size_t len) {
|
|||
};
|
||||
}
|
||||
|
||||
template<typename TEntryType>
|
||||
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<const TEntryType*>(&pcBaseAddress[desc.OriginalFirstThunk]), (dir.Size - desc.OriginalFirstThunk) / sizeof TEntryType);
|
||||
const auto importAddressesOversizedSpan = std::span(reinterpret_cast<const TEntryType*>(&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<const IMAGE_IMPORT_BY_NAME*>(&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<size_t>)(&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<void*>(reinterpret_cast<const void*>(&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<char*>(hModule);
|
||||
const auto& dosHeader = *reinterpret_cast<const IMAGE_DOS_HEADER*>(&pcBaseAddress[0]);
|
||||
const auto& ntHeader32 = *reinterpret_cast<const IMAGE_NT_HEADERS32*>(&pcBaseAddress[dosHeader.e_lfanew]);
|
||||
const auto& ntHeader64 = *reinterpret_cast<const IMAGE_NT_HEADERS64*>(&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<const IMAGE_IMPORT_DESCRIPTOR*>(&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<size_t>)(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<uint32_t>(pcBaseAddress, importDescriptor, *pDirectory, requestedFunctionName, hintOrOrdinal, ppFunctionAddress))
|
||||
return true;
|
||||
else if (!bPE32 && find_imported_function_pointer_helper<uint64_t>(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<void> utils::create_thunk(void* pfnFunction, void* pThis, uint64_t placeholderValue) {
|
||||
const auto pcBaseFn = reinterpret_cast<const uint8_t*>(pfnFunction);
|
||||
auto sourceCode = std::vector<uint8_t>(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<std::string>(get_env<std::wstring>(pcwzName));
|
||||
return unicode::convert<std::string>(get_env<std::wstring>(pcwzName));
|
||||
}
|
||||
|
||||
template<>
|
||||
int utils::get_env(const wchar_t* pcwzName) {
|
||||
auto env = get_env<std::wstring>(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<std::wstring> utils::get_env_list(const wchar_t* pcszName) {
|
||||
const auto src = utils::get_env<std::wstring>(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<bool>(L"XL_WINEONLINUX"))
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -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<HMODULE>(const_cast<void*>(hModule))) {}
|
||||
loaded_module(void* hModule) : m_hModule(reinterpret_cast<HMODULE>(hModule)) {}
|
||||
loaded_module(size_t hModule) : m_hModule(reinterpret_cast<HMODULE>(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<size_t>(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<char*>(m_hModule) + offset; }
|
||||
template<typename T> T* address_as(size_t offset) const { return reinterpret_cast<T*>(address(offset)); }
|
||||
template<typename T> std::span<T> span_as(size_t offset, size_t count) const { return std::span<T>(reinterpret_cast<T*>(address(offset)), count); }
|
||||
template<typename T> T& ref_as(size_t offset) const { return *reinterpret_cast<T*>(address(offset)); }
|
||||
|
||||
IMAGE_DOS_HEADER& dos_header() const { return ref_as<IMAGE_DOS_HEADER>(0); }
|
||||
IMAGE_NT_HEADERS32& nt_header32() const { return ref_as<IMAGE_NT_HEADERS32>(dos_header().e_lfanew); }
|
||||
IMAGE_NT_HEADERS64& nt_header64() const { return ref_as<IMAGE_NT_HEADERS64>(dos_header().e_lfanew); }
|
||||
bool is_pe64() const { return nt_header32().OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC; }
|
||||
|
||||
std::span<IMAGE_DATA_DIRECTORY> 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<IMAGE_SECTION_HEADER> section_headers() const;
|
||||
IMAGE_SECTION_HEADER& section_header(const char* pcszSectionName) const;
|
||||
std::span<char> section(size_t index) const;
|
||||
std::span<char> section(const char* pcszSectionName) const;
|
||||
|
||||
template<typename TFn> 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<TFn*>(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<typename TFn> TFn** get_imported_function_pointer(const char* pcszDllName, const char* pcszFunctionName, uint32_t hintOrOrdinal) { return reinterpret_cast<TFn**>(get_imported_function_pointer(pcszDllName, pcszFunctionName, hintOrOrdinal)); }
|
||||
|
||||
static loaded_module current_process();
|
||||
static std::vector<loaded_module> all_modules();
|
||||
};
|
||||
|
||||
class signature_finder {
|
||||
std::vector<std::span<const char>> m_ranges;
|
||||
std::vector<srell::regex> 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<typename T>
|
||||
signature_finder& look_in(std::span<T> 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<typename T>
|
||||
memory_tenderizer(std::span<const T> s, DWORD dwNewProtect) : memory_tenderizer(&s[0], s.size(), dwNewProtect) {}
|
||||
|
||||
template<typename T>
|
||||
memory_tenderizer(std::span<T> 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<typename TFn>
|
||||
TFn** get_imported_function_pointer(HMODULE hModule, const char* pcszDllName, const char* pcszFunctionName, uint32_t hintOrOrdinal) {
|
||||
return reinterpret_cast<TFn**>(get_imported_function_pointer(hModule, pcszDllName, pcszFunctionName, hintOrOrdinal));
|
||||
}
|
||||
|
||||
std::shared_ptr<void> allocate_executable_heap(size_t len);
|
||||
|
||||
template<typename T>
|
||||
|
|
@ -115,7 +165,7 @@ namespace utils {
|
|||
}
|
||||
};
|
||||
|
||||
template<class TElem, class TTraits>
|
||||
template<class TElem, class TTraits = std::char_traits<TElem>>
|
||||
std::basic_string_view<TElem, TTraits> trim(std::basic_string_view<TElem, TTraits> 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 TElem, class TTraits = std::char_traits<TElem>, class TAlloc = std::allocator<TElem>>
|
||||
std::basic_string<TElem, TTraits> trim(std::basic_string<TElem, TTraits> view, bool left = true, bool right = true) {
|
||||
return std::basic_string<TElem, TTraits, TAlloc>(trim(std::basic_string_view<TElem, TTraits>(view), left, right));
|
||||
}
|
||||
|
||||
template<class TElem, class TTraits = std::char_traits<TElem>, class TAlloc = std::allocator<TElem>>
|
||||
[[nodiscard]] std::vector<std::basic_string<TElem, TTraits, TAlloc>> split(const std::basic_string<TElem, TTraits, TAlloc>& str, const std::basic_string_view<TElem, TTraits>& delimiter, size_t maxSplit = SIZE_MAX) {
|
||||
std::vector<std::basic_string<TElem, TTraits, TAlloc>> 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 TElem, class TTraits = std::char_traits<TElem>, class TAlloc = std::allocator<TElem>>
|
||||
[[nodiscard]] std::vector<std::basic_string<TElem, TTraits, TAlloc>> split(const std::basic_string<TElem, TTraits, TAlloc>& str, const std::basic_string<TElem, TTraits, TAlloc>& delimiter, size_t maxSplit = SIZE_MAX) {
|
||||
return split(str, std::basic_string_view<TElem, TTraits>(delimiter), maxSplit);
|
||||
}
|
||||
|
||||
template<class TElem, class TTraits = std::char_traits<TElem>, class TAlloc = std::allocator<TElem>>
|
||||
[[nodiscard]] std::vector<std::basic_string<TElem, TTraits, TAlloc>> split(const std::basic_string<TElem, TTraits, TAlloc>& str, const TElem* pcszDelimiter, size_t maxSplit = SIZE_MAX) {
|
||||
return split(str, std::basic_string_view<TElem, TTraits>(pcszDelimiter), maxSplit);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
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<T>(unicode::convert<std::wstring>(pcszName).c_str());
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
std::vector<T> get_env_list(const wchar_t* pcwzName) {
|
||||
static_assert(false);
|
||||
}
|
||||
|
||||
template<>
|
||||
std::vector<std::wstring> get_env_list(const wchar_t* pcwzName);
|
||||
|
||||
template<typename T>
|
||||
std::vector<T> get_env_list(const char* pcszName) {
|
||||
return get_env_list<T>(unicode::convert<std::wstring>(pcszName).c_str());
|
||||
}
|
||||
|
||||
bool is_running_on_linux();
|
||||
|
||||
std::filesystem::path get_module_path(HMODULE hModule);
|
||||
|
|
|
|||
|
|
@ -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<logging::I>(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<logging::W>("{} Module 0x{:X}: Failed to resolve path: {}", LogTag, mod.address_int(), e.what());
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto moduleName = unicode::convert<std::string>(path.filename().wstring());
|
||||
|
||||
std::vector<char> buf;
|
||||
std::string formatBuf;
|
||||
try {
|
||||
const auto& sectionHeader = mod.section_header(".text");
|
||||
const auto section = mod.span_as<char>(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<logging::W>("{} Module loaded in current process but could not open file: Win32 error {}", LogTag, GetLastError());
|
||||
continue;
|
||||
}
|
||||
auto hFsDll = std::unique_ptr<void, decltype(CloseHandle)*>(hFsDllRaw, &CloseHandle);
|
||||
|
||||
buf.resize(section.size());
|
||||
SetFilePointer(hFsDll.get(), sectionHeader.PointerToRawData, nullptr, FILE_CURRENT);
|
||||
if (DWORD read{}; ReadFile(hFsDll.get(), &buf[0], static_cast<DWORD>(buf.size()), &read, nullptr)) {
|
||||
if (read < section.size_bytes()) {
|
||||
logging::print<logging::W>(L"{} ReadFile: read {} bytes < requested {} bytes", LogTagW, read, section.size_bytes());
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
logging::print<logging::I>(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<utils::memory_tenderizer> 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<logging::W>("{} {}+0x{:0X}: dd {:02X}", LogTag, moduleName, rva, static_cast<uint8_t>(section[i]));
|
||||
printed++;
|
||||
}
|
||||
} else {
|
||||
instructionLength = instruction.length;
|
||||
if (printed < 64) {
|
||||
formatBuf.resize(128);
|
||||
nmd_x86_format(&instruction, &formatBuf[0], reinterpret_cast<size_t>(§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<IMAGE_EXPORT_DIRECTORY>(directory.VirtualAddress);
|
||||
const auto names = mod.span_as<DWORD>(exportDirectory.AddressOfNames, exportDirectory.NumberOfNames);
|
||||
const auto ordinals = mod.span_as<WORD>(exportDirectory.AddressOfNameOrdinals, exportDirectory.NumberOfNames);
|
||||
const auto functions = mod.span_as<DWORD>(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<char>(names[j]));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
logging::print<logging::W>("{} {}+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<logging::I>("{} Verification and overwriting complete.", LogTag);
|
||||
else if (doRestore)
|
||||
logging::print<logging::I>("{} Verification complete. Overwriting was not required.", LogTag);
|
||||
|
||||
} catch (const std::exception& e) {
|
||||
logging::print<logging::W>("{} 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<hooks::wndproc_hook> s_hookWndProc;
|
||||
|
||||
if (bApply) {
|
||||
if (!bootconfig::gamefix_is_enabled(L"prevent_devicechange_crashes")) {
|
||||
logging::print<logging::I>("{} 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<logging::I>("{} CreateWindow(0x{:08X}, \"{}\", \"{}\", 0x{:08X}, {}, {}, {}, {}, 0x{:X}, 0x{:X}, 0x{:X}, 0x{:X}) called; unhooking CreateWindowExA and hooking WndProc.",
|
||||
logging::print<logging::I>(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<size_t>(hWndParent), reinterpret_cast<size_t>(hMenu), reinterpret_cast<size_t>(hInstance), reinterpret_cast<size_t>(lpParam));
|
||||
|
||||
s_hookCreateWindowExA.reset();
|
||||
|
|
@ -70,24 +193,34 @@ void xivfixes::prevent_devicechange_crashes(bool bApply) {
|
|||
});
|
||||
|
||||
logging::print<logging::I>("{} Enable", LogTag);
|
||||
} else {
|
||||
logging::print<logging::I>("{} Disable", LogTag);
|
||||
|
||||
s_hookCreateWindowExA.reset();
|
||||
} else {
|
||||
if (s_hookCreateWindowExA) {
|
||||
logging::print<logging::I>("{} Disable CreateWindowExA", LogTag);
|
||||
s_hookCreateWindowExA.reset();
|
||||
}
|
||||
|
||||
// This will effectively revert any other WndProc alterations, including Dalamud.
|
||||
s_hookWndProc.reset();
|
||||
if (s_hookWndProc) {
|
||||
logging::print<logging::I>("{} 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<hooks::import_hook<decltype(OpenProcess)>> hook;
|
||||
static std::optional<hooks::import_hook<decltype(OpenProcess)>> s_hook;
|
||||
|
||||
if (bApply) {
|
||||
hook.emplace("kernel32.dll", "OpenProcess", 0);
|
||||
hook->set_detour([](DWORD dwDesiredAccess, BOOL bInheritHandle, DWORD dwProcessId)->HANDLE {
|
||||
logging::print<logging::I>("{} 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<logging::I>("{} 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<logging::I>("{} 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<logging::I>("{} Enable", LogTag);
|
||||
} else {
|
||||
logging::print<logging::I>("{} Disable", LogTag);
|
||||
hook.reset();
|
||||
if (s_hook) {
|
||||
logging::print<logging::I>("{} Disable OpenProcess", LogTag);
|
||||
s_hook.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void xivfixes::redirect_openprocess(bool bApply) {
|
||||
static const char* LogTag = "[xivfixes:redirect_openprocess]";
|
||||
static std::optional<hooks::direct_hook<decltype(OpenProcess)>> hook;
|
||||
static std::shared_ptr<hooks::base_untyped_hook> s_hook;
|
||||
|
||||
if (bApply) {
|
||||
hook.emplace(OpenProcess);
|
||||
hook->set_detour([](DWORD dwDesiredAccess, BOOL bInheritHandle, DWORD dwProcessId)->HANDLE {
|
||||
if (dwProcessId == GetCurrentProcessId()) {
|
||||
logging::print<logging::I>("{} 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<logging::I>("{} 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<hooks::global_import_hook<decltype(OpenProcess)>>(L"kernel32.dll", "OpenProcess");
|
||||
hook->set_detour([hook = hook.get()](DWORD dwDesiredAccess, BOOL bInheritHandle, DWORD dwProcessId)->HANDLE {
|
||||
if (dwProcessId == GetCurrentProcessId()) {
|
||||
logging::print<logging::I>("{} 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<logging::I>("{} Enable", LogTag);
|
||||
return {};
|
||||
}
|
||||
return hook->call_original(dwDesiredAccess, bInheritHandle, dwProcessId);
|
||||
});
|
||||
s_hook = std::dynamic_pointer_cast<hooks::base_untyped_hook>(std::move(hook));
|
||||
|
||||
logging::print<logging::I>("{} Enable via import_hook", LogTag);
|
||||
|
||||
} else {
|
||||
auto hook = std::make_shared<hooks::direct_hook<decltype(OpenProcess)>>(OpenProcess);
|
||||
hook->set_detour([hook = hook.get()](DWORD dwDesiredAccess, BOOL bInheritHandle, DWORD dwProcessId)->HANDLE {
|
||||
if (dwProcessId == GetCurrentProcessId()) {
|
||||
logging::print<logging::I>("{} 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<hooks::base_untyped_hook>(std::move(hook));
|
||||
|
||||
logging::print<logging::I>("{} Enable via direct_hook", LogTag);
|
||||
}
|
||||
|
||||
} else {
|
||||
logging::print<logging::I>("{} Disable", LogTag);
|
||||
hook.reset();
|
||||
if (s_hook) {
|
||||
logging::print<logging::I>("{} Disable OpenProcess", LogTag);
|
||||
s_hook.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void xivfixes::apply_all(bool bApply) {
|
||||
for (const auto& [taskName, taskFunction] : std::initializer_list<std::pair<const char*, void(*)(bool)>>
|
||||
{
|
||||
{ "unhook_dll", &unhook_dll },
|
||||
{ "prevent_devicechange_crashes", &prevent_devicechange_crashes },
|
||||
{ "disable_game_openprocess_access_check", &disable_game_openprocess_access_check },
|
||||
{ "redirect_openprocess", &redirect_openprocess },
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -110,7 +110,7 @@ int InitializeClrAndGetEntryPoint(
|
|||
logging::print<logging::I>("Loading coreclr... ");
|
||||
if ((result = g_clr->load_runtime(runtimeconfig_path, &runtime_parameters)) != 0)
|
||||
{
|
||||
logging::print<logging::E>("Failed to load coreclr (err={})", result);
|
||||
logging::print<logging::E>("Failed to load coreclr (err=0x{:08X})", static_cast<uint32_t>(result));
|
||||
return result;
|
||||
}
|
||||
logging::print<logging::I>("Done!");
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue