diff --git a/Dalamud.Boot/dllmain.cpp b/Dalamud.Boot/dllmain.cpp index 7bccd08e7..d8b295fde 100644 --- a/Dalamud.Boot/dllmain.cpp +++ b/Dalamud.Boot/dllmain.cpp @@ -16,7 +16,7 @@ DllExport DWORD WINAPI Initialize(LPVOID lpParam, HANDLE hMainThreadContinue) { logging::print("No log file path given; not logging to file."); else { try { - logging::start_file_logging(logFilePath); + logging::start_file_logging(logFilePath, !bootconfig::is_show_console()); logging::print(L"Logging to file: {}", logFilePath); } catch (const std::exception& e) { logging::print(L"Couldn't open log file: {}", logFilePath); @@ -84,6 +84,13 @@ DllExport DWORD WINAPI Initialize(LPVOID lpParam, HANDLE hMainThreadContinue) { if (bootconfig::wait_messagebox() & bootconfig::WaitMessageboxFlags::BeforeDalamudEntrypoint) MessageBoxW(nullptr, L"Press OK to continue", L"Dalamud Boot", MB_OK); + if (hMainThreadContinue) { + // Let the game initialize. + SetEvent(hMainThreadContinue); + } + + utils::wait_for_game_window(); + logging::print("Initializing Dalamud..."); entrypoint_fn(lpParam, hMainThreadContinue); logging::print("Done!"); diff --git a/Dalamud.Boot/hooks.cpp b/Dalamud.Boot/hooks.cpp index 367dbde47..a0d341786 100644 --- a/Dalamud.Boot/hooks.cpp +++ b/Dalamud.Boot/hooks.cpp @@ -37,14 +37,15 @@ static const auto LdrUnregisterDllNotification = utils::loaded_module(GetModuleH 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); }) { + , m_thunk("kernel32!GetProcAddress(Singleton Import Hook)", + [this](HMODULE hModule, LPCSTR lpProcName) { return get_proc_address_handler(hModule, lpProcName); }) { } hooks::getprocaddress_singleton_import_hook::~getprocaddress_singleton_import_hook() { LdrUnregisterDllNotification(m_ldrDllNotificationCookie); } -std::shared_ptr hooks::getprocaddress_singleton_import_hook::set_handler(std::wstring dllName, std::string functionName, void* pfnDetour) { +std::shared_ptr hooks::getprocaddress_singleton_import_hook::set_handler(std::wstring dllName, std::string functionName, void* pfnDetour, std::function fnOnOriginalAddressAvailable) { const auto hModule = GetModuleHandleW(dllName.c_str()); if (!hModule) throw std::out_of_range("Specified DLL is not found."); @@ -53,6 +54,8 @@ std::shared_ptr hooks::getprocaddress_singleton_import_hook::set_handler(s if (!pfn) throw std::out_of_range("Could not find the specified function."); + fnOnOriginalAddressAvailable(pfn); + auto& target = m_targetFns[hModule][functionName]; if (target) throw std::runtime_error("Specified function has already been hooked."); @@ -95,7 +98,7 @@ std::shared_ptr hooks::getprocaddre } void hooks::getprocaddress_singleton_import_hook::initialize() { - m_getProcAddressHandler = set_handler(L"kernel32.dll", "GetProcAddress", m_thunk.get_thunk()); + m_getProcAddressHandler = set_handler(L"kernel32.dll", "GetProcAddress", m_thunk.get_thunk(), [this](void*) {}); LdrRegisterDllNotification(0, [](ULONG notiReason, const LDR_DLL_NOTIFICATION_DATA* pData, void* context) { if (notiReason == LDR_DLL_NOTIFICATION_REASON_LOADED) { @@ -138,7 +141,7 @@ void hooks::getprocaddress_singleton_import_hook::hook_module(const utils::loade if (!hook) { logging::print("{} Hooking {}!{} imported by {}", LogTag, dllName, targetFn, unicode::convert(mod.path().wstring())); - hook.emplace(static_cast(pGetProcAddressImport), pfnThunk); + hook.emplace(std::format("getprocaddress_singleton_import_hook::hook_module({}!{})", dllName, targetFn), static_cast(pGetProcAddressImport), pfnThunk); } } } diff --git a/Dalamud.Boot/hooks.h b/Dalamud.Boot/hooks.h index 547b18fb7..ad3b2cc6c 100644 --- a/Dalamud.Boot/hooks.h +++ b/Dalamud.Boot/hooks.h @@ -7,8 +7,23 @@ namespace hooks { class base_untyped_hook { + std::string m_name; + public: + base_untyped_hook(std::string name) : m_name(name) {} + virtual ~base_untyped_hook() = default; + + virtual bool check_consistencies() const { + return true; + } + + virtual void assert_dominance() const { + } + + const std::string& name() const { + return m_name; + } }; template @@ -23,9 +38,10 @@ namespace hooks { utils::thunk m_thunk; public: - base_hook(TFn* pfnOriginal) - : m_pfnOriginal(pfnOriginal) - , m_thunk(m_pfnOriginal) { + base_hook(std::string name, TFn* pfnOriginal) + : base_untyped_hook(name) + , m_pfnOriginal(pfnOriginal) + , m_thunk(std::move(name), m_pfnOriginal) { } virtual void set_detour(std::function fn) { @@ -56,23 +72,34 @@ namespace hooks { TFn** const m_ppfnImportTableItem; public: - import_hook(TFn** ppfnImportTableItem) - : Base(*ppfnImportTableItem) + import_hook(std::string name, TFn** ppfnImportTableItem) + : Base(std::move(name), *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::loaded_module::current_process().get_imported_function_pointer(pcszDllName, pcszFunctionName, hintOrOrdinal)) { + import_hook(std::string name, const char* pcszDllName, const char* pcszFunctionName, int hintOrOrdinal) + : import_hook(std::move(name), utils::loaded_module::current_process().get_imported_function_pointer(pcszDllName, pcszFunctionName, hintOrOrdinal)) { } ~import_hook() override { const utils::memory_tenderizer tenderizer(m_ppfnImportTableItem, sizeof * m_ppfnImportTableItem, PAGE_READWRITE); - *m_ppfnImportTableItem = Base::get_original(); } + + bool check_consistencies() const override { + return *m_ppfnImportTableItem == Base::get_thunk(); + } + + void assert_dominance() const override { + if (check_consistencies()) + return; + + const utils::memory_tenderizer tenderizer(m_ppfnImportTableItem, sizeof * m_ppfnImportTableItem, PAGE_READWRITE); + *m_ppfnImportTableItem = Base::get_thunk(); + } }; template @@ -86,8 +113,8 @@ namespace hooks { TFn* m_pfnMinHookBridge; public: - direct_hook(TFn* pfnFunction) - : Base(pfnFunction) { + direct_hook(std::string name, TFn* pfnFunction) + : Base(std::move(name), pfnFunction) { if (const auto mhStatus = MH_CreateHook(pfnFunction, Base::get_thunk(), reinterpret_cast(&m_pfnMinHookBridge)); mhStatus != MH_OK) throw std::runtime_error(std::format("MH_CreateHook(0x{:X}, ...) failure: {}", reinterpret_cast(pfnFunction), static_cast(mhStatus))); @@ -106,22 +133,33 @@ namespace hooks { class wndproc_hook : public base_hook> { using Base = base_hook>; - const HWND s_hwnd; + const HWND m_hwnd; public: - wndproc_hook(HWND hwnd) - : Base(reinterpret_cast(GetWindowLongPtrW(hwnd, GWLP_WNDPROC))) - , s_hwnd(hwnd) { + wndproc_hook(std::string name, HWND hwnd) + : Base(std::move(name), reinterpret_cast(GetWindowLongPtrW(hwnd, GWLP_WNDPROC))) + , m_hwnd(hwnd) { SetWindowLongPtrW(hwnd, GWLP_WNDPROC, reinterpret_cast(Base::get_thunk())); } ~wndproc_hook() override { - SetWindowLongPtrW(s_hwnd, GWLP_WNDPROC, reinterpret_cast(Base::get_original())); + SetWindowLongPtrW(m_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); } + + bool check_consistencies() const override { + return GetWindowLongPtrW(m_hwnd, GWLP_WNDPROC) == reinterpret_cast(Base::get_thunk()); + } + + void assert_dominance() const override { + if (check_consistencies()) + return; + + SetWindowLongPtrW(m_hwnd, GWLP_WNDPROC, reinterpret_cast(Base::get_thunk())); + } }; class untyped_import_hook : public base_untyped_hook { @@ -129,8 +167,9 @@ namespace hooks { void* const m_pfnOriginalImport; public: - untyped_import_hook(void** ppfnImportTableItem, void* pThunk) - : m_pfnOriginalImport(*ppfnImportTableItem) + untyped_import_hook(std::string name, void** ppfnImportTableItem, void* pThunk) + : base_untyped_hook(std::move(name)) + , m_pfnOriginalImport(*ppfnImportTableItem) , m_ppfnImportTableItem(ppfnImportTableItem) { const utils::memory_tenderizer tenderizer(ppfnImportTableItem, sizeof * ppfnImportTableItem, PAGE_READWRITE); @@ -165,7 +204,7 @@ namespace hooks { getprocaddress_singleton_import_hook(); ~getprocaddress_singleton_import_hook(); - std::shared_ptr set_handler(std::wstring dllName, std::string functionName, void* pfnDetour); + std::shared_ptr set_handler(std::wstring dllName, std::string functionName, void* pfnDetour, std::function fnOnOriginalAddressAvailable); static std::shared_ptr get_instance(); @@ -187,11 +226,16 @@ namespace hooks { std::shared_ptr m_singleImportHook; public: - global_import_hook(std::wstring dllName, std::string functionName) - : m_thunk(nullptr) { + global_import_hook(std::string name, std::wstring dllName, std::string functionName) + : base_untyped_hook(name) + , m_thunk(std::move(name), nullptr) { - m_singleImportHook = getprocaddress_singleton_import_hook::get_instance()->set_handler(dllName, functionName, m_thunk.get_thunk()); - m_thunk.set_target(reinterpret_cast(m_singleImportHook.get())); + m_singleImportHook = getprocaddress_singleton_import_hook::get_instance()->set_handler( + dllName, + functionName, + m_thunk.get_thunk(), + [this](void* p) { m_thunk.set_target(reinterpret_cast(p)); } + ); } virtual void set_detour(std::function fn) { diff --git a/Dalamud.Boot/logging.cpp b/Dalamud.Boot/logging.cpp index fdd6df014..c9d729b18 100644 --- a/Dalamud.Boot/logging.cpp +++ b/Dalamud.Boot/logging.cpp @@ -7,6 +7,7 @@ #include "logging.h" static bool s_bLoaded = false; +static bool s_bSkipLogFileWrite = false; static std::shared_ptr s_hLogFile; void logging::print(Level level, const char* s) { @@ -45,13 +46,13 @@ void logging::print(Level level, const char* s) { DWORD wr{}; WriteFile(GetStdHandle(STD_ERROR_HANDLE), &estr[0], static_cast(estr.size()), &wr, nullptr); - if (s_hLogFile) { + if (s_hLogFile && !s_bSkipLogFileWrite) { WriteFile(s_hLogFile.get(), &estr[0], static_cast(estr.size()), &wr, nullptr); } } } -void logging::start_file_logging(const std::filesystem::path& path) { +void logging::start_file_logging(const std::filesystem::path& path, bool redirect_stderrout) { if (s_hLogFile) return; @@ -76,6 +77,12 @@ void logging::start_file_logging(const std::filesystem::path& path) { SetFilePointer(h, 0, 0, FILE_END); s_hLogFile = { h, &CloseHandle }; + + if (redirect_stderrout) { + SetStdHandle(STD_ERROR_HANDLE, h); + SetStdHandle(STD_OUTPUT_HANDLE, h); + s_bSkipLogFileWrite = true; + } } void logging::update_dll_load_status(bool loaded) { diff --git a/Dalamud.Boot/logging.h b/Dalamud.Boot/logging.h index 6822b8763..c3d7634c9 100644 --- a/Dalamud.Boot/logging.h +++ b/Dalamud.Boot/logging.h @@ -63,7 +63,7 @@ namespace logging { print(level, std::format(pcszFormat, std::forward(arg1), std::forward(args)...)); } - void start_file_logging(const std::filesystem::path& path); + void start_file_logging(const std::filesystem::path& path, bool redirect_stderrout = false); void update_dll_load_status(bool loaded); }; diff --git a/Dalamud.Boot/pch.h b/Dalamud.Boot/pch.h index 6fb270aae..37f15f1b1 100644 --- a/Dalamud.Boot/pch.h +++ b/Dalamud.Boot/pch.h @@ -34,6 +34,7 @@ #include #include #include +#include #include #include #include diff --git a/Dalamud.Boot/rewrite_entrypoint.cpp b/Dalamud.Boot/rewrite_entrypoint.cpp index 3b24c02c3..9e96ad35e 100644 --- a/Dalamud.Boot/rewrite_entrypoint.cpp +++ b/Dalamud.Boot/rewrite_entrypoint.cpp @@ -241,20 +241,6 @@ void* get_mapped_image_base_address(HANDLE hProcess, const std::filesystem::path throw std::runtime_error("corresponding base address not found"); } -/// @brief Find the game main window. -/// @return Handle to the game main window, or nullptr if it doesn't exist (yet). -HWND try_find_game_window() { - HWND hwnd = nullptr; - while ((hwnd = FindWindowExW(nullptr, hwnd, L"FFXIVGAME", nullptr))) { - DWORD pid; - GetWindowThreadProcessId(hwnd, &pid); - - if (pid == GetCurrentProcessId() && IsWindowVisible(hwnd)) - break; - } - return hwnd; -} - std::string from_utf16(const std::wstring& wstr, UINT codePage = CP_UTF8) { std::string str(WideCharToMultiByte(codePage, 0, &wstr[0], static_cast(wstr.size()), nullptr, 0, nullptr, nullptr), 0); WideCharToMultiByte(codePage, 0, &wstr[0], static_cast(wstr.size()), &str[0], static_cast(str.size()), nullptr, nullptr); @@ -360,15 +346,6 @@ DllExport DWORD WINAPI RewriteRemoteEntryPoint(HANDLE hProcess, const wchar_t* p return RewriteRemoteEntryPointW(hProcess, pcwzPath, to_utf16(pcszLoadInfo).c_str()); } -void wait_for_game_window() { - HWND game_window; - while (!(game_window = try_find_game_window())) { - WaitForInputIdle(GetCurrentProcess(), INFINITE); - Sleep(100); - }; - SendMessageW(game_window, WM_NULL, 0, 0); -} - /// @brief Entry point function "called" instead of game's original main entry point. /// @param params Parameters set up from RewriteRemoteEntryPoint. DllExport void WINAPI RewrittenEntryPoint(RewrittenEntryPointParameters& params) { @@ -383,21 +360,14 @@ DllExport void WINAPI RewrittenEntryPoint(RewrittenEntryPointParameters& params) std::string loadInfo; auto& params = *reinterpret_cast(p); { - - // Restore original entry point. // Use WriteProcessMemory instead of memcpy to avoid having to fiddle with VirtualProtect. write_process_memory_or_throw(GetCurrentProcess(), params.pEntrypoint, params.pEntrypointBytes, params.entrypointLength); // Make a copy of load info, as the whole params will be freed after this code block. loadInfo = params.pLoadInfo; - - // Let the game initialize. - SetEvent(params.hMainThreadContinue); } - wait_for_game_window(); - Initialize(&loadInfo[0], params.hMainThreadContinue); return 0; } catch (const std::exception& e) { diff --git a/Dalamud.Boot/utils.cpp b/Dalamud.Boot/utils.cpp index 14576a603..74c9b7208 100644 --- a/Dalamud.Boot/utils.cpp +++ b/Dalamud.Boot/utils.cpp @@ -488,3 +488,24 @@ std::filesystem::path utils::get_module_path(HMODULE hModule) { buf.resize(buf.size() * 2); } } + +HWND utils::try_find_game_window() { + HWND hwnd = nullptr; + while ((hwnd = FindWindowExW(nullptr, hwnd, L"FFXIVGAME", nullptr))) { + DWORD pid; + GetWindowThreadProcessId(hwnd, &pid); + + if (pid == GetCurrentProcessId() && IsWindowVisible(hwnd)) + break; + } + return hwnd; +} + +void utils::wait_for_game_window() { + HWND game_window; + while (!(game_window = try_find_game_window())) { + WaitForInputIdle(GetCurrentProcess(), INFINITE); + Sleep(100); + }; + SendMessageW(game_window, WM_NULL, 0, 0); +} diff --git a/Dalamud.Boot/utils.h b/Dalamud.Boot/utils.h index 87dfe7447..53dc13146 100644 --- a/Dalamud.Boot/utils.h +++ b/Dalamud.Boot/utils.h @@ -137,12 +137,14 @@ namespace utils { static constexpr uint64_t Placeholder = 0xCC90CC90CC90CC90ULL; const std::shared_ptr m_pThunk; + std::string m_name; std::function m_fnTarget; public: - thunk(std::function target) + thunk(std::string name, std::function target) : m_pThunk(utils::create_thunk(&detour_static, this, Placeholder)) - , m_fnTarget(std::move(target)) { + , m_fnTarget(std::move(target)) + , m_name(name) { } void set_target(std::function detour) { @@ -153,6 +155,10 @@ namespace utils { return reinterpret_cast(m_pThunk.get()); } + const std::string& name() const { + return m_name; + } + private: // mark it as virtual to prevent compiler from inlining virtual TReturn detour(TArgs... args) { @@ -249,4 +255,10 @@ namespace utils { bool is_running_on_linux(); std::filesystem::path get_module_path(HMODULE hModule); + + /// @brief Find the game main window. + /// @return Handle to the game main window, or nullptr if it doesn't exist (yet). + HWND try_find_game_window(); + + void wait_for_game_window(); } diff --git a/Dalamud.Boot/xivfixes.cpp b/Dalamud.Boot/xivfixes.cpp index 1b02b2dae..93bb09575 100644 --- a/Dalamud.Boot/xivfixes.cpp +++ b/Dalamud.Boot/xivfixes.cpp @@ -184,7 +184,7 @@ void xivfixes::prevent_devicechange_crashes(bool bApply) { return; } - s_hookCreateWindowExA.emplace("user32.dll", "CreateWindowExA", 0); + s_hookCreateWindowExA.emplace("user32.dll!CreateWindowExA (prevent_devicechange_crashes)", "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); @@ -198,7 +198,7 @@ void xivfixes::prevent_devicechange_crashes(bool bApply) { s_hookCreateWindowExA.reset(); - s_hookWndProc.emplace(hWnd); + s_hookWndProc.emplace("FFXIVGAME:WndProc (prevent_devicechange_crashes)", hWnd); s_hookWndProc->set_detour([](HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) -> LRESULT { if (uMsg == WM_DEVICECHANGE && wParam == DBT_DEVNODES_CHANGED) { if (!GetGetInputDeviceManager(hWnd)()) { @@ -239,7 +239,7 @@ void xivfixes::disable_game_openprocess_access_check(bool bApply) { return; } - s_hook.emplace("kernel32.dll", "OpenProcess", 0); + s_hook.emplace("kernel32.dll!OpenProcess (import, disable_game_openprocess_access_check)", "kernel32.dll", "OpenProcess", 0); s_hook->set_detour([](DWORD dwDesiredAccess, BOOL bInheritHandle, DWORD dwProcessId)->HANDLE { logging::print("{} OpenProcess(0x{:08X}, {}, {}) was invoked by thread {}.", LogTag, dwDesiredAccess, bInheritHandle, dwProcessId, GetCurrentThreadId()); @@ -267,6 +267,8 @@ void xivfixes::disable_game_openprocess_access_check(bool bApply) { void xivfixes::redirect_openprocess(bool bApply) { static const char* LogTag = "[xivfixes:redirect_openprocess]"; static std::shared_ptr s_hook; + static std::mutex s_silenceSetMtx; + static std::set s_silenceSet; if (bApply) { if (!bootconfig::gamefix_is_enabled(L"redirect_openprocess")) { @@ -275,10 +277,11 @@ void xivfixes::redirect_openprocess(bool bApply) { } if (bootconfig::dotnet_openprocess_hook_mode() == bootconfig::ImportHooks) { - auto hook = std::make_shared>(L"kernel32.dll", "OpenProcess"); + auto hook = std::make_shared>("kernel32.dll!OpenProcess (global import, redirect_openprocess)", L"kernel32.dll", "OpenProcess"); hook->set_detour([hook = hook.get()](DWORD dwDesiredAccess, BOOL bInheritHandle, DWORD dwProcessId)->HANDLE { if (dwProcessId == GetCurrentProcessId()) { - logging::print("{} OpenProcess(0x{:08X}, {}, {}) was invoked by thread {}. Redirecting to DuplicateHandle.", LogTag, dwDesiredAccess, bInheritHandle, dwProcessId, GetCurrentThreadId()); + if (s_silenceSet.emplace(GetCurrentThreadId()).second) + logging::print("{} OpenProcess(0x{:08X}, {}, {}) was invoked by thread {}. Redirecting to DuplicateHandle.", LogTag, dwDesiredAccess, bInheritHandle, dwProcessId, GetCurrentThreadId()); if (HANDLE res; DuplicateHandle(GetCurrentProcess(), GetCurrentProcess(), GetCurrentProcess(), &res, dwDesiredAccess, bInheritHandle, 0)) return res; @@ -292,10 +295,11 @@ void xivfixes::redirect_openprocess(bool bApply) { logging::print("{} Enable via import_hook", LogTag); } else { - auto hook = std::make_shared>(OpenProcess); + auto hook = std::make_shared>("kernel32.dll!OpenProcess (direct, redirect_openprocess)", OpenProcess); hook->set_detour([hook = hook.get()](DWORD dwDesiredAccess, BOOL bInheritHandle, DWORD dwProcessId)->HANDLE { if (dwProcessId == GetCurrentProcessId()) { - logging::print("{} OpenProcess(0x{:08X}, {}, {}) was invoked by thread {}. Redirecting to DuplicateHandle.", LogTag, dwDesiredAccess, bInheritHandle, dwProcessId, GetCurrentThreadId()); + if (s_silenceSet.emplace(GetCurrentThreadId()).second) + logging::print("{} OpenProcess(0x{:08X}, {}, {}) was invoked by thread {}. Redirecting to DuplicateHandle.", LogTag, dwDesiredAccess, bInheritHandle, dwProcessId, GetCurrentThreadId()); if (HANDLE res; DuplicateHandle(GetCurrentProcess(), GetCurrentProcess(), GetCurrentProcess(), &res, dwDesiredAccess, bInheritHandle, 0)) return res; @@ -309,6 +313,12 @@ void xivfixes::redirect_openprocess(bool bApply) { logging::print("{} Enable via direct_hook", LogTag); } + //std::thread([]() { + // SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_IDLE); + // for (const auto to = GetTickCount64() + 3000; GetTickCount64() < to;) + // s_hook->assert_dominance(); + //}).detach(); + } else { if (s_hook) { logging::print("{} Disable OpenProcess", LogTag); diff --git a/Dalamud/Interface/GameFonts/FdtReader.cs b/Dalamud/Interface/GameFonts/FdtReader.cs index 155766ded..db373d221 100644 --- a/Dalamud/Interface/GameFonts/FdtReader.cs +++ b/Dalamud/Interface/GameFonts/FdtReader.cs @@ -9,29 +9,31 @@ namespace Dalamud.Interface.GameFonts /// public class FdtReader { + private static unsafe T StructureFromByteArray (byte[] data, int offset) + { + var len = Marshal.SizeOf(); + if (offset + len > data.Length) + throw new Exception("Data too short"); + + fixed (byte* ptr = data) + return Marshal.PtrToStructure(new(ptr + offset)); + } + /// /// Initializes a new instance of the class. /// /// Content of a FDT file. public FdtReader(byte[] data) { - unsafe - { - fixed (byte* ptr = data) - { - this.FileHeader = *(FdtHeader*)ptr; - this.FontHeader = *(FontTableHeader*)(ptr + this.FileHeader.FontTableHeaderOffset); - this.KerningHeader = *(KerningTableHeader*)(ptr + this.FileHeader.KerningTableHeaderOffset); + this.FileHeader = StructureFromByteArray(data, 0); + this.FontHeader = StructureFromByteArray(data, this.FileHeader.FontTableHeaderOffset); + this.KerningHeader = StructureFromByteArray(data, this.FileHeader.KerningTableHeaderOffset); - var glyphs = (FontTableEntry*)(ptr + this.FileHeader.FontTableHeaderOffset + Marshal.SizeOf(this.FontHeader)); - for (var i = 0; i < this.FontHeader.FontTableEntryCount; i++) - this.Glyphs.Add(glyphs[i]); + for (var i = 0; i < this.FontHeader.FontTableEntryCount; i++) + this.Glyphs.Add(StructureFromByteArray(data, this.FileHeader.FontTableHeaderOffset + Marshal.SizeOf() + (Marshal.SizeOf() * i))); - var kerns = (KerningTableEntry*)(ptr + this.FileHeader.KerningTableHeaderOffset + Marshal.SizeOf(this.KerningHeader)); - for (var i = 0; i < this.FontHeader.KerningTableEntryCount; i++) - this.Distances.Add(kerns[i]); - } - } + for (int i = 0, i_ = Math.Min(this.FontHeader.KerningTableEntryCount, this.KerningHeader.Count); i < i_; i++) + this.Distances.Add(StructureFromByteArray(data, this.FileHeader.KerningTableHeaderOffset+ Marshal.SizeOf() + (Marshal.SizeOf() * i))); } ///