mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-12 18:27:23 +01:00
Implement xiv fixes into Dalamud.Boot (#857)
This commit is contained in:
parent
02dd1eddec
commit
75de126c9d
40 changed files with 41576 additions and 196 deletions
|
|
@ -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" />
|
||||
|
|
|
|||
|
|
@ -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
25
Dalamud.Boot/bootconfig.h
Normal 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");
|
||||
}
|
||||
}
|
||||
|
|
@ -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
136
Dalamud.Boot/hooks.h
Normal 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
35
Dalamud.Boot/logging.cpp
Normal 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
59
Dalamud.Boot/logging.h
Normal 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)...));
|
||||
}
|
||||
};
|
||||
|
|
@ -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
|
||||
|
|
|
|||
2
Dalamud.Boot/pch_nmd_assembly_impl.cpp
Normal file
2
Dalamud.Boot/pch_nmd_assembly_impl.cpp
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
#define NMD_ASSEMBLY_IMPLEMENTATION
|
||||
#include "../lib/Nomade040-nmd/nmd_assembly.h"
|
||||
|
|
@ -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
235
Dalamud.Boot/unicode.cpp
Normal 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
92
Dalamud.Boot/unicode.h
Normal 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
434
Dalamud.Boot/utils.cpp
Normal 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, ®ion, 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, ®ion.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, ®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<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
155
Dalamud.Boot/utils.h
Normal 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
132
Dalamud.Boot/xivfixes.cpp
Normal 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
13
Dalamud.Boot/xivfixes.h
Normal 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);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue