diff --git a/.gitmodules b/.gitmodules
index 57306103a..6274b5410 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -4,3 +4,6 @@
[submodule "lib/FFXIVClientStructs"]
path = lib/FFXIVClientStructs
url = https://github.com/goatcorp/FFXIVClientStructs.git
+[submodule "lib/Nomade040-nmd"]
+ path = lib/Nomade040-nmd
+ url = https://github.com/Nomade040/nmd
diff --git a/Dalamud.Boot/Dalamud.Boot.vcxproj b/Dalamud.Boot/Dalamud.Boot.vcxproj
index 0629d4466..993b2caa2 100644
--- a/Dalamud.Boot/Dalamud.Boot.vcxproj
+++ b/Dalamud.Boot/Dalamud.Boot.vcxproj
@@ -58,6 +58,7 @@
true
false
_DEBUG;%(PreprocessorDefinitions)
+ Use
false
@@ -69,6 +70,7 @@
true
true
NDEBUG;%(PreprocessorDefinitions)
+ Use
true
@@ -91,22 +93,24 @@
NotUsing
NotUsing
+
+
+
+
+
+ NotUsing
+ NotUsing
+
Create
Create
-
- Use
- Use
-
+
Use
Use
-
- Use
- Use
-
+
@@ -114,8 +118,14 @@
+
+
+
+
+
+
diff --git a/Dalamud.Boot/Dalamud.Boot.vcxproj.filters b/Dalamud.Boot/Dalamud.Boot.vcxproj.filters
index ad673c2d4..b6991f9eb 100644
--- a/Dalamud.Boot/Dalamud.Boot.vcxproj.filters
+++ b/Dalamud.Boot/Dalamud.Boot.vcxproj.filters
@@ -8,14 +8,14 @@
{4FC737F1-C7A5-4376-A066-2A32D752A2FF}
cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx
+
+ {0c915688-91ea-431f-8b68-845cad422a50}
+
Dalamud.Boot DLL
-
- Dalamud.Boot DLL
-
Dalamud.Boot DLL
@@ -28,6 +28,24 @@
CoreCLR
+
+ Project Files
+
+
+ Project Files
+
+
+ Dalamud.Boot DLL
+
+
+ Dalamud.Boot DLL
+
+
+ Dalamud.Boot DLL
+
+
+ Dalamud.Boot DLL
+
@@ -49,6 +67,24 @@
Dalamud.Boot DLL
+ Project Files
+
+
+ Dalamud.Boot DLL
+
+
+ Dalamud.Boot DLL
+
+
+ Dalamud.Boot DLL
+
+
+ Dalamud.Boot DLL
+
+
+ Dalamud.Boot DLL
+
+
Dalamud.Boot DLL
diff --git a/Dalamud.Boot/bootconfig.h b/Dalamud.Boot/bootconfig.h
new file mode 100644
index 000000000..667369212
--- /dev/null
+++ b/Dalamud.Boot/bootconfig.h
@@ -0,0 +1,25 @@
+#pragma once
+
+#include "utils.h"
+
+namespace bootconfig {
+ inline bool is_wait_messagebox() {
+ return utils::get_env(L"DALAMUD_WAIT_MESSAGEBOX");
+ }
+
+ inline bool is_show_console() {
+ return utils::get_env(L"DALAMUD_SHOW_CONSOLE");
+ }
+
+ inline bool is_wait_debugger() {
+ return utils::get_env(L"DALAMUD_WAIT_DEBUGGER");
+ }
+
+ inline bool is_veh_enabled() {
+ return utils::get_env(L"DALAMUD_IS_VEH");
+ }
+
+ inline bool is_veh_full() {
+ return utils::get_env("DALAMUD_IS_VEH_FULL");
+ }
+}
diff --git a/Dalamud.Boot/dllmain.cpp b/Dalamud.Boot/dllmain.cpp
index 2ad8bb20d..64e2b41be 100644
--- a/Dalamud.Boot/dllmain.cpp
+++ b/Dalamud.Boot/dllmain.cpp
@@ -1,86 +1,40 @@
#include "pch.h"
+#include "bootconfig.h"
+#include "logging.h"
#include "veh.h"
+#include "xivfixes.h"
HMODULE g_hModule;
+HINSTANCE g_hGameInstance = GetModuleHandleW(nullptr);
-bool check_env_var(std::string name)
-{
- size_t required_size;
- getenv_s(&required_size, nullptr, 0, name.c_str());
- if (required_size > 0)
- {
- if (char* is_no_veh = static_cast(malloc(required_size * sizeof(char))))
- {
- getenv_s(&required_size, is_no_veh, required_size, name.c_str());
- auto result = _stricmp(is_no_veh, "true");
- free(is_no_veh);
- if (result == 0)
- return true;
- }
+DllExport DWORD WINAPI Initialize(LPVOID lpParam, HANDLE hMainThreadContinue) {
+ if (bootconfig::is_show_console())
+ ConsoleSetup(L"Dalamud Boot");
+
+ if (bootconfig::is_wait_messagebox())
+ MessageBoxW(nullptr, L"Press OK to continue", L"Dalamud Boot", MB_OK);
+
+ try {
+ xivfixes::apply_all(true);
+ } catch (const std::exception& e) {
+ logging::print("Failed to do general fixups. Some things might not work.");
+ logging::print("Error: {}", e.what());
}
- return false;
-}
+ logging::print("Dalamud.Boot Injectable, (c) 2021 XIVLauncher Contributors");
+ logging::print("Built at : " __DATE__ "@" __TIME__);
-bool is_running_on_linux()
-{
- size_t required_size;
- getenv_s(&required_size, nullptr, 0, "XL_WINEONLINUX");
- if (required_size > 0)
- {
- if (char* is_wine_on_linux = static_cast(malloc(required_size * sizeof(char))))
- {
- getenv_s(&required_size, is_wine_on_linux, required_size, "XL_WINEONLINUX");
- auto result = _stricmp(is_wine_on_linux, "true");
- free(is_wine_on_linux);
- if (result == 0)
- return true;
- }
- }
-
- HMODULE hntdll = GetModuleHandleW(L"ntdll.dll");
- if (!hntdll) // not running on NT
- return true;
-
- FARPROC pwine_get_version = GetProcAddress(hntdll, "wine_get_version");
- FARPROC pwine_get_host_version = GetProcAddress(hntdll, "wine_get_host_version");
-
- return pwine_get_version != nullptr || pwine_get_host_version != nullptr;
-}
-
-bool is_veh_enabled()
-{
- return check_env_var("DALAMUD_IS_VEH");
-}
-
-bool is_full_dumps()
-{
- return check_env_var("DALAMUD_IS_VEH_FULL");
-}
-
-DllExport DWORD WINAPI Initialize(LPVOID lpParam, HANDLE hMainThreadContinue)
-{
- #ifndef NDEBUG
- ConsoleSetup(L"Dalamud Boot");
- #endif
-
- printf("Dalamud.Boot Injectable, (c) 2021 XIVLauncher Contributors\nBuilt at: %s@%s\n\n", __DATE__, __TIME__);
-
- if (check_env_var("DALAMUD_WAIT_DEBUGGER"))
- {
- printf("Waiting for debugger to attach...\n");
+ if (bootconfig::is_wait_debugger()) {
+ logging::print("Waiting for debugger to attach...");
while (!IsDebuggerPresent())
Sleep(100);
- printf("Debugger attached.\n");
+ logging::print("Debugger attached.");
}
- wchar_t _module_path[MAX_PATH];
- GetModuleFileNameW(g_hModule, _module_path, sizeof _module_path / 2);
- std::filesystem::path fs_module_path(_module_path);
-
- std::wstring runtimeconfig_path = _wcsdup(fs_module_path.replace_filename(L"Dalamud.runtimeconfig.json").c_str());
- std::wstring module_path = _wcsdup(fs_module_path.replace_filename(L"Dalamud.dll").c_str());
+ const auto fs_module_path = utils::get_module_path(g_hModule);
+ const auto runtimeconfig_path = std::filesystem::path(fs_module_path).replace_filename(L"Dalamud.runtimeconfig.json").wstring();
+ const auto module_path = std::filesystem::path(fs_module_path).replace_filename(L"Dalamud.dll").wstring();
// ============================== CLR ========================================= //
@@ -97,39 +51,28 @@ DllExport DWORD WINAPI Initialize(LPVOID lpParam, HANDLE hMainThreadContinue)
if (result != 0)
return result;
- typedef void (CORECLR_DELEGATE_CALLTYPE* custom_component_entry_point_fn)(LPVOID, HANDLE);
- custom_component_entry_point_fn entrypoint_fn = reinterpret_cast(entrypoint_vfn);
+ using custom_component_entry_point_fn = void (CORECLR_DELEGATE_CALLTYPE*)(LPVOID, HANDLE);
+ const auto entrypoint_fn = reinterpret_cast(entrypoint_vfn);
// ============================== VEH ======================================== //
- printf("Initializing VEH... ");
- if(is_running_on_linux())
- {
- printf("VEH was disabled, running on linux\n");
- }
- else if (is_veh_enabled())
- {
- if (veh::add_handler(is_full_dumps()))
- printf("Done!\n");
- else printf("Failed!\n");
- }
- else
- {
- printf("VEH was disabled manually\n");
+ logging::print("Initializing VEH...");
+ if (utils::is_running_on_linux()) {
+ logging::print("=> VEH was disabled, running on linux");
+ } else if (bootconfig::is_veh_enabled()) {
+ if (veh::add_handler(bootconfig::is_veh_full()))
+ logging::print("=> Done!");
+ else
+ logging::print("=> Failed!");
+ } else {
+ logging::print("VEH was disabled manually");
}
// ============================== Dalamud ==================================== //
- printf("Initializing Dalamud... ");
+ logging::print("Initializing Dalamud...");
entrypoint_fn(lpParam, hMainThreadContinue);
- printf("Done!\n");
-
- #ifndef NDEBUG
- fclose(stdin);
- fclose(stdout);
- fclose(stderr);
- FreeConsole();
- #endif
+ logging::print("Done!");
return 0;
}
@@ -137,12 +80,12 @@ DllExport DWORD WINAPI Initialize(LPVOID lpParam, HANDLE hMainThreadContinue)
BOOL APIENTRY DllMain(const HMODULE hModule, const DWORD dwReason, LPVOID lpReserved) {
DisableThreadLibraryCalls(hModule);
- switch (dwReason)
- {
+ switch (dwReason) {
case DLL_PROCESS_ATTACH:
g_hModule = hModule;
break;
case DLL_PROCESS_DETACH:
+ xivfixes::apply_all(false);
veh::remove_handler();
break;
}
diff --git a/Dalamud.Boot/hooks.h b/Dalamud.Boot/hooks.h
new file mode 100644
index 000000000..ac8f93b21
--- /dev/null
+++ b/Dalamud.Boot/hooks.h
@@ -0,0 +1,136 @@
+#pragma once
+
+#include
+
+#include "utils.h"
+
+namespace hooks {
+ template
+ class base_hook;
+
+ template
+ class base_hook {
+ using TFn = TReturn(TArgs...);
+
+ private:
+ TFn* const m_pfnOriginal;
+ utils::thunk m_thunk;
+
+ public:
+ base_hook(TFn* pfnOriginal)
+ : m_pfnOriginal(pfnOriginal)
+ , m_thunk(m_pfnOriginal) {
+ }
+
+ virtual ~base_hook() = default;
+
+ virtual void set_detour(std::function fn) {
+ if (!fn)
+ m_thunk.set_target(m_pfnOriginal);
+ else
+ m_thunk.set_target(std::move(fn));
+ }
+
+ virtual TReturn call_original(TArgs... args) {
+ return m_pfnOriginal(std::forward(args)...);
+ }
+
+ protected:
+ TFn* get_original() const {
+ return m_pfnOriginal;
+ }
+
+ TFn* get_thunk() const {
+ return m_thunk.get_thunk();
+ }
+ };
+
+ template
+ class import_hook : public base_hook {
+ using Base = base_hook;
+
+ TFn** const m_ppfnImportTableItem;
+
+ public:
+ import_hook(TFn** ppfnImportTableItem)
+ : Base(*ppfnImportTableItem)
+ , m_ppfnImportTableItem(ppfnImportTableItem) {
+
+ const utils::memory_tenderizer tenderizer(ppfnImportTableItem, sizeof * ppfnImportTableItem, PAGE_READWRITE);
+ *ppfnImportTableItem = Base::get_thunk();
+ }
+
+ import_hook(const char* pcszDllName, const char* pcszFunctionName, int hintOrOrdinal)
+ : import_hook(utils::get_imported_function_pointer(GetModuleHandleW(nullptr), pcszDllName, pcszFunctionName, hintOrOrdinal)) {
+ }
+
+ ~import_hook() override {
+ const utils::memory_tenderizer tenderizer(m_ppfnImportTableItem, sizeof * m_ppfnImportTableItem, PAGE_READWRITE);
+
+ *m_ppfnImportTableItem = Base::get_original();
+ }
+ };
+
+ template
+ class export_hook : public base_hook {
+ using Base = base_hook;
+
+ static constexpr uint8_t DetouringThunkTemplate[12]{
+ 0x48, 0xB8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // movabs rax, 0x0000000000000000
+ 0xFF, 0xE0, // jmp rax
+ };
+
+ TFn* const m_pfnExportThunk;
+ uint8_t s_originalThunk[sizeof DetouringThunkTemplate]{};
+
+ public:
+ export_hook(TFn* pfnExportThunk)
+ : Base(reinterpret_cast(utils::resolve_unconditional_jump_target(pfnExportThunk)))
+ , m_pfnExportThunk(pfnExportThunk) {
+ auto pExportThunk = reinterpret_cast(pfnExportThunk);
+
+ // Make it writeable.
+ const utils::memory_tenderizer tenderizer(pfnExportThunk, sizeof DetouringThunkTemplate, PAGE_EXECUTE_READWRITE);
+
+ // Back up original thunk bytes.
+ memcpy(s_originalThunk, pExportThunk, sizeof s_originalThunk);
+
+ // Write thunk template.
+ memcpy(pExportThunk, DetouringThunkTemplate, sizeof DetouringThunkTemplate);
+
+ // Write target address.
+ *reinterpret_cast(&pExportThunk[2]) = Base::get_thunk();
+ }
+
+ ~export_hook() override {
+ const utils::memory_tenderizer tenderizer(m_pfnExportThunk, sizeof DetouringThunkTemplate, PAGE_EXECUTE_READWRITE);
+
+ // Restore original thunk bytes.
+ memcpy(m_pfnExportThunk, s_originalThunk, sizeof s_originalThunk);
+
+ // Clear state.
+ memset(s_originalThunk, 0, sizeof s_originalThunk);
+ }
+ };
+
+ class wndproc_hook : public base_hook> {
+ using Base = base_hook>;
+
+ const HWND s_hwnd;
+
+ public:
+ wndproc_hook(HWND hwnd)
+ : Base(reinterpret_cast(GetWindowLongPtrW(hwnd, GWLP_WNDPROC)))
+ , s_hwnd(hwnd) {
+ SetWindowLongPtrW(hwnd, GWLP_WNDPROC, reinterpret_cast(Base::get_thunk()));
+ }
+
+ ~wndproc_hook() override {
+ SetWindowLongPtrW(s_hwnd, GWLP_WNDPROC, reinterpret_cast(Base::get_original()));
+ }
+
+ LRESULT call_original(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) override {
+ return CallWindowProcW(Base::get_original(), hwnd, msg, wParam, lParam);
+ }
+ };
+}
diff --git a/Dalamud.Boot/logging.cpp b/Dalamud.Boot/logging.cpp
new file mode 100644
index 000000000..925fcdb18
--- /dev/null
+++ b/Dalamud.Boot/logging.cpp
@@ -0,0 +1,35 @@
+#include "pch.h"
+#include "logging.h"
+
+void logging::print(Level level, const char* s) {
+ SYSTEMTIME st;
+ GetLocalTime(&st);
+
+ std::string estr;
+ switch (level) {
+ case Verbose:
+ estr = std::format("[{:02}:{:02}:{:02} CPP/VRB] {}\n", st.wHour, st.wMinute, st.wSecond, s);
+ break;
+ case Debug:
+ estr = std::format("[{:02}:{:02}:{:02} CPP/DBG] {}\n", st.wHour, st.wMinute, st.wSecond, s);
+ break;
+ case Info:
+ estr = std::format("[{:02}:{:02}:{:02} CPP/INF] {}\n", st.wHour, st.wMinute, st.wSecond, s);
+ break;
+ case Warning:
+ estr = std::format("[{:02}:{:02}:{:02} CPP/WRN] {}\n", st.wHour, st.wMinute, st.wSecond, s);
+ break;
+ case Error:
+ estr = std::format("[{:02}:{:02}:{:02} CPP/ERR] {}\n", st.wHour, st.wMinute, st.wSecond, s);
+ break;
+ case Fatal:
+ estr = std::format("[{:02}:{:02}:{:02} CPP/FTL] {}\n", st.wHour, st.wMinute, st.wSecond, s);
+ break;
+ default:
+ estr = std::format("[{:02}:{:02}:{:02} CPP/???] {}\n", st.wHour, st.wMinute, st.wSecond, s);
+ break;
+ }
+
+ DWORD wr;
+ WriteFile(GetStdHandle(STD_ERROR_HANDLE), &estr[0], static_cast(estr.size()), &wr, nullptr);
+}
diff --git a/Dalamud.Boot/logging.h b/Dalamud.Boot/logging.h
new file mode 100644
index 000000000..a9b88d605
--- /dev/null
+++ b/Dalamud.Boot/logging.h
@@ -0,0 +1,59 @@
+#pragma once
+
+#include
+#include
+#include
+
+#include "unicode.h"
+
+namespace logging {
+ enum Level : int {
+ Verbose = 0,
+ V = 0,
+ Debug = 1,
+ D = 1,
+ Info = 2,
+ I = 2,
+ Warning = 3,
+ W = 3,
+ Error = 4,
+ E = 4,
+ Fatal = 5,
+ F = 5,
+ };
+
+ void print(Level level, const char* s);
+
+ inline void print(Level level, const wchar_t* s) {
+ const auto cs = unicode::convert(s);
+ print(level, cs.c_str());
+ }
+
+ inline void print(Level level, const std::string& s) {
+ print(level, s.c_str());
+ }
+
+ inline void print(Level level, const std::wstring& s) {
+ print(level, s.c_str());
+ }
+
+ template
+ inline void print(const T* s) {
+ print(level, s);
+ }
+
+ template
+ inline void print(Level level, const char* pcszFormat, Arg arg1, Args...args) {
+ print(level, std::format(pcszFormat, std::forward(arg1), std::forward(args)...));
+ }
+
+ template
+ inline void print(Level level, const wchar_t* pcszFormat, Arg arg1, Args...args) {
+ print(level, std::format(pcszFormat, std::forward(arg1), std::forward(args)...));
+ }
+
+ template>>
+ inline void print(const T* pcszFormat, Arg arg1, Args...args) {
+ print(level, std::format(pcszFormat, std::forward(arg1), std::forward(args)...));
+ }
+};
diff --git a/Dalamud.Boot/pch.h b/Dalamud.Boot/pch.h
index 3c2bd6c4b..01cb992a0 100644
--- a/Dalamud.Boot/pch.h
+++ b/Dalamud.Boot/pch.h
@@ -8,7 +8,8 @@
#define PCH_H
// Exclude rarely-used stuff from Windows headers
-#define WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN
+#define NOMINMAX
// Windows Header Files
#include
@@ -17,6 +18,10 @@
#include
#include
#include
+#include
+
+// MSVC Compiler Intrinsic
+#include
// C++ Standard Libraries
#include
@@ -24,18 +29,32 @@
#include
#include
#include
+#include
+#include
+#include
#include
+#include
#include
+#include
+
+// https://www.akenotsuki.com/misc/srell/en/
+#include "../lib/srell3_009/single-header/srell.hpp"
+
+// https://github.com/Nomade040/nmd
+#include "../lib/Nomade040-nmd/nmd_assembly.h"
// https://github.com/dotnet/coreclr
-#include "..\lib\CoreCLR\CoreCLR.h"
-#include "..\lib\CoreCLR\boot.h"
+#include "../lib/CoreCLR/CoreCLR.h"
+#include "../lib/CoreCLR/boot.h"
+
+#include "unicode.h"
// Commonly used macros
#define DllExport extern "C" __declspec(dllexport)
// Global variables
extern HMODULE g_hModule;
+extern HINSTANCE g_hGameInstance;
extern std::optional g_clr;
#endif //PCH_H
diff --git a/Dalamud.Boot/pch_nmd_assembly_impl.cpp b/Dalamud.Boot/pch_nmd_assembly_impl.cpp
new file mode 100644
index 000000000..6f3af675a
--- /dev/null
+++ b/Dalamud.Boot/pch_nmd_assembly_impl.cpp
@@ -0,0 +1,2 @@
+#define NMD_ASSEMBLY_IMPLEMENTATION
+#include "../lib/Nomade040-nmd/nmd_assembly.h"
diff --git a/Dalamud.Boot/rewrite_entrypoint.cpp b/Dalamud.Boot/rewrite_entrypoint.cpp
index 4092a39bc..d01f05b2a 100644
--- a/Dalamud.Boot/rewrite_entrypoint.cpp
+++ b/Dalamud.Boot/rewrite_entrypoint.cpp
@@ -1,5 +1,7 @@
#include "pch.h"
+#include "logging.h"
+
DllExport DWORD WINAPI Initialize(LPVOID lpParam, HANDLE hMainThreadContinue);
struct RewrittenEntryPointParameters {
@@ -231,8 +233,8 @@ void* get_mapped_image_base_address(HANDLE hProcess, const std::filesystem::path
return mbi.AllocationBase;
- } catch (const std::filesystem::filesystem_error& e) {
- printf("%s", e.what());
+ } catch (const std::exception& e) {
+ logging::print("Failed to check memory block 0x{:X}(len=0x{:X}): {}", mbi.BaseAddress, mbi.RegionSize, e.what());
continue;
}
}
diff --git a/Dalamud.Boot/unicode.cpp b/Dalamud.Boot/unicode.cpp
new file mode 100644
index 000000000..9d66d29f2
--- /dev/null
+++ b/Dalamud.Boot/unicode.cpp
@@ -0,0 +1,235 @@
+#include "pch.h"
+
+#include "unicode.h"
+
+size_t unicode::decode(EncodingTag, char32_t& out, const char8_t* in, size_t nRemainingBytes, bool strict) {
+ if (nRemainingBytes == 0) {
+ out = 0;
+ return 0;
+ }
+
+ if (0 == (*in & 0x80)) {
+ out = *in;
+ return 1;
+ }
+
+ if (0xC0 == (*in & 0xE0)) {
+ if (nRemainingBytes < 2) goto invalid;
+ if (0x80 != (in[1] & 0xC0)) goto invalid;
+ out = (
+ ((static_cast(in[0]) & 0x1F) << 6) |
+ ((static_cast(in[1]) & 0x3F) << 0));
+ return 2;
+ }
+
+ if (0xE0 == (*in & 0xF0)) {
+ if (nRemainingBytes < 3) goto invalid;
+ if (0x80 != (in[1] & 0xC0)) goto invalid;
+ if (0x80 != (in[2] & 0xC0)) goto invalid;
+ out = static_cast(
+ ((static_cast(in[0]) & 0x0F) << 12) |
+ ((static_cast(in[1]) & 0x3F) << 6) |
+ ((static_cast(in[2]) & 0x3F) << 0));
+ return 3;
+ }
+
+ if (0xF0 == (*in & 0xF8)) {
+ if (nRemainingBytes < 4) goto invalid;
+ if (0x80 != (in[1] & 0xC0)) goto invalid;
+ if (0x80 != (in[2] & 0xC0)) goto invalid;
+ if (0x80 != (in[3] & 0xC0)) goto invalid;
+ out = (
+ ((static_cast(in[0]) & 0x07) << 18) |
+ ((static_cast(in[1]) & 0x3F) << 12) |
+ ((static_cast(in[2]) & 0x3F) << 6) |
+ ((static_cast(in[3]) & 0x3F) << 0));
+ return 4;
+ }
+
+ if (!strict) {
+ if (0xF8 == (*in & 0xFC)) {
+ if (nRemainingBytes < 5) goto invalid;
+ if (0x80 != (in[1] & 0xC0)) goto invalid;
+ if (0x80 != (in[2] & 0xC0)) goto invalid;
+ if (0x80 != (in[3] & 0xC0)) goto invalid;
+ if (0x80 != (in[4] & 0xC0)) goto invalid;
+ out = (
+ ((static_cast(in[0]) & 0x07) << 24) |
+ ((static_cast(in[1]) & 0x3F) << 18) |
+ ((static_cast(in[2]) & 0x3F) << 12) |
+ ((static_cast(in[3]) & 0x3F) << 6) |
+ ((static_cast(in[4]) & 0x3F) << 0));
+ return 4;
+ }
+
+ if (0xFC == (*in & 0xFE)) {
+ if (nRemainingBytes < 6) goto invalid;
+ if (0x80 != (in[1] & 0xC0)) goto invalid;
+ if (0x80 != (in[2] & 0xC0)) goto invalid;
+ if (0x80 != (in[3] & 0xC0)) goto invalid;
+ if (0x80 != (in[4] & 0xC0)) goto invalid;
+ if (0x80 != (in[5] & 0xC0)) goto invalid;
+ out = (
+ ((static_cast(in[0]) & 0x07) << 30) |
+ ((static_cast(in[1]) & 0x3F) << 24) |
+ ((static_cast(in[2]) & 0x3F) << 18) |
+ ((static_cast(in[3]) & 0x3F) << 12) |
+ ((static_cast(in[4]) & 0x3F) << 6) |
+ ((static_cast(in[5]) & 0x3F) << 0));
+ return 5;
+ }
+ }
+
+invalid:
+ out = UReplacement;
+ return 1;
+}
+
+size_t unicode::decode(EncodingTag, char32_t& out, const char16_t* in, size_t nRemainingBytes, bool strict) {
+ if (nRemainingBytes == 0) {
+ out = 0;
+ return 0;
+ }
+
+ if ((*in & 0xFC00) == 0xD800) {
+ if (nRemainingBytes < 2 || (in[1] & 0xFC00) != 0xDC00)
+ goto invalid;
+ out = 0x10000 + (
+ ((static_cast(in[0]) & 0x03FF) << 10) |
+ ((static_cast(in[1]) & 0x03FF) << 0)
+ );
+ return 2;
+ }
+
+ if (0xD800 <= *in && *in <= 0xDFFF && strict)
+ out = UReplacement;
+ else
+ out = *in;
+ return 1;
+
+invalid:
+ out = UReplacement;
+ return 1;
+}
+
+size_t unicode::decode(EncodingTag, char32_t& out, const char32_t* in, size_t nRemainingBytes, bool strict) {
+ if (nRemainingBytes == 0) {
+ out = 0;
+ return 0;
+ }
+
+ out = *in;
+ return 1;
+}
+
+size_t unicode::decode(EncodingTag, char32_t& out, const char* in, size_t nRemainingBytes, bool strict) {
+ return decode(EncodingTag(), out, reinterpret_cast(in), nRemainingBytes, strict);
+}
+
+size_t unicode::decode(EncodingTag, char32_t& out, const wchar_t* in, size_t nRemainingBytes, bool strict) {
+ return decode(EncodingTag(), out, reinterpret_cast(in), nRemainingBytes, strict);
+}
+
+size_t unicode::encode(EncodingTag, char8_t* ptr, char32_t c, bool strict) {
+ if (c < (1 << 7)) {
+ if (ptr)
+ *(ptr++) = static_cast(c);
+ return 1;
+ }
+
+ if (c < (1 << (5 + 6))) {
+ if (ptr) {
+ *(ptr++) = 0xC0 | static_cast(c >> 6);
+ *(ptr++) = 0x80 | static_cast((c >> 0) & 0x3F);
+ }
+ return 2;
+ }
+ if (c < (1 << (4 + 6 + 6))) {
+ if (ptr) {
+ *(ptr++) = 0xE0 | static_cast(c >> 12);
+ *(ptr++) = 0x80 | static_cast((c >> 6) & 0x3F);
+ *(ptr++) = 0x80 | static_cast((c >> 0) & 0x3F);
+ }
+ return 3;
+ }
+
+ if (c < (1 << (3 + 6 + 6 + 6))) {
+ if (ptr) {
+ *(ptr++) = 0xF0 | static_cast(c >> 18);
+ *(ptr++) = 0x80 | static_cast((c >> 12) & 0x3F);
+ *(ptr++) = 0x80 | static_cast((c >> 6) & 0x3F);
+ *(ptr++) = 0x80 | static_cast((c >> 0) & 0x3F);
+ }
+ return 4;
+ }
+
+ if (strict) {
+ if (ptr) { // Replacement character U+FFFD
+ *(ptr++) = 0xEF;
+ *(ptr++) = 0xBF;
+ *(ptr++) = 0xBD;
+ }
+ return 3;
+ }
+
+ if (c < (1 << (3 + 6 + 6 + 6 + 6))) {
+ if (ptr) {
+ *(ptr++) = 0xF8 | static_cast(c >> 24);
+ *(ptr++) = 0x80 | static_cast((c >> 18) & 0x3F);
+ *(ptr++) = 0x80 | static_cast((c >> 12) & 0x3F);
+ *(ptr++) = 0x80 | static_cast((c >> 6) & 0x3F);
+ *(ptr++) = 0x80 | static_cast((c >> 0) & 0x3F);
+ }
+ return 5;
+ }
+
+ if (ptr) {
+ *(ptr++) = 0xFC | static_cast(c >> 30);
+ *(ptr++) = 0x80 | static_cast((c >> 24) & 0x3F);
+ *(ptr++) = 0x80 | static_cast((c >> 18) & 0x3F);
+ *(ptr++) = 0x80 | static_cast((c >> 12) & 0x3F);
+ *(ptr++) = 0x80 | static_cast((c >> 6) & 0x3F);
+ *(ptr++) = 0x80 | static_cast((c >> 0) & 0x3F);
+ }
+ return 6;
+}
+
+size_t unicode::encode(EncodingTag, char16_t* ptr, char32_t c, bool strict) {
+ if (c < 0x10000) {
+ if (ptr) {
+ if (0xD800 <= c && c <= 0xDFFF && strict)
+ *(ptr++) = 0xFFFD;
+ else
+ *(ptr++) = static_cast(c);
+ }
+ return 1;
+ }
+
+ c -= 0x10000;
+
+ if (c < (1 << 20)) {
+ if (ptr) {
+ *(ptr++) = 0xD800 | static_cast((c >> 10) & 0x3FF);
+ *(ptr++) = 0xDC00 | static_cast((c >> 0) & 0x3FF);
+ }
+ return 2;
+ }
+
+ if (ptr)
+ *(ptr++) = 0xFFFD;
+ return 1;
+}
+
+size_t unicode::encode(EncodingTag, char32_t* ptr, char32_t c, bool strict) {
+ if (ptr)
+ *ptr = c;
+ return 1;
+}
+
+size_t unicode::encode(EncodingTag, char* ptr, char32_t c, bool strict) {
+ return encode(EncodingTag(), reinterpret_cast(ptr), c, strict);
+}
+
+size_t unicode::encode(EncodingTag, wchar_t* ptr, char32_t c, bool strict) {
+ return encode(EncodingTag(), reinterpret_cast(ptr), c, strict);
+}
diff --git a/Dalamud.Boot/unicode.h b/Dalamud.Boot/unicode.h
new file mode 100644
index 000000000..53e584eeb
--- /dev/null
+++ b/Dalamud.Boot/unicode.h
@@ -0,0 +1,92 @@
+#pragma once
+
+#include
+#include
+#include
+#include
+
+namespace unicode {
+ constexpr char32_t UReplacement = U'\uFFFD';
+ constexpr char32_t UInvalid = U'\uFFFF';
+
+ template struct EncodingTag {};
+
+ size_t decode(EncodingTag, char32_t& out, const char8_t* in, size_t nRemainingBytes, bool strict);
+
+ size_t decode(EncodingTag, char32_t& out, const char16_t* in, size_t nRemainingBytes, bool strict);
+
+ size_t decode(EncodingTag, char32_t& out, const char32_t* in, size_t nRemainingBytes, bool strict);
+
+ size_t decode(EncodingTag, char32_t& out, const char* in, size_t nRemainingBytes, bool strict);
+
+ size_t decode(EncodingTag, char32_t& out, const wchar_t* in, size_t nRemainingBytes, bool strict);
+
+ template
+ inline size_t decode(char32_t& out, const T* in, size_t nRemainingBytes, bool strict = true) {
+ return decode(EncodingTag(), out, in, nRemainingBytes, strict);
+ }
+
+ size_t encode(EncodingTag, char8_t* ptr, char32_t c, bool strict);
+
+ size_t encode(EncodingTag, char16_t* ptr, char32_t c, bool strict);
+
+ size_t encode(EncodingTag, char32_t* ptr, char32_t c, bool strict);
+
+ size_t encode(EncodingTag, char* ptr, char32_t c, bool strict);
+
+ size_t encode(EncodingTag, wchar_t* ptr, char32_t c, bool strict);
+
+ template
+ inline size_t encode(T* ptr, char32_t c, bool strict = true) {
+ return encode(EncodingTag(), ptr, c, strict);
+ }
+
+ template>
+ inline TTo& convert(TTo& out, const std::basic_string_view& in, bool strict = true) {
+ out.reserve(out.size() + in.size() * 4 / sizeof(in[0]) / sizeof(out[0]));
+
+ char32_t c{};
+ for (size_t decLen = 0, decIdx = 0; decIdx < in.size() && (decLen = unicode::decode(c, &in[decIdx], in.size() - decIdx, strict)); decIdx += decLen) {
+ const auto encIdx = out.size();
+ const auto encLen = unicode::encode(nullptr, c, strict);
+ out.resize(encIdx + encLen);
+ unicode::encode(&out[encIdx], c, strict);
+ }
+
+ return out;
+ }
+
+ template, class TFromAlloc = std::allocator>
+ inline TTo& convert(TTo& out, const std::basic_string& in, bool strict = true) {
+ return convert(out, std::basic_string_view(in), strict);
+ }
+
+ template>>
+ inline TTo& convert(TTo& out, const TFromElem* in, size_t length = (std::numeric_limits::max)(), bool strict = true) {
+ if (length == (std::numeric_limits::max)())
+ length = std::char_traits::length(in);
+
+ return convert(out, std::basic_string_view(in, length), strict);
+ }
+
+ template>
+ inline TTo convert(const std::basic_string_view& in, bool strict = true) {
+ TTo out{};
+ return convert(out, in, strict);
+ }
+
+ template, class TFromAlloc = std::allocator>
+ inline TTo convert(const std::basic_string& in, bool strict = true) {
+ TTo out{};
+ return convert(out, std::basic_string_view(in), strict);
+ }
+
+ template>>
+ inline TTo convert(const TFromElem* in, size_t length = (std::numeric_limits::max)(), bool strict = true) {
+ if (length == (std::numeric_limits::max)())
+ length = std::char_traits::length(in);
+
+ TTo out{};
+ return convert(out, std::basic_string_view(in, length), strict);
+ }
+}
diff --git a/Dalamud.Boot/utils.cpp b/Dalamud.Boot/utils.cpp
new file mode 100644
index 000000000..7294fab08
--- /dev/null
+++ b/Dalamud.Boot/utils.cpp
@@ -0,0 +1,434 @@
+#include "pch.h"
+
+#include "utils.h"
+
+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));
+
+ 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_for(std::string_view pattern, std::string_view mask, char cExactMatch, char cWildcard) {
+ if (pattern.size() != mask.size())
+ throw std::runtime_error("Length of pattern does not match the length of mask.");
+
+ std::string buf;
+ buf.reserve(pattern.size() * 4);
+ for (size_t i = 0; i < pattern.size(); i++) {
+ const auto c = pattern[i];
+ if (mask[i] == cWildcard) {
+ buf.push_back('.');
+ } else if (mask[i] == cExactMatch) {
+ buf.push_back('\\');
+ buf.push_back('x');
+ buf.push_back((c >> 4) < 10 ? (c >> 4) - 10 : 'A' + (c >> 4) - 10);
+ buf.push_back((c & 15) < 10 ? (c & 15) - 10 : 'A' + (c & 15) - 10);
+ }
+ }
+ m_patterns.emplace_back(buf);
+ return *this;
+}
+
+utils::signature_finder& utils::signature_finder::look_for(std::string_view pattern, char wildcardMask) {
+ std::string buf;
+ buf.reserve(pattern.size() * 4);
+ for (const auto& c : pattern) {
+ if (c == wildcardMask) {
+ buf.push_back('.');
+ } else {
+ buf.push_back('\\');
+ buf.push_back('x');
+ buf.push_back((c >> 4) < 10 ? '0' + (c >> 4) : 'A' + (c >> 4) - 10);
+ buf.push_back((c & 15) < 10 ? '0' + (c & 15) : 'A' + (c & 15) - 10);
+ }
+ }
+ m_patterns.emplace_back(buf);
+ return *this;
+}
+
+utils::signature_finder& utils::signature_finder::look_for(std::string_view pattern) {
+ std::string buf;
+ buf.reserve(pattern.size() * 4);
+ for (const auto& c : pattern) {
+ buf.push_back('\\');
+ buf.push_back('x');
+ buf.push_back((c >> 4) < 10 ? '0' + (c >> 4) : 'A' + (c >> 4) - 10);
+ buf.push_back((c & 15) < 10 ? '0' + (c & 15) : 'A' + (c & 15) - 10);
+ }
+ m_patterns.emplace_back(buf);
+ return *this;
+}
+
+utils::signature_finder& utils::signature_finder::look_for_hex(std::string_view pattern) {
+ std::string buf;
+ buf.reserve(pattern.size());
+ bool bHighByte = true;
+ for (size_t i = 0; i < pattern.size(); i++) {
+ int n = -1;
+ if ('0' <= pattern[i] && pattern[i] <= '9')
+ n = pattern[i] - '0';
+ else if ('a' <= pattern[i] && pattern[i] <= 'f')
+ n = 10 + pattern[i] - 'A';
+ else if ('A' <= pattern[i] && pattern[i] <= 'F')
+ n = 10 + pattern[i] - 'A';
+ else if (pattern[i] == '?' && i + 1 < pattern.size() && pattern[i + 1] == '?') {
+ i++;
+ n = -2;
+ } else if (pattern[i] == '?')
+ n = -2;
+
+ if (n == -1)
+ continue;
+ else if (n == -2) {
+ if (!bHighByte) {
+ buf.insert(buf.begin() + buf.size() - 1, '0');
+ bHighByte = true;
+ }
+ buf.push_back('.');
+ continue;
+ }
+
+ if (bHighByte) {
+ buf.push_back('\\');
+ buf.push_back('x');
+ }
+ buf.push_back(pattern[i]);
+ bHighByte = !bHighByte;
+ }
+ m_patterns.emplace_back(buf);
+ return *this;
+}
+
+std::vector utils::signature_finder::find(size_t minCount, size_t maxCount, bool bErrorOnMoreThanMaximum) const {
+ std::vector res;
+
+ for (const auto& rangeSpan : m_ranges) {
+ for (size_t patternIndex = 0; patternIndex < m_patterns.size(); patternIndex++) {
+ srell::match_results::iterator> matches;
+ auto ptr = rangeSpan.begin();
+ for (size_t matchIndex = 0;; ptr = matches[0].first + 1, matchIndex++) {
+ if (!m_patterns[patternIndex].search(ptr, rangeSpan.end(), rangeSpan.begin(), matches, srell::regex_constants::match_flag_type::match_default))
+ break;
+
+ for (size_t captureIndex = 0; captureIndex < matches.size(); captureIndex++) {
+ const auto& capture = matches[captureIndex];
+ res.emplace_back(
+ std::span(capture.first, capture.second),
+ patternIndex,
+ matchIndex,
+ captureIndex);
+
+ if (bErrorOnMoreThanMaximum) {
+ if (res.size() > maxCount)
+ throw std::runtime_error(std::format("Found {} result(s), wanted at most {} results", res.size(), maxCount));
+ } else if (res.size() == maxCount)
+ return res;
+ }
+ }
+ }
+ }
+
+ if (res.size() < minCount)
+ throw std::runtime_error(std::format("Found {} result(s), wanted at least {} results", res.size(), minCount));
+
+ return res;
+}
+
+std::span utils::signature_finder::find_one() const {
+ return find(1, 1, false).front().Match;
+}
+
+utils::memory_tenderizer::memory_tenderizer(const void* pAddress, size_t length, DWORD dwNewProtect) : m_data(reinterpret_cast(const_cast(pAddress)), length) {
+ try {
+ for (auto pCoveredAddress = &m_data[0];
+ pCoveredAddress < &m_data[0] + m_data.size();
+ pCoveredAddress = reinterpret_cast(m_regions.back().BaseAddress) + m_regions.back().RegionSize) {
+
+ MEMORY_BASIC_INFORMATION region{};
+ if (!VirtualQuery(pCoveredAddress, ®ion, sizeof region)) {
+ throw std::runtime_error(std::format(
+ "VirtualQuery(addr=0x{:X}, ..., cb={}) failed with Win32 code 0x{:X}",
+ reinterpret_cast(pCoveredAddress),
+ sizeof region,
+ GetLastError()));
+ }
+
+ if (!VirtualProtect(region.BaseAddress, region.RegionSize, dwNewProtect, ®ion.Protect)) {
+ throw std::runtime_error(std::format(
+ "(Change)VirtualProtect(addr=0x{:X}, size=0x{:X}, ..., ...) failed with Win32 code 0x{:X}",
+ reinterpret_cast(region.BaseAddress),
+ region.RegionSize,
+ GetLastError()));
+ }
+
+ m_regions.emplace_back(region);
+ }
+
+ } catch (...) {
+ for (auto& region : std::ranges::reverse_view(m_regions)) {
+ if (!VirtualProtect(region.BaseAddress, region.RegionSize, region.Protect, ®ion.Protect)) {
+ // Could not restore; fast fail
+ __fastfail(GetLastError());
+ }
+ }
+
+ throw;
+ }
+}
+
+utils::memory_tenderizer::~memory_tenderizer() {
+ for (auto& region : std::ranges::reverse_view(m_regions)) {
+ if (!VirtualProtect(region.BaseAddress, region.RegionSize, region.Protect, ®ion.Protect)) {
+ // Could not restore; fast fail
+ __fastfail(GetLastError());
+ }
+ }
+}
+
+std::shared_ptr utils::allocate_executable_heap(size_t len) {
+ static std::weak_ptr s_hHeap;
+
+ std::shared_ptr hHeap;
+ if (hHeap = s_hHeap.lock(); !hHeap) {
+ static std::mutex m_mtx;
+ const auto lock = std::lock_guard(m_mtx);
+
+ if (hHeap = s_hHeap.lock(); !hHeap) {
+ if (const auto hHeapRaw = HeapCreate(HEAP_CREATE_ENABLE_EXECUTE, 0, 0); hHeapRaw)
+ s_hHeap = hHeap = std::shared_ptr(hHeapRaw, HeapDestroy);
+ else
+ throw std::runtime_error("Failed to create heap.");
+ }
+ }
+
+ const auto pAllocRaw = HeapAlloc(hHeap.get(), 0, len);
+ if (!pAllocRaw)
+ throw std::runtime_error("Failed to allocate memory.");
+
+ return {
+ pAllocRaw,
+ [hHeap = std::move(hHeap)](void* pAddress) { HeapFree(hHeap.get(), 0, pAddress); },
+ };
+}
+
+void* utils::resolve_unconditional_jump_target(void* pfn) {
+ const auto bytes = reinterpret_cast(pfn);
+
+ // JMP QWORD PTR [RIP + int32]
+ // 48 FF 25 ?? ?? ?? ??
+ if (bytes[0] == 0x48 && bytes[1] == 0xFF && bytes[2] == 0x25)
+ return *reinterpret_cast(&bytes[7 + *reinterpret_cast(&bytes[3])]);
+
+ throw std::runtime_error("Unexpected thunk bytes.");
+}
+
+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);
+
+ size_t i = 0;
+ auto placeholderFound = false;
+ for (nmd_x86_instruction instruction{}; ; i += instruction.length) {
+ if (i == sourceCode.size() || !nmd_x86_decode(&sourceCode[i], sourceCode.size() - i, &instruction, NMD_X86_MODE_64, NMD_X86_DECODER_FLAGS_ALL)) {
+ sourceCode.insert(sourceCode.end(), &pcBaseFn[sourceCode.size()], &pcBaseFn[sourceCode.size() + 512]);
+ if (!nmd_x86_decode(&sourceCode[i], sourceCode.size() - i, &instruction, NMD_X86_MODE_64, NMD_X86_DECODER_FLAGS_ALL))
+ throw std::runtime_error("Failed to find detour function");
+ }
+
+ if (instruction.opcode == 0xCC)
+ throw std::runtime_error("Failed to find detour function");
+
+ // msvc debugger related
+ if ((instruction.group & NMD_GROUP_CALL) && (instruction.imm_mask & NMD_X86_IMM_ANY))
+ std::fill_n(&sourceCode[i], instruction.length, 0x90);
+
+ if ((instruction.group & NMD_GROUP_JUMP) || (instruction.group & NMD_GROUP_RET)) {
+ sourceCode.resize(i + instruction.length);
+ break;
+ }
+
+ if (instruction.opcode == 0xB8 // mov ,
+ && (instruction.imm_mask & NMD_X86_IMM64)
+ && instruction.immediate == placeholderValue) {
+ *reinterpret_cast(&sourceCode[i + instruction.length - 8]) = pThis;
+ placeholderFound = true;
+ }
+ }
+
+ if (!placeholderFound)
+ throw std::runtime_error("Failed to find detour function");
+
+ return allocate_executable_heap(std::span(sourceCode));
+}
+
+template<>
+std::wstring utils::get_env(const wchar_t* pcwzName) {
+ std::wstring buf(GetEnvironmentVariableW(pcwzName, nullptr, 0) + 1, L'\0');
+ buf.resize(GetEnvironmentVariableW(pcwzName, &buf[0], static_cast(buf.size())));
+ return buf;
+}
+
+template<>
+std::string utils::get_env(const wchar_t* pcwzName) {
+ return unicode::convert(get_env(pcwzName));
+}
+
+template<>
+bool utils::get_env(const wchar_t* pcwzName) {
+ auto env = get_env(pcwzName);
+ const auto trimmed = trim(std::wstring_view(env));
+ for (auto& c : env) {
+ if (c < 255)
+ c = std::tolower(c);
+ }
+ return trimmed == L"1"
+ || trimmed == L"true"
+ || trimmed == L"t"
+ || trimmed == L"yes"
+ || trimmed == L"y";
+}
+
+bool utils::is_running_on_linux() {
+ if (get_env(L"XL_WINEONLINUX"))
+ return true;
+ HMODULE hntdll = GetModuleHandleW(L"ntdll.dll");
+ if (!hntdll)
+ return true;
+ if (GetProcAddress(hntdll, "wine_get_version"))
+ return true;
+ if (GetProcAddress(hntdll, "wine_get_host_version"))
+ return true;
+ return false;
+}
+
+std::filesystem::path utils::get_module_path(HMODULE hModule) {
+ std::wstring buf(MAX_PATH, L'\0');
+ while (true) {
+ if (const auto res = GetModuleFileNameW(hModule, &buf[0], static_cast(buf.size())); !res)
+ throw std::runtime_error(std::format("GetModuleFileName failure: 0x{:X}", GetLastError()));
+ else if (res < buf.size()) {
+ buf.resize(res);
+ return buf;
+ } else
+ buf.resize(buf.size() * 2);
+ }
+}
diff --git a/Dalamud.Boot/utils.h b/Dalamud.Boot/utils.h
new file mode 100644
index 000000000..9180e0c8e
--- /dev/null
+++ b/Dalamud.Boot/utils.h
@@ -0,0 +1,155 @@
+#pragma once
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "unicode.h"
+
+namespace utils {
+ 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_for(std::string_view pattern, std::string_view mask, char cExactMatch = 'x', char cWildcard = '.');
+ signature_finder& look_for(std::string_view pattern, char wildcardMask);
+ signature_finder& look_for(std::string_view pattern);
+ signature_finder& look_for_hex(std::string_view pattern);
+
+ template
+ signature_finder& look_for(char pattern[len]) {
+ static_assert(len == 5);
+ }
+
+ struct result {
+ std::span Match;
+ size_t PatternIndex;
+ size_t MatchIndex;
+ size_t CaptureIndex;
+ };
+
+ std::vector find(size_t minCount, size_t maxCount, bool bErrorOnMoreThanMaximum) const;
+
+ std::span find_one() const;
+ };
+
+ class memory_tenderizer {
+ std::span m_data;
+ std::vector m_regions;
+
+ public:
+ memory_tenderizer(const void* pAddress, size_t length, DWORD dwNewProtect);
+
+ template&& std::is_standard_layout_v>>
+ memory_tenderizer(const T& object, DWORD dwNewProtect) : memory_tenderizer(&object, sizeof T, dwNewProtect) {}
+
+ template
+ memory_tenderizer(std::span s, DWORD dwNewProtect) : memory_tenderizer(&s[0], s.size(), dwNewProtect) {}
+
+ ~memory_tenderizer();
+ };
+
+ void* resolve_unconditional_jump_target(void* pfn);
+
+ 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