Implement xiv fixes into Dalamud.Boot (#857)

This commit is contained in:
kizer 2022-05-29 02:11:03 +09:00 committed by GitHub
parent 02dd1eddec
commit 75de126c9d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
40 changed files with 41576 additions and 196 deletions

View file

@ -58,6 +58,7 @@
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>false</IntrinsicFunctions>
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Use</PrecompiledHeader>
</ClCompile>
<Link>
<EnableCOMDATFolding>false</EnableCOMDATFolding>
@ -69,6 +70,7 @@
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Use</PrecompiledHeader>
</ClCompile>
<Link>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
@ -91,22 +93,24 @@
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
</ClCompile>
<ClCompile Include="logging.cpp" />
<ClCompile Include="unicode.cpp" />
<ClCompile Include="xivfixes.cpp" />
<ClCompile Include="utils.cpp" />
<ClCompile Include="pch_nmd_assembly_impl.cpp">
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
</ClCompile>
<ClCompile Include="pch.cpp">
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader>
</ClCompile>
<ClCompile Include="dllmain.cpp">
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Use</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Use</PrecompiledHeader>
</ClCompile>
<ClCompile Include="dllmain.cpp" />
<ClCompile Include="rewrite_entrypoint.cpp">
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Use</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Use</PrecompiledHeader>
</ClCompile>
<ClCompile Include="veh.cpp">
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Use</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Use</PrecompiledHeader>
</ClCompile>
<ClCompile Include="veh.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\lib\CoreCLR\boot.h" />
@ -114,8 +118,14 @@
<ClInclude Include="..\lib\CoreCLR\core\coreclr_delegates.h" />
<ClInclude Include="..\lib\CoreCLR\core\hostfxr.h" />
<ClInclude Include="..\lib\CoreCLR\nethost\nethost.h" />
<ClInclude Include="bootconfig.h" />
<ClInclude Include="hooks.h" />
<ClInclude Include="logging.h" />
<ClInclude Include="unicode.h" />
<ClInclude Include="utils.h" />
<ClInclude Include="pch.h" />
<ClInclude Include="veh.h" />
<ClInclude Include="xivfixes.h" />
</ItemGroup>
<Target Name="RemoveExtraFiles" AfterTargets="PostBuildEvent">
<Delete Files="$(OutDir)$(TargetName).lib" />

View file

@ -8,14 +8,14 @@
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
<Filter Include="Project Files">
<UniqueIdentifier>{0c915688-91ea-431f-8b68-845cad422a50}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="dllmain.cpp">
<Filter>Dalamud.Boot DLL</Filter>
</ClCompile>
<ClCompile Include="pch.cpp">
<Filter>Dalamud.Boot DLL</Filter>
</ClCompile>
<ClCompile Include="veh.cpp">
<Filter>Dalamud.Boot DLL</Filter>
</ClCompile>
@ -28,6 +28,24 @@
<ClCompile Include="..\lib\CoreCLR\boot.cpp">
<Filter>CoreCLR</Filter>
</ClCompile>
<ClCompile Include="pch_nmd_assembly_impl.cpp">
<Filter>Project Files</Filter>
</ClCompile>
<ClCompile Include="pch.cpp">
<Filter>Project Files</Filter>
</ClCompile>
<ClCompile Include="utils.cpp">
<Filter>Dalamud.Boot DLL</Filter>
</ClCompile>
<ClCompile Include="xivfixes.cpp">
<Filter>Dalamud.Boot DLL</Filter>
</ClCompile>
<ClCompile Include="logging.cpp">
<Filter>Dalamud.Boot DLL</Filter>
</ClCompile>
<ClCompile Include="unicode.cpp">
<Filter>Dalamud.Boot DLL</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\lib\CoreCLR\CoreCLR.h">
@ -49,6 +67,24 @@
<Filter>Dalamud.Boot DLL</Filter>
</ClInclude>
<ClInclude Include="pch.h">
<Filter>Project Files</Filter>
</ClInclude>
<ClInclude Include="utils.h">
<Filter>Dalamud.Boot DLL</Filter>
</ClInclude>
<ClInclude Include="hooks.h">
<Filter>Dalamud.Boot DLL</Filter>
</ClInclude>
<ClInclude Include="bootconfig.h">
<Filter>Dalamud.Boot DLL</Filter>
</ClInclude>
<ClInclude Include="xivfixes.h">
<Filter>Dalamud.Boot DLL</Filter>
</ClInclude>
<ClInclude Include="logging.h">
<Filter>Dalamud.Boot DLL</Filter>
</ClInclude>
<ClInclude Include="unicode.h">
<Filter>Dalamud.Boot DLL</Filter>
</ClInclude>
</ItemGroup>

25
Dalamud.Boot/bootconfig.h Normal file
View file

@ -0,0 +1,25 @@
#pragma once
#include "utils.h"
namespace bootconfig {
inline bool is_wait_messagebox() {
return utils::get_env<bool>(L"DALAMUD_WAIT_MESSAGEBOX");
}
inline bool is_show_console() {
return utils::get_env<bool>(L"DALAMUD_SHOW_CONSOLE");
}
inline bool is_wait_debugger() {
return utils::get_env<bool>(L"DALAMUD_WAIT_DEBUGGER");
}
inline bool is_veh_enabled() {
return utils::get_env<bool>(L"DALAMUD_IS_VEH");
}
inline bool is_veh_full() {
return utils::get_env<bool>("DALAMUD_IS_VEH_FULL");
}
}

View file

@ -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<char*>(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<logging::W>("Failed to do general fixups. Some things might not work.");
logging::print<logging::W>("Error: {}", e.what());
}
return false;
}
logging::print<logging::I>("Dalamud.Boot Injectable, (c) 2021 XIVLauncher Contributors");
logging::print<logging::I>("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<char*>(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<logging::I>("Waiting for debugger to attach...");
while (!IsDebuggerPresent())
Sleep(100);
printf("Debugger attached.\n");
logging::print<logging::I>("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<custom_component_entry_point_fn>(entrypoint_vfn);
using custom_component_entry_point_fn = void (CORECLR_DELEGATE_CALLTYPE*)(LPVOID, HANDLE);
const auto entrypoint_fn = reinterpret_cast<custom_component_entry_point_fn>(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<logging::I>("Initializing VEH...");
if (utils::is_running_on_linux()) {
logging::print<logging::I>("=> VEH was disabled, running on linux");
} else if (bootconfig::is_veh_enabled()) {
if (veh::add_handler(bootconfig::is_veh_full()))
logging::print<logging::I>("=> Done!");
else
logging::print<logging::I>("=> Failed!");
} else {
logging::print<logging::I>("VEH was disabled manually");
}
// ============================== Dalamud ==================================== //
printf("Initializing Dalamud... ");
logging::print<logging::I>("Initializing Dalamud...");
entrypoint_fn(lpParam, hMainThreadContinue);
printf("Done!\n");
#ifndef NDEBUG
fclose(stdin);
fclose(stdout);
fclose(stderr);
FreeConsole();
#endif
logging::print<logging::I>("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;
}

136
Dalamud.Boot/hooks.h Normal file
View file

@ -0,0 +1,136 @@
#pragma once
#include <limits>
#include "utils.h"
namespace hooks {
template<typename>
class base_hook;
template<typename TReturn, typename ... TArgs>
class base_hook<TReturn(TArgs...)> {
using TFn = TReturn(TArgs...);
private:
TFn* const m_pfnOriginal;
utils::thunk<TReturn(TArgs...)> m_thunk;
public:
base_hook(TFn* pfnOriginal)
: m_pfnOriginal(pfnOriginal)
, m_thunk(m_pfnOriginal) {
}
virtual ~base_hook() = default;
virtual void set_detour(std::function<TFn> 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<TArgs>(args)...);
}
protected:
TFn* get_original() const {
return m_pfnOriginal;
}
TFn* get_thunk() const {
return m_thunk.get_thunk();
}
};
template<typename TFn>
class import_hook : public base_hook<TFn> {
using Base = base_hook<TFn>;
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<TFn>(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<typename TFn>
class export_hook : public base_hook<TFn> {
using Base = base_hook<TFn>;
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<TFn*>(utils::resolve_unconditional_jump_target(pfnExportThunk)))
, m_pfnExportThunk(pfnExportThunk) {
auto pExportThunk = reinterpret_cast<uint8_t*>(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<TFn**>(&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<std::remove_pointer_t<WNDPROC>> {
using Base = base_hook<std::remove_pointer_t<WNDPROC>>;
const HWND s_hwnd;
public:
wndproc_hook(HWND hwnd)
: Base(reinterpret_cast<WNDPROC>(GetWindowLongPtrW(hwnd, GWLP_WNDPROC)))
, s_hwnd(hwnd) {
SetWindowLongPtrW(hwnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(Base::get_thunk()));
}
~wndproc_hook() override {
SetWindowLongPtrW(s_hwnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(Base::get_original()));
}
LRESULT call_original(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) override {
return CallWindowProcW(Base::get_original(), hwnd, msg, wParam, lParam);
}
};
}

35
Dalamud.Boot/logging.cpp Normal file
View file

@ -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<DWORD>(estr.size()), &wr, nullptr);
}

59
Dalamud.Boot/logging.h Normal file
View file

@ -0,0 +1,59 @@
#pragma once
#include <format>
#include <numeric>
#include <string>
#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<std::string>(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<Level level, typename T>
inline void print(const T* s) {
print(level, s);
}
template<typename Arg, typename...Args>
inline void print(Level level, const char* pcszFormat, Arg arg1, Args...args) {
print(level, std::format(pcszFormat, std::forward<Arg>(arg1), std::forward<Args>(args)...));
}
template<typename Arg, typename...Args>
inline void print(Level level, const wchar_t* pcszFormat, Arg arg1, Args...args) {
print(level, std::format(pcszFormat, std::forward<Arg>(arg1), std::forward<Args>(args)...));
}
template<Level level, typename T, typename Arg, typename...Args, typename = std::enable_if_t<std::is_integral_v<T>>>
inline void print(const T* pcszFormat, Arg arg1, Args...args) {
print(level, std::format(pcszFormat, std::forward<Arg>(arg1), std::forward<Args>(args)...));
}
};

View file

@ -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 <windows.h>
@ -17,6 +18,10 @@
#include <Psapi.h>
#include <Shlobj.h>
#include <TlHelp32.h>
#include <Dbt.h>
// MSVC Compiler Intrinsic
#include <intrin.h>
// C++ Standard Libraries
#include <cassert>
@ -24,18 +29,32 @@
#include <filesystem>
#include <format>
#include <fstream>
#include <functional>
#include <iostream>
#include <ranges>
#include <span>
#include <string>
#include <mutex>
#include <type_traits>
// 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<CoreCLR> g_clr;
#endif //PCH_H

View file

@ -0,0 +1,2 @@
#define NMD_ASSEMBLY_IMPLEMENTATION
#include "../lib/Nomade040-nmd/nmd_assembly.h"

View file

@ -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<logging::W>("Failed to check memory block 0x{:X}(len=0x{:X}): {}", mbi.BaseAddress, mbi.RegionSize, e.what());
continue;
}
}

235
Dalamud.Boot/unicode.cpp Normal file
View file

@ -0,0 +1,235 @@
#include "pch.h"
#include "unicode.h"
size_t unicode::decode(EncodingTag<char8_t>, 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<char32_t>(in[0]) & 0x1F) << 6) |
((static_cast<char32_t>(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<char32_t>(
((static_cast<char32_t>(in[0]) & 0x0F) << 12) |
((static_cast<char32_t>(in[1]) & 0x3F) << 6) |
((static_cast<char32_t>(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<char32_t>(in[0]) & 0x07) << 18) |
((static_cast<char32_t>(in[1]) & 0x3F) << 12) |
((static_cast<char32_t>(in[2]) & 0x3F) << 6) |
((static_cast<char32_t>(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<char32_t>(in[0]) & 0x07) << 24) |
((static_cast<char32_t>(in[1]) & 0x3F) << 18) |
((static_cast<char32_t>(in[2]) & 0x3F) << 12) |
((static_cast<char32_t>(in[3]) & 0x3F) << 6) |
((static_cast<char32_t>(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<char32_t>(in[0]) & 0x07) << 30) |
((static_cast<char32_t>(in[1]) & 0x3F) << 24) |
((static_cast<char32_t>(in[2]) & 0x3F) << 18) |
((static_cast<char32_t>(in[3]) & 0x3F) << 12) |
((static_cast<char32_t>(in[4]) & 0x3F) << 6) |
((static_cast<char32_t>(in[5]) & 0x3F) << 0));
return 5;
}
}
invalid:
out = UReplacement;
return 1;
}
size_t unicode::decode(EncodingTag<char16_t>, 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<char32_t>(in[0]) & 0x03FF) << 10) |
((static_cast<char32_t>(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>, 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<char>, char32_t& out, const char* in, size_t nRemainingBytes, bool strict) {
return decode(EncodingTag<char8_t>(), out, reinterpret_cast<const char8_t*>(in), nRemainingBytes, strict);
}
size_t unicode::decode(EncodingTag<wchar_t>, char32_t& out, const wchar_t* in, size_t nRemainingBytes, bool strict) {
return decode(EncodingTag<char16_t>(), out, reinterpret_cast<const char16_t*>(in), nRemainingBytes, strict);
}
size_t unicode::encode(EncodingTag<char8_t>, char8_t* ptr, char32_t c, bool strict) {
if (c < (1 << 7)) {
if (ptr)
*(ptr++) = static_cast<char8_t>(c);
return 1;
}
if (c < (1 << (5 + 6))) {
if (ptr) {
*(ptr++) = 0xC0 | static_cast<char8_t>(c >> 6);
*(ptr++) = 0x80 | static_cast<char8_t>((c >> 0) & 0x3F);
}
return 2;
}
if (c < (1 << (4 + 6 + 6))) {
if (ptr) {
*(ptr++) = 0xE0 | static_cast<char8_t>(c >> 12);
*(ptr++) = 0x80 | static_cast<char8_t>((c >> 6) & 0x3F);
*(ptr++) = 0x80 | static_cast<char8_t>((c >> 0) & 0x3F);
}
return 3;
}
if (c < (1 << (3 + 6 + 6 + 6))) {
if (ptr) {
*(ptr++) = 0xF0 | static_cast<char8_t>(c >> 18);
*(ptr++) = 0x80 | static_cast<char8_t>((c >> 12) & 0x3F);
*(ptr++) = 0x80 | static_cast<char8_t>((c >> 6) & 0x3F);
*(ptr++) = 0x80 | static_cast<char8_t>((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<char8_t>(c >> 24);
*(ptr++) = 0x80 | static_cast<char8_t>((c >> 18) & 0x3F);
*(ptr++) = 0x80 | static_cast<char8_t>((c >> 12) & 0x3F);
*(ptr++) = 0x80 | static_cast<char8_t>((c >> 6) & 0x3F);
*(ptr++) = 0x80 | static_cast<char8_t>((c >> 0) & 0x3F);
}
return 5;
}
if (ptr) {
*(ptr++) = 0xFC | static_cast<char8_t>(c >> 30);
*(ptr++) = 0x80 | static_cast<char8_t>((c >> 24) & 0x3F);
*(ptr++) = 0x80 | static_cast<char8_t>((c >> 18) & 0x3F);
*(ptr++) = 0x80 | static_cast<char8_t>((c >> 12) & 0x3F);
*(ptr++) = 0x80 | static_cast<char8_t>((c >> 6) & 0x3F);
*(ptr++) = 0x80 | static_cast<char8_t>((c >> 0) & 0x3F);
}
return 6;
}
size_t unicode::encode(EncodingTag<char16_t>, 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<char16_t>(c);
}
return 1;
}
c -= 0x10000;
if (c < (1 << 20)) {
if (ptr) {
*(ptr++) = 0xD800 | static_cast<char16_t>((c >> 10) & 0x3FF);
*(ptr++) = 0xDC00 | static_cast<char16_t>((c >> 0) & 0x3FF);
}
return 2;
}
if (ptr)
*(ptr++) = 0xFFFD;
return 1;
}
size_t unicode::encode(EncodingTag<char32_t>, char32_t* ptr, char32_t c, bool strict) {
if (ptr)
*ptr = c;
return 1;
}
size_t unicode::encode(EncodingTag<char>, char* ptr, char32_t c, bool strict) {
return encode(EncodingTag<char8_t>(), reinterpret_cast<char8_t*>(ptr), c, strict);
}
size_t unicode::encode(EncodingTag<wchar_t>, wchar_t* ptr, char32_t c, bool strict) {
return encode(EncodingTag<char16_t>(), reinterpret_cast<char16_t*>(ptr), c, strict);
}

92
Dalamud.Boot/unicode.h Normal file
View file

@ -0,0 +1,92 @@
#pragma once
#include <array>
#include <cstdint>
#include <string>
#include <type_traits>
namespace unicode {
constexpr char32_t UReplacement = U'\uFFFD';
constexpr char32_t UInvalid = U'\uFFFF';
template<typename T> struct EncodingTag {};
size_t decode(EncodingTag<char8_t>, char32_t& out, const char8_t* in, size_t nRemainingBytes, bool strict);
size_t decode(EncodingTag<char16_t>, char32_t& out, const char16_t* in, size_t nRemainingBytes, bool strict);
size_t decode(EncodingTag<char32_t>, char32_t& out, const char32_t* in, size_t nRemainingBytes, bool strict);
size_t decode(EncodingTag<char>, char32_t& out, const char* in, size_t nRemainingBytes, bool strict);
size_t decode(EncodingTag<wchar_t>, char32_t& out, const wchar_t* in, size_t nRemainingBytes, bool strict);
template<typename T>
inline size_t decode(char32_t& out, const T* in, size_t nRemainingBytes, bool strict = true) {
return decode(EncodingTag<T>(), out, in, nRemainingBytes, strict);
}
size_t encode(EncodingTag<char8_t>, char8_t* ptr, char32_t c, bool strict);
size_t encode(EncodingTag<char16_t>, char16_t* ptr, char32_t c, bool strict);
size_t encode(EncodingTag<char32_t>, char32_t* ptr, char32_t c, bool strict);
size_t encode(EncodingTag<char>, char* ptr, char32_t c, bool strict);
size_t encode(EncodingTag<wchar_t>, wchar_t* ptr, char32_t c, bool strict);
template<typename T>
inline size_t encode(T* ptr, char32_t c, bool strict = true) {
return encode(EncodingTag<T>(), ptr, c, strict);
}
template<class TTo, class TFromElem, class TFromTraits = std::char_traits<TFromElem>>
inline TTo& convert(TTo& out, const std::basic_string_view<TFromElem, TFromTraits>& 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<TTo::value_type>(nullptr, c, strict);
out.resize(encIdx + encLen);
unicode::encode(&out[encIdx], c, strict);
}
return out;
}
template<class TTo, class TFromElem, class TFromTraits = std::char_traits<TFromElem>, class TFromAlloc = std::allocator<TFromElem>>
inline TTo& convert(TTo& out, const std::basic_string<TFromElem, TFromTraits, TFromAlloc>& in, bool strict = true) {
return convert(out, std::basic_string_view<TFromElem, TFromTraits>(in), strict);
}
template<class TTo, class TFromElem, typename = std::enable_if_t<std::is_integral_v<TFromElem>>>
inline TTo& convert(TTo& out, const TFromElem* in, size_t length = (std::numeric_limits<size_t>::max)(), bool strict = true) {
if (length == (std::numeric_limits<size_t>::max)())
length = std::char_traits<TFromElem>::length(in);
return convert(out, std::basic_string_view<TFromElem>(in, length), strict);
}
template<class TTo, class TFromElem, class TFromTraits = std::char_traits<TFromElem>>
inline TTo convert(const std::basic_string_view<TFromElem, TFromTraits>& in, bool strict = true) {
TTo out{};
return convert(out, in, strict);
}
template<class TTo, class TFromElem, class TFromTraits = std::char_traits<TFromElem>, class TFromAlloc = std::allocator<TFromElem>>
inline TTo convert(const std::basic_string<TFromElem, TFromTraits, TFromAlloc>& in, bool strict = true) {
TTo out{};
return convert(out, std::basic_string_view<TFromElem, TFromTraits>(in), strict);
}
template<class TTo, class TFromElem, typename = std::enable_if_t<std::is_integral_v<TFromElem>>>
inline TTo convert(const TFromElem* in, size_t length = (std::numeric_limits<size_t>::max)(), bool strict = true) {
if (length == (std::numeric_limits<size_t>::max)())
length = std::char_traits<TFromElem>::length(in);
TTo out{};
return convert(out, std::basic_string_view<TFromElem>(in, length), strict);
}
}

434
Dalamud.Boot/utils.cpp Normal file
View file

@ -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<const char*>(pFirst), length));
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_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::result> utils::signature_finder::find(size_t minCount, size_t maxCount, bool bErrorOnMoreThanMaximum) const {
std::vector<result> res;
for (const auto& rangeSpan : m_ranges) {
for (size_t patternIndex = 0; patternIndex < m_patterns.size(); patternIndex++) {
srell::match_results<std::span<const char>::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<const char> 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<char*>(const_cast<void*>(pAddress)), length) {
try {
for (auto pCoveredAddress = &m_data[0];
pCoveredAddress < &m_data[0] + m_data.size();
pCoveredAddress = reinterpret_cast<char*>(m_regions.back().BaseAddress) + m_regions.back().RegionSize) {
MEMORY_BASIC_INFORMATION region{};
if (!VirtualQuery(pCoveredAddress, &region, sizeof region)) {
throw std::runtime_error(std::format(
"VirtualQuery(addr=0x{:X}, ..., cb={}) failed with Win32 code 0x{:X}",
reinterpret_cast<size_t>(pCoveredAddress),
sizeof region,
GetLastError()));
}
if (!VirtualProtect(region.BaseAddress, region.RegionSize, dwNewProtect, &region.Protect)) {
throw std::runtime_error(std::format(
"(Change)VirtualProtect(addr=0x{:X}, size=0x{:X}, ..., ...) failed with Win32 code 0x{:X}",
reinterpret_cast<size_t>(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, &region.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, &region.Protect)) {
// Could not restore; fast fail
__fastfail(GetLastError());
}
}
}
std::shared_ptr<void> utils::allocate_executable_heap(size_t len) {
static std::weak_ptr<void> s_hHeap;
std::shared_ptr<void> 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<void>(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<uint8_t*>(pfn);
// JMP QWORD PTR [RIP + int32]
// 48 FF 25 ?? ?? ?? ??
if (bytes[0] == 0x48 && bytes[1] == 0xFF && bytes[2] == 0x25)
return *reinterpret_cast<void**>(&bytes[7 + *reinterpret_cast<int*>(&bytes[3])]);
throw std::runtime_error("Unexpected thunk bytes.");
}
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);
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 <register>, <thunk placeholder 64bit value>
&& (instruction.imm_mask & NMD_X86_IMM64)
&& instruction.immediate == placeholderValue) {
*reinterpret_cast<void**>(&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<DWORD>(buf.size())));
return buf;
}
template<>
std::string utils::get_env(const wchar_t* pcwzName) {
return unicode::convert<std::string>(get_env<std::wstring>(pcwzName));
}
template<>
bool utils::get_env(const wchar_t* pcwzName) {
auto env = get_env<std::wstring>(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<bool>(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<int>(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);
}
}

155
Dalamud.Boot/utils.h Normal file
View file

@ -0,0 +1,155 @@
#pragma once
#include <filesystem>
#include <functional>
#include <span>
#include <string>
#include <memory>
#include <vector>
#include "unicode.h"
namespace utils {
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_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<size_t len>
signature_finder& look_for(char pattern[len]) {
static_assert(len == 5);
}
struct result {
std::span<const char> Match;
size_t PatternIndex;
size_t MatchIndex;
size_t CaptureIndex;
};
std::vector<result> find(size_t minCount, size_t maxCount, bool bErrorOnMoreThanMaximum) const;
std::span<const char> find_one() const;
};
class memory_tenderizer {
std::span<char> m_data;
std::vector<MEMORY_BASIC_INFORMATION> m_regions;
public:
memory_tenderizer(const void* pAddress, size_t length, DWORD dwNewProtect);
template<typename T, typename = std::enable_if_t<std::is_trivial_v<T>&& std::is_standard_layout_v<T>>>
memory_tenderizer(const T& object, DWORD dwNewProtect) : memory_tenderizer(&object, sizeof T, dwNewProtect) {}
template<typename T>
memory_tenderizer(std::span<const T> 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<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>
std::shared_ptr<void> allocate_executable_heap(std::span<T> data) {
auto res = allocate_executable_heap(data.size_bytes());
memcpy(res.get(), data.data(), data.size_bytes());
return res;
}
std::shared_ptr<void> create_thunk(void* pfnFunction, void* pThis, uint64_t placeholderValue);
template<typename>
class thunk;
template<typename TReturn, typename ... TArgs>
class thunk<TReturn(TArgs...)> {
using TFn = TReturn(TArgs...);
static constexpr uint64_t Placeholder = 0xCC90CC90CC90CC90ULL;
const std::shared_ptr<void> m_pThunk;
std::function<TFn> m_fnTarget;
public:
thunk(std::function<TFn> target)
: m_pThunk(utils::create_thunk(&detour_static, this, Placeholder))
, m_fnTarget(std::move(target)) {
}
void set_target(std::function<TFn> detour) {
m_fnTarget = std::move(detour);
}
TFn* get_thunk() const {
return reinterpret_cast<TFn*>(m_pThunk.get());
}
private:
// mark it as virtual to prevent compiler from inlining
virtual TReturn detour(TArgs... args) {
return m_fnTarget(std::forward<TArgs>(args)...);
}
static TReturn detour_static(TArgs... args) {
const volatile auto pThis = reinterpret_cast<thunk<TFn>*>(Placeholder);
return pThis->detour(args...);
}
};
template<class TElem, class TTraits>
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())))
view = view.substr(1);
}
if (right) {
while (!view.empty() && (view.back() < 255 && std::isspace(view.back())))
view = view.substr(0, view.size() - 1);
}
return view;
}
template<typename T>
T get_env(const wchar_t* pcwzName) {
static_assert(false);
}
template<>
std::wstring get_env(const wchar_t* pcwzName);
template<>
std::string get_env(const wchar_t* pcwzName);
template<>
bool get_env(const wchar_t* pcwzName);
template<typename T>
T get_env(const char* pcszName) {
return get_env<T>(unicode::convert<std::wstring>(pcszName).c_str());
}
bool is_running_on_linux();
std::filesystem::path get_module_path(HMODULE hModule);
}

132
Dalamud.Boot/xivfixes.cpp Normal file
View file

@ -0,0 +1,132 @@
#include "pch.h"
#include "xivfixes.h"
#include "hooks.h"
#include "logging.h"
#include "utils.h"
using TFnGetInputDeviceManager = void* ();
static TFnGetInputDeviceManager* GetGetInputDeviceManager(HWND hwnd) {
static TFnGetInputDeviceManager* pCached = nullptr;
if (pCached)
return pCached;
char szClassName[256];
GetClassNameA(hwnd, szClassName, static_cast<int>(sizeof szClassName));
WNDCLASSEXA wcx{};
GetClassInfoExA(g_hGameInstance, szClassName, &wcx);
const auto match = utils::signature_finder()
.look_in(g_hGameInstance, ".text")
.look_for_hex("41 81 fe 19 02 00 00 0f 87 ?? ?? 00 00 0f 84 ?? ?? 00 00")
.find_one();
auto ptr = match.data() + match.size() + *reinterpret_cast<const int*>(match.data() + match.size() - 4);
ptr += 4; // CMP RBX, 0x7
ptr += 2; // JNZ <giveup>
ptr += 7; // MOV RCX, <Framework::Instance>
ptr += 3; // TEST RCX, RCX
ptr += 2; // JZ <giveup>
ptr += 5; // CALL <GetInputDeviceManagerInstance()>
ptr += *reinterpret_cast<const int*>(ptr - 4);
return pCached = reinterpret_cast<TFnGetInputDeviceManager*>(ptr);
}
void xivfixes::prevent_devicechange_crashes(bool bApply) {
static const char* LogTag = "[xivfixes:prevent_devicechange_crashes]";
static std::optional<hooks::import_hook<decltype(CreateWindowExA)>> s_hookCreateWindowExA;
static std::optional<hooks::wndproc_hook> s_hookWndProc;
if (bApply) {
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);
if (!hWnd
|| hInstance != g_hGameInstance
|| 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.",
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();
s_hookWndProc.emplace(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)()) {
logging::print<logging::I>("{} WndProc(0x{:X}, WM_DEVICECHANGE, DBT_DEVNODES_CHANGED, {}) called but the game does not have InputDeviceManager initialized; doing nothing.", LogTag, reinterpret_cast<size_t>(hWnd), lParam);
return 0;
}
}
return s_hookWndProc->call_original(hWnd, uMsg, wParam, lParam);
});
return hWnd;
});
} else {
logging::print<logging::I>("{} Disable", LogTag);
s_hookCreateWindowExA.reset();
// This will effectively revert any other WndProc alterations, including Dalamud.
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;
if (bApply) {
hook.emplace("kernel32.dll", "OpenProcess", 0);
hook->set_detour([](DWORD dwDesiredAccess, BOOL bInheritHandle, DWORD dwProcessId)->HANDLE {
if (dwProcessId == GetCurrentProcessId()) {
logging::print<logging::I>("{} OpenProcess(0{:08X}, {}, {}) was invoked by thread {}.", LogTag, dwDesiredAccess, bInheritHandle, dwProcessId, GetCurrentThreadId());
// Prevent game from feeling unsafe that it restarts
if (dwDesiredAccess & PROCESS_VM_WRITE) {
logging::print<logging::I>("{} Returning failure with last error code set to ERROR_ACCESS_DENIED(5).", LogTag);
SetLastError(ERROR_ACCESS_DENIED);
return {};
}
}
return hook->call_original(dwDesiredAccess, bInheritHandle, dwProcessId);
});
} else {
logging::print<logging::I>("{} Disable", LogTag);
hook.reset();
}
}
void xivfixes::redirect_openprocess(bool bApply) {
static const char* LogTag = "[xivfixes:redirect_openprocess]";
static std::optional<hooks::export_hook<decltype(OpenProcess)>> hook;
if (bApply) {
logging::print<logging::I>("{} Enable", LogTag);
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 (HANDLE res; DuplicateHandle(GetCurrentProcess(), GetCurrentProcess(), GetCurrentProcess(), &res, dwDesiredAccess, bInheritHandle, 0))
return res;
return {};
}
return hook->call_original(dwDesiredAccess, bInheritHandle, dwProcessId);
});
} else {
logging::print<logging::I>("{} Disable", LogTag);
hook.reset();
}
}

13
Dalamud.Boot/xivfixes.h Normal file
View file

@ -0,0 +1,13 @@
#pragma once
namespace xivfixes {
void prevent_devicechange_crashes(bool bApply);
void disable_game_openprocess_access_check(bool bApply);
void redirect_openprocess(bool bApply);
inline void apply_all(bool bApply) {
prevent_devicechange_crashes(bApply);
disable_game_openprocess_access_check(bApply);
redirect_openprocess(bApply);
}
}