Merge branch 'master' into new_im_hooks

This commit is contained in:
Soreepeong 2024-03-21 00:39:26 +09:00
commit 50f74c55a7
460 changed files with 40079 additions and 12893 deletions

View file

@ -32,6 +32,9 @@
<IntDir>obj\$(Configuration)\</IntDir>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
<Import Project="$(VCTargetsPath)\BuildCustomizations\masm.props" />
</ImportGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<UseDebugLibraries>true</UseDebugLibraries>
<LibraryPath>$(SolutionDir)bin\lib\$(Configuration)\libMinHook\;$(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64)</LibraryPath>
@ -56,7 +59,7 @@
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableUAC>false</EnableUAC>
<AdditionalDependencies>Version.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>Version.lib;Shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>..\lib\CoreCLR;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
</Link>
</ItemDefinitionGroup>
@ -72,6 +75,7 @@
<Link>
<EnableCOMDATFolding>false</EnableCOMDATFolding>
<OptimizeReferences>false</OptimizeReferences>
<ModuleDefinitionFile Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">module.def</ModuleDefinitionFile>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Release'">
@ -85,9 +89,13 @@
<Link>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<ModuleDefinitionFile Condition="'$(Configuration)|$(Platform)'=='Release|x64'">module.def</ModuleDefinitionFile>
</Link>
</ItemDefinitionGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
<Import Project="$(VCTargetsPath)\BuildCustomizations\masm.targets" />
</ImportGroup>
<ItemGroup>
<Content Include="..\lib\CoreCLR\nethost\nethost.dll">
<Link>nethost.dll</Link>
@ -131,6 +139,7 @@
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
</ClCompile>
<ClCompile Include="ntdll.cpp" />
<ClCompile Include="unicode.cpp">
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
@ -178,6 +187,7 @@
<ClInclude Include="DalamudStartInfo.h" />
<ClInclude Include="hooks.h" />
<ClInclude Include="logging.h" />
<ClInclude Include="ntdll.h" />
<ClInclude Include="resource.h" />
<ClInclude Include="unicode.h" />
<ClInclude Include="utils.h" />
@ -191,8 +201,14 @@
<ItemGroup>
<Image Include="dalamud.ico" />
</ItemGroup>
<ItemGroup>
<MASM Include="rewrite_entrypoint_thunks.asm" />
</ItemGroup>
<ItemGroup>
<None Include="module.def" />
</ItemGroup>
<Target Name="RemoveExtraFiles" AfterTargets="PostBuildEvent">
<Delete Files="$(OutDir)$(TargetName).lib" />
<Delete Files="$(OutDir)$(TargetName).exp" />
</Target>
</Project>
</Project>

View file

@ -76,6 +76,9 @@
<ClCompile Include="DalamudStartInfo.cpp">
<Filter>Dalamud.Boot DLL</Filter>
</ClCompile>
<ClCompile Include="ntdll.cpp">
<Filter>Dalamud.Boot DLL</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\lib\CoreCLR\CoreCLR.h">
@ -143,6 +146,9 @@
</ClInclude>
<ClInclude Include="resource.h" />
<ClInclude Include="crashhandler_shared.h" />
<ClInclude Include="ntdll.h">
<Filter>Dalamud.Boot DLL</Filter>
</ClInclude>
<ClInclude Include="..\lib\reshade\include\reshade.hpp">
<Filter>ReshadePlugin</Filter>
</ClInclude>
@ -174,4 +180,14 @@
<ItemGroup>
<Image Include="dalamud.ico" />
</ItemGroup>
</Project>
<ItemGroup>
<MASM Include="rewrite_entrypoint_thunks.asm">
<Filter>Dalamud.Boot DLL</Filter>
</MASM>
</ItemGroup>
<ItemGroup>
<None Include="module.def">
<Filter>Dalamud.Boot DLL</Filter>
</None>
</ItemGroup>
</Project>

View file

@ -68,19 +68,37 @@ void from_json(const nlohmann::json& json, DalamudStartInfo::ClientLanguage& val
}
}
void from_json(const nlohmann::json& json, DalamudStartInfo::LoadMethod& value) {
if (json.is_number_integer()) {
value = static_cast<DalamudStartInfo::LoadMethod>(json.get<int>());
}
else if (json.is_string()) {
const auto langstr = unicode::convert<std::string>(json.get<std::string>(), &unicode::lower);
if (langstr == "entrypoint")
value = DalamudStartInfo::LoadMethod::Entrypoint;
else if (langstr == "inject")
value = DalamudStartInfo::LoadMethod::DllInject;
}
}
void from_json(const nlohmann::json& json, DalamudStartInfo& config) {
if (!json.is_object())
return;
config.DalamudLoadMethod = json.value("LoadMethod", config.DalamudLoadMethod);
config.WorkingDirectory = json.value("WorkingDirectory", config.WorkingDirectory);
config.ConfigurationPath = json.value("ConfigurationPath", config.ConfigurationPath);
config.LogPath = json.value("LogPath", config.LogPath);
config.LogName = json.value("LogName", config.LogName);
config.PluginDirectory = json.value("PluginDirectory", config.PluginDirectory);
config.DefaultPluginDirectory = json.value("DefaultPluginDirectory", config.DefaultPluginDirectory);
config.AssetDirectory = json.value("AssetDirectory", config.AssetDirectory);
config.Language = json.value("Language", config.Language);
config.GameVersion = json.value("GameVersion", config.GameVersion);
config.DelayInitializeMs = json.value("DelayInitializeMs", config.DelayInitializeMs);
config.TroubleshootingPackData = json.value("TroubleshootingPackData", std::string{});
config.DelayInitializeMs = json.value("DelayInitializeMs", config.DelayInitializeMs);
config.NoLoadPlugins = json.value("NoLoadPlugins", config.NoLoadPlugins);
config.NoLoadThirdPartyPlugins = json.value("NoLoadThirdPartyPlugins", config.NoLoadThirdPartyPlugins);
config.BootLogPath = json.value("BootLogPath", config.BootLogPath);
config.BootShowConsole = json.value("BootShowConsole", config.BootShowConsole);
@ -103,6 +121,7 @@ void from_json(const nlohmann::json& json, DalamudStartInfo& config) {
}
config.CrashHandlerShow = json.value("CrashHandlerShow", config.CrashHandlerShow);
config.NoExceptionHandlers = json.value("NoExceptionHandlers", config.NoExceptionHandlers);
}
void DalamudStartInfo::from_envvars() {

View file

@ -26,15 +26,25 @@ struct DalamudStartInfo {
};
friend void from_json(const nlohmann::json&, ClientLanguage&);
enum class LoadMethod : int {
Entrypoint,
DllInject,
};
friend void from_json(const nlohmann::json&, LoadMethod&);
LoadMethod DalamudLoadMethod = LoadMethod::Entrypoint;
std::string WorkingDirectory;
std::string ConfigurationPath;
std::string LogPath;
std::string LogName;
std::string PluginDirectory;
std::string DefaultPluginDirectory;
std::string AssetDirectory;
ClientLanguage Language = ClientLanguage::English;
std::string GameVersion;
int DelayInitializeMs = 0;
std::string TroubleshootingPackData;
int DelayInitializeMs = 0;
bool NoLoadPlugins;
bool NoLoadThirdPartyPlugins;
std::string BootLogPath;
bool BootShowConsole = false;
@ -49,6 +59,7 @@ struct DalamudStartInfo {
std::set<std::string> BootUnhookDlls{};
bool CrashHandlerShow = false;
bool NoExceptionHandlers = false;
friend void from_json(const nlohmann::json&, DalamudStartInfo&);
void from_envvars();

View file

@ -14,6 +14,7 @@ struct exception_info
CONTEXT ContextRecord;
uint64_t nLifetime;
HANDLE hThreadHandle;
HANDLE hEventHandle;
DWORD dwStackTraceLength;
DWORD dwTroubleshootingPackDataLength;
};

View file

@ -17,7 +17,7 @@ static void OnReshadeOverlay(reshade::api::effect_runtime *runtime) {
s_pfnReshadeOverlayCallback(reinterpret_cast<void*>(runtime->get_native()));
}
DWORD WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue) {
HRESULT WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue) {
g_startInfo.from_envvars();
std::string jsonParseError;
@ -122,7 +122,7 @@ DWORD WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue) {
logging::I("Calling InitializeClrAndGetEntryPoint");
void* entrypoint_vfn;
int result = InitializeClrAndGetEntryPoint(
const auto result = InitializeClrAndGetEntryPoint(
g_hModule,
g_startInfo.BootEnableEtw,
runtimeconfig_path,
@ -132,7 +132,7 @@ DWORD WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue) {
L"Dalamud.EntryPoint+InitDelegate, Dalamud",
&entrypoint_vfn);
if (result != 0)
if (FAILED(result))
return result;
using custom_component_entry_point_fn = void (CORECLR_DELEGATE_CALLTYPE*)(LPVOID, HANDLE, LPVOID);
@ -141,8 +141,8 @@ DWORD WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue) {
// ============================== VEH ======================================== //
logging::I("Initializing VEH...");
if (utils::is_running_on_linux()) {
logging::I("=> VEH was disabled, running on linux");
if (g_startInfo.NoExceptionHandlers) {
logging::W("=> Exception handlers are disabled from DalamudStartInfo.");
} else if (g_startInfo.BootVehEnabled) {
if (veh::add_handler(g_startInfo.BootVehFull, g_startInfo.WorkingDirectory))
logging::I("=> Done!");
@ -164,10 +164,10 @@ DWORD WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue) {
entrypoint_fn(lpParam, hMainThreadContinue, g_bReshadeAvailable ? &s_pfnReshadeOverlayCallback : nullptr);
logging::I("Done!");
return 0;
return S_OK;
}
DllExport DWORD WINAPI Initialize(LPVOID lpParam) {
extern "C" DWORD WINAPI Initialize(LPVOID lpParam) {
return InitializeImpl(lpParam, CreateEvent(nullptr, TRUE, FALSE, nullptr));
}

View file

@ -2,39 +2,9 @@
#include "hooks.h"
#include "ntdll.h"
#include "logging.h"
enum {
LDR_DLL_NOTIFICATION_REASON_LOADED = 1,
LDR_DLL_NOTIFICATION_REASON_UNLOADED = 2,
};
struct LDR_DLL_UNLOADED_NOTIFICATION_DATA {
ULONG Flags; //Reserved.
const UNICODE_STRING* FullDllName; //The full path name of the DLL module.
const UNICODE_STRING* BaseDllName; //The base file name of the DLL module.
PVOID DllBase; //A pointer to the base address for the DLL in memory.
ULONG SizeOfImage; //The size of the DLL image, in bytes.
};
struct LDR_DLL_LOADED_NOTIFICATION_DATA {
ULONG Flags; //Reserved.
const UNICODE_STRING* FullDllName; //The full path name of the DLL module.
const UNICODE_STRING* BaseDllName; //The base file name of the DLL module.
PVOID DllBase; //A pointer to the base address for the DLL in memory.
ULONG SizeOfImage; //The size of the DLL image, in bytes.
};
union LDR_DLL_NOTIFICATION_DATA {
LDR_DLL_LOADED_NOTIFICATION_DATA Loaded;
LDR_DLL_UNLOADED_NOTIFICATION_DATA Unloaded;
};
using PLDR_DLL_NOTIFICATION_FUNCTION = VOID CALLBACK(_In_ ULONG NotificationReason, _In_ const LDR_DLL_NOTIFICATION_DATA* NotificationData, _In_opt_ PVOID Context);
static const auto LdrRegisterDllNotification = utils::loaded_module(GetModuleHandleW(L"ntdll.dll")).get_exported_function<NTSTATUS(NTAPI)(ULONG Flags, PLDR_DLL_NOTIFICATION_FUNCTION NotificationFunction, PVOID Context, PVOID* Cookie)>("LdrRegisterDllNotification");
static const auto LdrUnregisterDllNotification = utils::loaded_module(GetModuleHandleW(L"ntdll.dll")).get_exported_function<NTSTATUS(NTAPI)(PVOID Cookie)>("LdrUnregisterDllNotification");
hooks::getprocaddress_singleton_import_hook::getprocaddress_singleton_import_hook()
: m_pfnGetProcAddress(GetProcAddress)
, m_thunk("kernel32!GetProcAddress(Singleton Import Hook)",

View file

@ -1,6 +1,5 @@
#pragma once
#include <limits>
#include <map>
#include "utils.h"

5
Dalamud.Boot/module.def Normal file
View file

@ -0,0 +1,5 @@
LIBRARY Dalamud.Boot
EXPORTS
Initialize @1
RewriteRemoteEntryPointW @2
RewrittenEntryPoint @3

15
Dalamud.Boot/ntdll.cpp Normal file
View file

@ -0,0 +1,15 @@
#include "pch.h"
#include "ntdll.h"
#include "utils.h"
NTSTATUS LdrRegisterDllNotification(ULONG Flags, PLDR_DLL_NOTIFICATION_FUNCTION NotificationFunction, PVOID Context, PVOID* Cookie) {
static const auto pfn = utils::loaded_module(GetModuleHandleW(L"ntdll.dll")).get_exported_function<NTSTATUS(NTAPI)(ULONG Flags, PLDR_DLL_NOTIFICATION_FUNCTION NotificationFunction, PVOID Context, PVOID* Cookie)>("LdrRegisterDllNotification");
return pfn(Flags, NotificationFunction, Context, Cookie);
}
NTSTATUS LdrUnregisterDllNotification(PVOID Cookie) {
static const auto pfn = utils::loaded_module(GetModuleHandleW(L"ntdll.dll")).get_exported_function<NTSTATUS(NTAPI)(PVOID Cookie)>("LdrUnregisterDllNotification");
return pfn(Cookie);
}

33
Dalamud.Boot/ntdll.h Normal file
View file

@ -0,0 +1,33 @@
#pragma once
// ntdll exports
enum {
LDR_DLL_NOTIFICATION_REASON_LOADED = 1,
LDR_DLL_NOTIFICATION_REASON_UNLOADED = 2,
};
struct LDR_DLL_UNLOADED_NOTIFICATION_DATA {
ULONG Flags; //Reserved.
const UNICODE_STRING* FullDllName; //The full path name of the DLL module.
const UNICODE_STRING* BaseDllName; //The base file name of the DLL module.
PVOID DllBase; //A pointer to the base address for the DLL in memory.
ULONG SizeOfImage; //The size of the DLL image, in bytes.
};
struct LDR_DLL_LOADED_NOTIFICATION_DATA {
ULONG Flags; //Reserved.
const UNICODE_STRING* FullDllName; //The full path name of the DLL module.
const UNICODE_STRING* BaseDllName; //The base file name of the DLL module.
PVOID DllBase; //A pointer to the base address for the DLL in memory.
ULONG SizeOfImage; //The size of the DLL image, in bytes.
};
union LDR_DLL_NOTIFICATION_DATA {
LDR_DLL_LOADED_NOTIFICATION_DATA Loaded;
LDR_DLL_UNLOADED_NOTIFICATION_DATA Unloaded;
};
using PLDR_DLL_NOTIFICATION_FUNCTION = VOID CALLBACK(_In_ ULONG NotificationReason, _In_ const LDR_DLL_NOTIFICATION_DATA* NotificationData, _In_opt_ PVOID Context);
NTSTATUS LdrRegisterDllNotification(ULONG Flags, PLDR_DLL_NOTIFICATION_FUNCTION NotificationFunction, PVOID Context, PVOID* Cookie);
NTSTATUS LdrUnregisterDllNotification(PVOID Cookie);

View file

@ -15,18 +15,28 @@
#include <Windows.h>
// Windows Header Files (2)
#include <DbgHelp.h>
#include <Dbt.h>
#include <dwmapi.h>
#include <iphlpapi.h>
#include <PathCch.h>
#include <Psapi.h>
#include <ShlObj.h>
#include <Shlwapi.h>
#include <SubAuth.h>
#include <TlHelp32.h>
// Windows Header Files (3)
#include <icmpapi.h> // Must be loaded after iphlpapi.h
// MSVC Compiler Intrinsic
#include <intrin.h>
// COM
#include <comdef.h>
// C++ Standard Libraries
#include <algorithm>
#include <cassert>
#include <chrono>
#include <cstdio>
@ -64,9 +74,6 @@
#include "unicode.h"
// Commonly used macros
#define DllExport extern "C" __declspec(dllexport)
// Global variables
extern HMODULE g_hModule;
extern HINSTANCE g_hGameInstance;

View file

@ -1,115 +1,92 @@
#include "pch.h"
#include "logging.h"
#include "utils.h"
DWORD WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue);
HRESULT WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue);
struct RewrittenEntryPointParameters {
void* pAllocation;
char* pEntrypoint;
char* pEntrypointBytes;
size_t entrypointLength;
char* pLoadInfo;
HANDLE hMainThread;
HANDLE hMainThreadContinue;
};
#pragma pack(push, 1)
struct EntryPointThunkTemplate {
struct DUMMYSTRUCTNAME {
struct {
const uint8_t op_mov_rdi[2]{ 0x48, 0xbf };
void* ptr = nullptr;
} fn;
namespace thunks {
constexpr uint64_t Terminator = 0xCCCCCCCCCCCCCCCCu;
constexpr uint64_t Placeholder = 0x0606060606060606u;
extern "C" void EntryPointReplacement();
extern "C" void RewrittenEntryPoint_Standalone();
const uint8_t op_call_rdi[2]{ 0xff, 0xd7 };
} CallTrampoline;
};
void* resolve_thunk_address(void (*pfn)()) {
const auto ptr = reinterpret_cast<uint8_t*>(pfn);
if (*ptr == 0xe9)
return ptr + 5 + *reinterpret_cast<int32_t*>(ptr + 1);
return ptr;
}
struct TrampolineTemplate {
const struct {
const uint8_t op_sub_rsp_imm[3]{ 0x48, 0x81, 0xec };
const uint32_t length = 0x80;
} stack_alloc;
size_t get_thunk_length(void (*pfn)()) {
size_t length = 0;
for (auto ptr = reinterpret_cast<char*>(resolve_thunk_address(pfn)); *reinterpret_cast<uint64_t*>(ptr) != Terminator; ptr++)
length++;
return length;
}
struct DUMMYSTRUCTNAME {
struct {
const uint8_t op_mov_rcx_imm[2]{ 0x48, 0xb9 };
void* val = nullptr;
} lpLibFileName;
template<typename T>
void* fill_placeholders(void* pfn, const T& value) {
auto ptr = static_cast<char*>(pfn);
struct {
const uint8_t op_mov_rdi_imm[2]{ 0x48, 0xbf };
decltype(&LoadLibraryW) ptr = nullptr;
} fn;
while (*reinterpret_cast<uint64_t*>(ptr) != Placeholder)
ptr++;
const uint8_t op_call_rdi[2]{ 0xff, 0xd7 };
} CallLoadLibrary_nethost;
*reinterpret_cast<uint64_t*>(ptr) = 0;
*reinterpret_cast<T*>(ptr) = value;
return ptr + sizeof(value);
}
struct DUMMYSTRUCTNAME {
struct {
const uint8_t op_mov_rcx_imm[2]{ 0x48, 0xb9 };
void* val = nullptr;
} lpLibFileName;
template<typename T, typename...TArgs>
void* fill_placeholders(void* ptr, const T& value, TArgs&&...more_values) {
return fill_placeholders(fill_placeholders(ptr, value), std::forward<TArgs>(more_values)...);
}
struct {
const uint8_t op_mov_rdi_imm[2]{ 0x48, 0xbf };
decltype(&LoadLibraryW) ptr = nullptr;
} fn;
std::vector<char> create_entrypointreplacement() {
std::vector<char> buf(get_thunk_length(&EntryPointReplacement));
memcpy(buf.data(), resolve_thunk_address(&EntryPointReplacement), buf.size());
return buf;
}
const uint8_t op_call_rdi[2]{ 0xff, 0xd7 };
} CallLoadLibrary_DalamudBoot;
std::vector<char> create_standalone_rewrittenentrypoint(const std::filesystem::path& dalamud_path) {
const auto nethost_path = std::filesystem::path(dalamud_path).replace_filename(L"nethost.dll");
struct {
const uint8_t hModule_op_mov_rcx_rax[3]{ 0x48, 0x89, 0xc1 };
// These are null terminated, since pointers are returned from .c_str()
const auto dalamud_path_wview = std::wstring_view(dalamud_path.c_str());
const auto nethost_path_wview = std::wstring_view(nethost_path.c_str());
struct {
const uint8_t op_mov_rdx_imm[2]{ 0x48, 0xba };
void* val = nullptr;
} lpProcName;
// +2 is for null terminator
const auto dalamud_path_view = std::span(reinterpret_cast<const char*>(dalamud_path_wview.data()), dalamud_path_wview.size() * 2 + 2);
const auto nethost_path_view = std::span(reinterpret_cast<const char*>(nethost_path_wview.data()), nethost_path_wview.size() * 2 + 2);
struct {
const uint8_t op_mov_rdi_imm[2]{ 0x48, 0xbf };
decltype(&GetProcAddress) ptr = nullptr;
} fn;
std::vector<char> buffer;
const auto thunk_template_length = thunks::get_thunk_length(&thunks::RewrittenEntryPoint_Standalone);
buffer.reserve(thunk_template_length + dalamud_path_view.size() + nethost_path_view.size());
buffer.resize(thunk_template_length);
memcpy(buffer.data(), resolve_thunk_address(&thunks::RewrittenEntryPoint_Standalone), thunk_template_length);
const uint8_t op_call_rdi[2]{ 0xff, 0xd7 };
} CallGetProcAddress;
// &::GetProcAddress will return Dalamud.dll's import table entry.
// GetProcAddress(..., "GetProcAddress") returns the address inside kernel32.dll.
const auto kernel32 = GetModuleHandleA("kernel32.dll");
struct {
const uint8_t op_add_rsp_imm[3]{ 0x48, 0x81, 0xc4 };
const uint32_t length = 0x80;
} stack_release;
struct DUMMYSTRUCTNAME2 {
// rdi := returned value from GetProcAddress
const uint8_t op_mov_rdi_rax[3]{ 0x48, 0x89, 0xc7 };
// rax := return address
const uint8_t op_pop_rax[1]{ 0x58 };
// rax := rax - sizeof thunk (last instruction must be call)
struct {
const uint8_t op_sub_rax_imm4[2]{ 0x48, 0x2d };
const uint32_t displacement = static_cast<uint32_t>(sizeof EntryPointThunkTemplate);
} op_sub_rax_to_entry_point;
struct {
const uint8_t op_mov_rcx_imm[2]{ 0x48, 0xb9 };
void* val = nullptr;
} param;
const uint8_t op_push_rax[1]{ 0x50 };
const uint8_t op_jmp_rdi[2]{ 0xff, 0xe7 };
} CallInjectEntryPoint;
const char buf_CallGetProcAddress_lpProcName[20] = "RewrittenEntryPoint";
uint8_t buf_EntryPointBackup[sizeof EntryPointThunkTemplate]{};
#pragma pack(push, 8)
RewrittenEntryPointParameters parameters{};
#pragma pack(pop)
};
#pragma pack(pop)
thunks::fill_placeholders(buffer.data(),
/* pfnLoadLibraryW = */ GetProcAddress(kernel32, "LoadLibraryW"),
/* pfnGetProcAddress = */ GetProcAddress(kernel32, "GetProcAddress"),
/* pRewrittenEntryPointParameters = */ Placeholder,
/* nNethostOffset = */ 0,
/* nDalamudOffset = */ nethost_path_view.size_bytes()
);
buffer.insert(buffer.end(), nethost_path_view.begin(), nethost_path_view.end());
buffer.insert(buffer.end(), dalamud_path_view.begin(), dalamud_path_view.end());
return buffer;
}
}
void read_process_memory_or_throw(HANDLE hProcess, void* pAddress, void* data, size_t len) {
SIZE_T read = 0;
@ -126,6 +103,7 @@ void read_process_memory_or_throw(HANDLE hProcess, void* pAddress, T& data) {
void write_process_memory_or_throw(HANDLE hProcess, void* pAddress, const void* data, size_t len) {
SIZE_T written = 0;
const utils::memory_tenderizer tenderizer(hProcess, pAddress, len, PAGE_EXECUTE_READWRITE);
if (!WriteProcessMemory(hProcess, pAddress, data, len, &written))
throw std::runtime_error("WriteProcessMemory failure");
if (written != len)
@ -170,10 +148,17 @@ void* get_mapped_image_base_address(HANDLE hProcess, const std::filesystem::path
exe.read(reinterpret_cast<char*>(&exe_section_headers[0]), sizeof IMAGE_SECTION_HEADER * exe_section_headers.size());
if (!exe)
throw std::runtime_error("Game executable is corrupt (Truncated section header).");
SYSTEM_INFO sysinfo;
GetSystemInfo(&sysinfo);
for (MEMORY_BASIC_INFORMATION mbi{};
VirtualQueryEx(hProcess, mbi.BaseAddress, &mbi, sizeof mbi);
mbi.BaseAddress = static_cast<char*>(mbi.BaseAddress) + mbi.RegionSize) {
// wine: apparently there exists a RegionSize of 0xFFF
mbi.RegionSize = (mbi.RegionSize + sysinfo.dwPageSize - 1) / sysinfo.dwPageSize * sysinfo.dwPageSize;
if (!(mbi.State & MEM_COMMIT) || mbi.Type != MEM_IMAGE)
continue;
@ -241,31 +226,22 @@ void* get_mapped_image_base_address(HANDLE hProcess, const std::filesystem::path
throw std::runtime_error("corresponding base address not found");
}
std::string from_utf16(const std::wstring& wstr, UINT codePage = CP_UTF8) {
std::string str(WideCharToMultiByte(codePage, 0, &wstr[0], static_cast<int>(wstr.size()), nullptr, 0, nullptr, nullptr), 0);
WideCharToMultiByte(codePage, 0, &wstr[0], static_cast<int>(wstr.size()), &str[0], static_cast<int>(str.size()), nullptr, nullptr);
return str;
}
std::wstring to_utf16(const std::string& str, UINT codePage = CP_UTF8, bool errorOnInvalidChars = false) {
std::wstring wstr(MultiByteToWideChar(codePage, 0, &str[0], static_cast<int>(str.size()), nullptr, 0), 0);
MultiByteToWideChar(codePage, errorOnInvalidChars ? MB_ERR_INVALID_CHARS : 0, &str[0], static_cast<int>(str.size()), &wstr[0], static_cast<int>(wstr.size()));
return wstr;
}
/// @brief Rewrite target process' entry point so that this DLL can be loaded and executed first.
/// @param hProcess Process handle.
/// @param pcwzPath Path to target process.
/// @param pcszLoadInfo JSON string to be passed to Initialize.
/// @return 0 if successful; nonzero if unsuccessful
/// @param pcwzLoadInfo JSON string to be passed to Initialize.
/// @return null if successful; memory containing wide string allocated via GlobalAlloc if unsuccessful
///
/// When the process has just been started up via CreateProcess (CREATE_SUSPENDED), GetModuleFileName and alikes result in an error.
/// Instead, we have to enumerate through all the files mapped into target process' virtual address space and find the base address
/// of memory region corresponding to the path given.
///
DllExport DWORD WINAPI RewriteRemoteEntryPointW(HANDLE hProcess, const wchar_t* pcwzPath, const wchar_t* pcwzLoadInfo) {
extern "C" HRESULT WINAPI RewriteRemoteEntryPointW(HANDLE hProcess, const wchar_t* pcwzPath, const wchar_t* pcwzLoadInfo) {
std::wstring last_operation;
SetLastError(ERROR_SUCCESS);
try {
const auto base_address = reinterpret_cast<char*>(get_mapped_image_base_address(hProcess, pcwzPath));
last_operation = L"get_mapped_image_base_address";
const auto base_address = static_cast<char*>(get_mapped_image_base_address(hProcess, pcwzPath));
IMAGE_DOS_HEADER dos_header{};
union {
@ -273,113 +249,150 @@ DllExport DWORD WINAPI RewriteRemoteEntryPointW(HANDLE hProcess, const wchar_t*
IMAGE_NT_HEADERS64 nt_header64{};
};
last_operation = L"read_process_memory_or_throw(base_address)";
read_process_memory_or_throw(hProcess, base_address, dos_header);
last_operation = L"read_process_memory_or_throw(base_address + dos_header.e_lfanew)";
read_process_memory_or_throw(hProcess, base_address + dos_header.e_lfanew, nt_header64);
const auto entrypoint = base_address + (nt_header32.OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC
? nt_header32.OptionalHeader.AddressOfEntryPoint
: nt_header64.OptionalHeader.AddressOfEntryPoint);
auto path = get_path_from_local_module(g_hModule).wstring();
path.resize(path.size() + 1); // ensure null termination
auto path_bytes = std::span(reinterpret_cast<const char*>(&path[0]), std::span(path).size_bytes());
last_operation = L"get_path_from_local_module(g_hModule)";
auto local_module_path = get_path_from_local_module(g_hModule);
last_operation = L"thunks::create_standalone_rewrittenentrypoint(local_module_path)";
auto standalone_rewrittenentrypoint = thunks::create_standalone_rewrittenentrypoint(local_module_path);
auto nethost_path = (get_path_from_local_module(g_hModule).parent_path() / L"nethost.dll").wstring();
nethost_path.resize(nethost_path.size() + 1); // ensure null termination
auto nethost_path_bytes = std::span(reinterpret_cast<const char*>(&nethost_path[0]), std::span(nethost_path).size_bytes());
last_operation = L"thunks::create_entrypointreplacement()";
auto entrypoint_replacement = thunks::create_entrypointreplacement();
auto load_info = from_utf16(pcwzLoadInfo);
last_operation = L"unicode::convert<std::string>(pcwzLoadInfo)";
auto load_info = unicode::convert<std::string>(pcwzLoadInfo);
load_info.resize(load_info.size() + 1); //ensure null termination
// Allocate full buffer in advance to keep reference to trampoline valid.
std::vector<uint8_t> buffer(sizeof TrampolineTemplate + load_info.size() + nethost_path_bytes.size() + path_bytes.size());
auto& trampoline = *reinterpret_cast<TrampolineTemplate*>(&buffer[0]);
const auto load_info_buffer = std::span(buffer).subspan(sizeof trampoline, load_info.size());
const auto nethost_path_buffer = std::span(buffer).subspan(sizeof trampoline + load_info.size(), nethost_path_bytes.size());
const auto dalamud_path_buffer = std::span(buffer).subspan(sizeof trampoline + load_info.size() + nethost_path_bytes.size(), path_bytes.size());
new(&trampoline)TrampolineTemplate(); // this line initializes given buffer instead of allocating memory
memcpy(&load_info_buffer[0], &load_info[0], load_info_buffer.size());
memcpy(&nethost_path_buffer[0], &nethost_path_bytes[0], nethost_path_buffer.size());
memcpy(&dalamud_path_buffer[0], &path_bytes[0], dalamud_path_buffer.size());
// Backup remote process' original entry point.
read_process_memory_or_throw(hProcess, entrypoint, trampoline.buf_EntryPointBackup);
const auto bufferSize = sizeof(RewrittenEntryPointParameters) + entrypoint_replacement.size() + load_info.size() + standalone_rewrittenentrypoint.size();
last_operation = std::format(L"std::vector alloc({}b)", bufferSize);
std::vector<uint8_t> buffer(bufferSize);
// Allocate buffer in remote process, which will be used to fill addresses in the local buffer.
const auto remote_buffer = reinterpret_cast<char*>(VirtualAllocEx(hProcess, nullptr, buffer.size(), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE));
// Fill the values to be used in RewrittenEntryPoint
trampoline.parameters = {
.pAllocation = remote_buffer,
.pEntrypoint = entrypoint,
.pEntrypointBytes = remote_buffer + offsetof(TrampolineTemplate, buf_EntryPointBackup),
.entrypointLength = sizeof trampoline.buf_EntryPointBackup,
.pLoadInfo = remote_buffer + (&load_info_buffer[0] - &buffer[0]),
};
last_operation = std::format(L"VirtualAllocEx({}b)", bufferSize);
const auto remote_buffer = static_cast<char*>(VirtualAllocEx(hProcess, nullptr, buffer.size(), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE));
auto& params = *reinterpret_cast<RewrittenEntryPointParameters*>(buffer.data());
params.entrypointLength = entrypoint_replacement.size();
params.pEntrypoint = entrypoint;
// Fill the addresses referred in machine code.
trampoline.CallLoadLibrary_nethost.lpLibFileName.val = remote_buffer + (&nethost_path_buffer[0] - &buffer[0]);
trampoline.CallLoadLibrary_nethost.fn.ptr = LoadLibraryW;
trampoline.CallLoadLibrary_DalamudBoot.lpLibFileName.val = remote_buffer + (&dalamud_path_buffer[0] - &buffer[0]);
trampoline.CallLoadLibrary_DalamudBoot.fn.ptr = LoadLibraryW;
trampoline.CallGetProcAddress.lpProcName.val = remote_buffer + offsetof(TrampolineTemplate, buf_CallGetProcAddress_lpProcName);
trampoline.CallGetProcAddress.fn.ptr = GetProcAddress;
trampoline.CallInjectEntryPoint.param.val = remote_buffer + offsetof(TrampolineTemplate, parameters);
// Backup original entry point.
last_operation = std::format(L"read_process_memory_or_throw(entrypoint, {}b)", entrypoint_replacement.size());
read_process_memory_or_throw(hProcess, entrypoint, &buffer[sizeof params], entrypoint_replacement.size());
memcpy(&buffer[sizeof params + entrypoint_replacement.size()], load_info.data(), load_info.size());
last_operation = L"thunks::fill_placeholders(EntryPointReplacement)";
thunks::fill_placeholders(standalone_rewrittenentrypoint.data(), remote_buffer);
memcpy(&buffer[sizeof params + entrypoint_replacement.size() + load_info.size()], standalone_rewrittenentrypoint.data(), standalone_rewrittenentrypoint.size());
// Write the local buffer into the buffer in remote process.
last_operation = std::format(L"write_process_memory_or_throw(remote_buffer, {}b)", buffer.size());
write_process_memory_or_throw(hProcess, remote_buffer, buffer.data(), buffer.size());
// Overwrite remote process' entry point with a thunk that immediately calls our trampoline function.
EntryPointThunkTemplate thunk{};
thunk.CallTrampoline.fn.ptr = remote_buffer;
write_process_memory_or_throw(hProcess, entrypoint, thunk);
last_operation = L"thunks::fill_placeholders(RewrittenEntryPoint_Standalone::pRewrittenEntryPointParameters)";
thunks::fill_placeholders(entrypoint_replacement.data(), remote_buffer + sizeof params + entrypoint_replacement.size() + load_info.size());
return 0;
// Overwrite remote process' entry point with a thunk that will load our DLLs and call our trampoline function.
last_operation = std::format(L"write_process_memory_or_throw(entrypoint={:X}, {}b)", reinterpret_cast<uintptr_t>(entrypoint), buffer.size());
write_process_memory_or_throw(hProcess, entrypoint, entrypoint_replacement.data(), entrypoint_replacement.size());
FlushInstructionCache(hProcess, entrypoint, entrypoint_replacement.size());
return S_OK;
} catch (const std::exception& e) {
OutputDebugStringA(std::format("RewriteRemoteEntryPoint failure: {} (GetLastError: {})\n", e.what(), GetLastError()).c_str());
return 1;
}
}
const auto err = GetLastError();
const auto hr = err == ERROR_SUCCESS ? E_FAIL : HRESULT_FROM_WIN32(err);
auto formatted = std::format(
L"{}: {} ({})",
last_operation,
unicode::convert<std::wstring>(e.what()),
utils::format_win32_error(err));
OutputDebugStringW((formatted + L"\r\n").c_str());
/// @deprecated
DllExport DWORD WINAPI RewriteRemoteEntryPoint(HANDLE hProcess, const wchar_t* pcwzPath, const char* pcszLoadInfo) {
return RewriteRemoteEntryPointW(hProcess, pcwzPath, to_utf16(pcszLoadInfo).c_str());
ICreateErrorInfoPtr cei;
if (FAILED(CreateErrorInfo(&cei)))
return hr;
if (FAILED(cei->SetSource(const_cast<LPOLESTR>(L"Dalamud.Boot"))))
return hr;
if (FAILED(cei->SetDescription(const_cast<LPOLESTR>(formatted.c_str()))))
return hr;
IErrorInfoPtr ei;
if (FAILED(cei.QueryInterface(IID_PPV_ARGS(&ei))))
return hr;
(void)SetErrorInfo(0, ei);
return hr;
}
}
/// @brief Entry point function "called" instead of game's original main entry point.
/// @param params Parameters set up from RewriteRemoteEntryPoint.
DllExport void WINAPI RewrittenEntryPoint(RewrittenEntryPointParameters& params) {
params.hMainThreadContinue = CreateEventW(nullptr, true, false, nullptr);
if (!params.hMainThreadContinue)
ExitProcess(-1);
extern "C" void WINAPI RewrittenEntryPoint_AdjustedStack(RewrittenEntryPointParameters & params) {
HANDLE hMainThreadContinue = nullptr;
auto hr = S_OK;
std::wstring last_operation;
std::wstring exc_msg;
SetLastError(ERROR_SUCCESS);
// Do whatever the work in a separate thread to minimize the stack usage at this context,
// as this function really should have been a naked procedure but __declspec(naked) isn't supported in x64 version of msvc.
params.hMainThread = CreateThread(nullptr, 0, [](void* p) -> DWORD {
try {
std::string loadInfo;
auto& params = *reinterpret_cast<RewrittenEntryPointParameters*>(p);
{
// Restore original entry point.
// Use WriteProcessMemory instead of memcpy to avoid having to fiddle with VirtualProtect.
write_process_memory_or_throw(GetCurrentProcess(), params.pEntrypoint, params.pEntrypointBytes, params.entrypointLength);
try {
const auto pOriginalEntryPointBytes = reinterpret_cast<char*>(&params) + sizeof(params);
const auto pLoadInfo = pOriginalEntryPointBytes + params.entrypointLength;
// Make a copy of load info, as the whole params will be freed after this code block.
loadInfo = params.pLoadInfo;
}
// Restore original entry point.
// Use WriteProcessMemory instead of memcpy to avoid having to fiddle with VirtualProtect.
last_operation = L"restore original entry point";
write_process_memory_or_throw(GetCurrentProcess(), params.pEntrypoint, pOriginalEntryPointBytes, params.entrypointLength);
FlushInstructionCache(GetCurrentProcess(), params.pEntrypoint, params.entrypointLength);
if (const auto err = InitializeImpl(&loadInfo[0], params.hMainThreadContinue))
throw std::exception(std::format("{:08X}", err).c_str());
return 0;
} catch (const std::exception& e) {
MessageBoxA(nullptr, std::format("Failed to load Dalamud.\n\nError: {}", e.what()).c_str(), "Dalamud.Boot", MB_OK | MB_ICONERROR);
ExitProcess(-1);
hMainThreadContinue = CreateEventW(nullptr, true, false, nullptr);
last_operation = L"hMainThreadContinue = CreateEventW";
if (!hMainThreadContinue)
throw std::runtime_error("CreateEventW");
last_operation = L"InitializeImpl";
hr = InitializeImpl(pLoadInfo, hMainThreadContinue);
} catch (const std::exception& e) {
if (hr == S_OK) {
const auto err = GetLastError();
hr = err == ERROR_SUCCESS ? E_FAIL : HRESULT_FROM_WIN32(err);
}
}, &params, 0, nullptr);
if (!params.hMainThread)
ExitProcess(-1);
CloseHandle(params.hMainThread);
WaitForSingleObject(params.hMainThreadContinue, INFINITE);
VirtualFree(params.pAllocation, 0, MEM_RELEASE);
ICreateErrorInfoPtr cei;
IErrorInfoPtr ei;
if (SUCCEEDED(CreateErrorInfo(&cei))
&& SUCCEEDED(cei->SetDescription(const_cast<wchar_t*>(unicode::convert<std::wstring>(e.what()).c_str())))
&& SUCCEEDED(cei.QueryInterface(IID_PPV_ARGS(&ei)))) {
(void)SetErrorInfo(0, ei);
}
}
if (FAILED(hr)) {
const _com_error err(hr);
auto desc = err.Description();
if (desc.length() == 0)
desc = err.ErrorMessage();
if (MessageBoxW(nullptr, std::format(
L"Failed to load Dalamud. Load game without Dalamud(yes) or abort(no)?\n\n{}\n{}",
last_operation,
desc.GetBSTR()).c_str(),
L"Dalamud.Boot", MB_OK | MB_YESNO) == IDNO)
ExitProcess(-1);
if (hMainThreadContinue) {
CloseHandle(hMainThreadContinue);
hMainThreadContinue = nullptr;
}
}
if (hMainThreadContinue)
WaitForSingleObject(hMainThreadContinue, INFINITE);
VirtualFree(&params, 0, MEM_RELEASE);
}

View file

@ -0,0 +1,82 @@
PUBLIC EntryPointReplacement
PUBLIC RewrittenEntryPoint_Standalone
PUBLIC RewrittenEntryPoint
; 06 and 07 are invalid opcodes
; CC is int3 = bp
; using 0CCCCCCCCCCCCCCCCh as function terminator
; using 00606060606060606h as placeholders
TERMINATOR = 0CCCCCCCCCCCCCCCCh
PLACEHOLDER = 00606060606060606h
.code
EntryPointReplacement PROC
start:
; rsp % 0x10 = 0x08
lea rax, [start]
push rax
; rsp % 0x10 = 0x00
mov rax, PLACEHOLDER
; this calls RewrittenEntryPoint_Standalone
jmp rax
dq TERMINATOR
EntryPointReplacement ENDP
RewrittenEntryPoint_Standalone PROC
start:
; stack is aligned to 0x10; see above
sub rsp, 20h
lea rcx, [embeddedData]
add rcx, qword ptr [nNethostOffset]
call qword ptr [pfnLoadLibraryW]
lea rcx, [embeddedData]
add rcx, qword ptr [nDalamudOffset]
call qword ptr [pfnLoadLibraryW]
mov rcx, rax
lea rdx, [pcszEntryPointName]
call qword ptr [pfnGetProcAddress]
mov rcx, qword ptr [pRewrittenEntryPointParameters]
; this calls RewrittenEntryPoint
jmp rax
pfnLoadLibraryW:
dq PLACEHOLDER
pfnGetProcAddress:
dq PLACEHOLDER
pRewrittenEntryPointParameters:
dq PLACEHOLDER
nNethostOffset:
dq PLACEHOLDER
nDalamudOffset:
dq PLACEHOLDER
pcszEntryPointName:
db "RewrittenEntryPoint", 0
embeddedData:
dq TERMINATOR
RewrittenEntryPoint_Standalone ENDP
EXTERN RewrittenEntryPoint_AdjustedStack :PROC
RewrittenEntryPoint PROC
; stack is aligned to 0x10; see above
call RewrittenEntryPoint_AdjustedStack
add rsp, 20h
ret
RewrittenEntryPoint ENDP
END

View file

@ -408,14 +408,20 @@ utils::signature_finder::result utils::signature_finder::find_one() const {
return find(1, 1, false).front();
}
utils::memory_tenderizer::memory_tenderizer(const void* pAddress, size_t length, DWORD dwNewProtect) : m_data(reinterpret_cast<char*>(const_cast<void*>(pAddress)), length) {
utils::memory_tenderizer::memory_tenderizer(const void* pAddress, size_t length, DWORD dwNewProtect)
: memory_tenderizer(GetCurrentProcess(), pAddress, length, dwNewProtect) {
}
utils::memory_tenderizer::memory_tenderizer(HANDLE hProcess, const void* pAddress, size_t length, DWORD dwNewProtect)
: m_process(hProcess)
, m_data(static_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) {
for (auto pCoveredAddress = m_data.data();
pCoveredAddress < m_data.data() + m_data.size();
pCoveredAddress = static_cast<char*>(m_regions.back().BaseAddress) + m_regions.back().RegionSize) {
MEMORY_BASIC_INFORMATION region{};
if (!VirtualQuery(pCoveredAddress, &region, sizeof region)) {
if (!VirtualQueryEx(hProcess, 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),
@ -423,7 +429,7 @@ utils::memory_tenderizer::memory_tenderizer(const void* pAddress, size_t length,
GetLastError()));
}
if (!VirtualProtect(region.BaseAddress, region.RegionSize, dwNewProtect, &region.Protect)) {
if (!VirtualProtectEx(hProcess, 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),
@ -436,7 +442,7 @@ utils::memory_tenderizer::memory_tenderizer(const void* pAddress, size_t length,
} catch (...) {
for (auto& region : std::ranges::reverse_view(m_regions)) {
if (!VirtualProtect(region.BaseAddress, region.RegionSize, region.Protect, &region.Protect)) {
if (!VirtualProtectEx(hProcess, region.BaseAddress, region.RegionSize, region.Protect, &region.Protect)) {
// Could not restore; fast fail
__fastfail(GetLastError());
}
@ -448,7 +454,7 @@ utils::memory_tenderizer::memory_tenderizer(const void* pAddress, size_t length,
utils::memory_tenderizer::~memory_tenderizer() {
for (auto& region : std::ranges::reverse_view(m_regions)) {
if (!VirtualProtect(region.BaseAddress, region.RegionSize, region.Protect, &region.Protect)) {
if (!VirtualProtectEx(m_process, region.BaseAddress, region.RegionSize, region.Protect, &region.Protect)) {
// Could not restore; fast fail
__fastfail(GetLastError());
}
@ -578,19 +584,6 @@ std::vector<std::string> utils::get_env_list(const wchar_t* pcszName) {
return res;
}
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) {
@ -657,3 +650,25 @@ std::wstring utils::escape_shell_arg(const std::wstring& arg) {
}
return res;
}
std::wstring utils::format_win32_error(DWORD err) {
wchar_t* pwszMsg = nullptr;
FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
nullptr,
err,
MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US),
reinterpret_cast<LPWSTR>(&pwszMsg),
0,
nullptr);
if (pwszMsg) {
std::wstring result = std::format(L"Win32 error ({}=0x{:X}): {}", err, err, pwszMsg);
while (!result.empty() && std::isspace(result.back()))
result.pop_back();
LocalFree(pwszMsg);
return result;
}
return std::format(L"Win32 error ({}=0x{:X})", err, err);
}

View file

@ -111,10 +111,13 @@ namespace utils {
};
class memory_tenderizer {
HANDLE m_process;
std::span<char> m_data;
std::vector<MEMORY_BASIC_INFORMATION> m_regions;
public:
memory_tenderizer(HANDLE hProcess, const void* pAddress, size_t length, DWORD dwNewProtect);
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>>>
@ -264,8 +267,6 @@ namespace utils {
return get_env_list<T>(unicode::convert<std::wstring>(pcszName).c_str());
}
bool is_running_on_linux();
std::filesystem::path get_module_path(HMODULE hModule);
/// @brief Find the game main window.
@ -275,4 +276,6 @@ namespace utils {
void wait_for_game_window();
std::wstring escape_shell_arg(const std::wstring& arg);
std::wstring format_win32_error(DWORD err);
}

View file

@ -6,6 +6,7 @@
#include "logging.h"
#include "utils.h"
#include "hooks.h"
#include "crashhandler_shared.h"
#include "DalamudStartInfo.h"
@ -24,8 +25,10 @@
PVOID g_veh_handle = nullptr;
bool g_veh_do_full_dump = false;
std::optional<hooks::import_hook<decltype(SetUnhandledExceptionFilter)>> g_HookSetUnhandledExceptionFilter;
HANDLE g_crashhandler_process = nullptr;
HANDLE g_crashhandler_event = nullptr;
HANDLE g_crashhandler_pipe_write = nullptr;
std::recursive_mutex g_exception_handler_mutex;
@ -101,8 +104,24 @@ bool is_ffxiv_address(const wchar_t* module_name, const DWORD64 address)
static void append_injector_launch_args(std::vector<std::wstring>& args)
{
args.emplace_back(L"-g");
args.emplace_back(utils::loaded_module::current_process().path().wstring());
args.emplace_back(L"--game=\"" + utils::loaded_module::current_process().path().wstring() + L"\"");
switch (g_startInfo.DalamudLoadMethod) {
case DalamudStartInfo::LoadMethod::Entrypoint:
args.emplace_back(L"--mode=entrypoint");
break;
case DalamudStartInfo::LoadMethod::DllInject:
args.emplace_back(L"--mode=inject");
}
args.emplace_back(L"--dalamud-working-directory=\"" + unicode::convert<std::wstring>(g_startInfo.WorkingDirectory) + L"\"");
args.emplace_back(L"--dalamud-configuration-path=\"" + unicode::convert<std::wstring>(g_startInfo.ConfigurationPath) + L"\"");
args.emplace_back(L"--logpath=\"" + unicode::convert<std::wstring>(g_startInfo.LogPath) + L"\"");
args.emplace_back(L"--logname=\"" + unicode::convert<std::wstring>(g_startInfo.LogName) + L"\"");
args.emplace_back(L"--dalamud-plugin-directory=\"" + unicode::convert<std::wstring>(g_startInfo.PluginDirectory) + L"\"");
args.emplace_back(L"--dalamud-asset-directory=\"" + unicode::convert<std::wstring>(g_startInfo.AssetDirectory) + L"\"");
args.emplace_back(std::format(L"--dalamud-client-language={}", static_cast<int>(g_startInfo.Language)));
args.emplace_back(std::format(L"--dalamud-delay-initialize={}", g_startInfo.DelayInitializeMs));
// NoLoadPlugins/NoLoadThirdPartyPlugins: supplied from DalamudCrashHandler
if (g_startInfo.BootShowConsole)
args.emplace_back(L"--console");
if (g_startInfo.BootEnableEtw)
@ -128,6 +147,85 @@ static void append_injector_launch_args(std::vector<std::wstring>& args)
}
LONG exception_handler(EXCEPTION_POINTERS* ex)
{
// block any other exceptions hitting the handler while the messagebox is open
const auto lock = std::lock_guard(g_exception_handler_mutex);
exception_info exinfo{};
exinfo.pExceptionPointers = ex;
exinfo.ExceptionPointers = *ex;
exinfo.ContextRecord = *ex->ContextRecord;
exinfo.ExceptionRecord = ex->ExceptionRecord ? *ex->ExceptionRecord : EXCEPTION_RECORD{};
const auto time_now = std::chrono::system_clock::now();
auto lifetime = std::chrono::duration_cast<std::chrono::seconds>(
time_now.time_since_epoch()).count()
- std::chrono::duration_cast<std::chrono::seconds>(
g_time_start.time_since_epoch()).count();
exinfo.nLifetime = lifetime;
DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), g_crashhandler_process, &exinfo.hThreadHandle, 0, TRUE, DUPLICATE_SAME_ACCESS);
DuplicateHandle(GetCurrentProcess(), g_crashhandler_event, g_crashhandler_process, &exinfo.hEventHandle, 0, TRUE, DUPLICATE_SAME_ACCESS);
std::wstring stackTrace;
if (!g_clr)
{
stackTrace = L"(no CLR stack trace available)";
}
else if (void* fn; const auto err = static_cast<DWORD>(g_clr->get_function_pointer(
L"Dalamud.EntryPoint, Dalamud",
L"VehCallback",
L"Dalamud.EntryPoint+VehDelegate, Dalamud",
nullptr, nullptr, &fn)))
{
stackTrace = std::format(L"Failed to read stack trace: 0x{:08x}", err);
}
else
{
stackTrace = static_cast<wchar_t*(*)()>(fn)();
// Don't free it, as the program's going to be quit anyway
}
exinfo.dwStackTraceLength = static_cast<DWORD>(stackTrace.size());
exinfo.dwTroubleshootingPackDataLength = static_cast<DWORD>(g_startInfo.TroubleshootingPackData.size());
if (DWORD written; !WriteFile(g_crashhandler_pipe_write, &exinfo, static_cast<DWORD>(sizeof exinfo), &written, nullptr) || sizeof exinfo != written)
return EXCEPTION_CONTINUE_SEARCH;
if (const auto nb = static_cast<DWORD>(std::span(stackTrace).size_bytes()))
{
if (DWORD written; !WriteFile(g_crashhandler_pipe_write, stackTrace.data(), nb, &written, nullptr) || nb != written)
return EXCEPTION_CONTINUE_SEARCH;
}
if (const auto nb = static_cast<DWORD>(std::span(g_startInfo.TroubleshootingPackData).size_bytes()))
{
if (DWORD written; !WriteFile(g_crashhandler_pipe_write, g_startInfo.TroubleshootingPackData.data(), nb, &written, nullptr) || nb != written)
return EXCEPTION_CONTINUE_SEARCH;
}
AllowSetForegroundWindow(GetProcessId(g_crashhandler_process));
HANDLE waitHandles[] = { g_crashhandler_process, g_crashhandler_event };
DWORD waitResult = WaitForMultipleObjects(2, waitHandles, FALSE, INFINITE);
switch (waitResult) {
case WAIT_OBJECT_0:
logging::E("DalamudCrashHandler.exe exited unexpectedly");
break;
case WAIT_OBJECT_0 + 1:
logging::I("Crashing thread was resumed");
break;
default:
logging::E("Unexpected WaitForMultipleObjects return code 0x{:x}", waitResult);
}
return EXCEPTION_CONTINUE_SEARCH;
}
LONG WINAPI structured_exception_handler(EXCEPTION_POINTERS* ex)
{
return exception_handler(ex);
}
LONG WINAPI vectored_exception_handler(EXCEPTION_POINTERS* ex)
{
if (ex->ExceptionRecord->ExceptionCode == 0x12345678)
{
@ -143,50 +241,7 @@ LONG exception_handler(EXCEPTION_POINTERS* ex)
return EXCEPTION_CONTINUE_SEARCH;
}
// block any other exceptions hitting the veh while the messagebox is open
const auto lock = std::lock_guard(g_exception_handler_mutex);
exception_info exinfo{};
exinfo.pExceptionPointers = ex;
exinfo.ExceptionPointers = *ex;
exinfo.ContextRecord = *ex->ContextRecord;
exinfo.ExceptionRecord = ex->ExceptionRecord ? *ex->ExceptionRecord : EXCEPTION_RECORD{};
const auto time_now = std::chrono::system_clock::now();
auto lifetime = std::chrono::duration_cast<std::chrono::seconds>(
time_now.time_since_epoch()).count()
- std::chrono::duration_cast<std::chrono::seconds>(
g_time_start.time_since_epoch()).count();
exinfo.nLifetime = lifetime;
DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), g_crashhandler_process, &exinfo.hThreadHandle, 0, TRUE, DUPLICATE_SAME_ACCESS);
std::wstring stackTrace;
if (void* fn; const auto err = static_cast<DWORD>(g_clr->get_function_pointer(
L"Dalamud.EntryPoint, Dalamud",
L"VehCallback",
L"Dalamud.EntryPoint+VehDelegate, Dalamud",
nullptr, nullptr, &fn)))
{
stackTrace = std::format(L"Failed to read stack trace: 0x{:08x}", err);
}
else
{
stackTrace = static_cast<wchar_t*(*)()>(fn)();
// Don't free it, as the program's going to be quit anyway
}
exinfo.dwStackTraceLength = static_cast<DWORD>(stackTrace.size());
exinfo.dwTroubleshootingPackDataLength = static_cast<DWORD>(g_startInfo.TroubleshootingPackData.size());
if (DWORD written; !WriteFile(g_crashhandler_pipe_write, &exinfo, static_cast<DWORD>(sizeof exinfo), &written, nullptr) || sizeof exinfo != written)
return EXCEPTION_CONTINUE_SEARCH;
if (DWORD written; !WriteFile(g_crashhandler_pipe_write, &stackTrace[0], static_cast<DWORD>(std::span(stackTrace).size_bytes()), &written, nullptr) || std::span(stackTrace).size_bytes() != written)
return EXCEPTION_CONTINUE_SEARCH;
if (DWORD written; !WriteFile(g_crashhandler_pipe_write, &g_startInfo.TroubleshootingPackData[0], static_cast<DWORD>(std::span(g_startInfo.TroubleshootingPackData).size_bytes()), &written, nullptr) || std::span(g_startInfo.TroubleshootingPackData).size_bytes() != written)
return EXCEPTION_CONTINUE_SEARCH;
SuspendThread(GetCurrentThread());
return EXCEPTION_CONTINUE_SEARCH;
return exception_handler(ex);
}
bool veh::add_handler(bool doFullDump, const std::string& workingDirectory)
@ -194,8 +249,15 @@ bool veh::add_handler(bool doFullDump, const std::string& workingDirectory)
if (g_veh_handle)
return false;
g_veh_handle = AddVectoredExceptionHandler(1, exception_handler);
SetUnhandledExceptionFilter(nullptr);
g_veh_handle = AddVectoredExceptionHandler(TRUE, vectored_exception_handler);
g_HookSetUnhandledExceptionFilter.emplace("kernel32.dll!SetUnhandledExceptionFilter (lpTopLevelExceptionFilter)", "kernel32.dll", "SetUnhandledExceptionFilter", 0);
g_HookSetUnhandledExceptionFilter->set_detour([](LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter) -> LPTOP_LEVEL_EXCEPTION_FILTER
{
logging::I("Overwriting UnhandledExceptionFilter from {} to {}", reinterpret_cast<ULONG_PTR>(lpTopLevelExceptionFilter), reinterpret_cast<ULONG_PTR>(structured_exception_handler));
return g_HookSetUnhandledExceptionFilter->call_original(structured_exception_handler);
});
SetUnhandledExceptionFilter(structured_exception_handler);
g_veh_do_full_dump = doFullDump;
g_time_start = std::chrono::system_clock::now();
@ -308,6 +370,12 @@ bool veh::add_handler(bool doFullDump, const std::string& workingDirectory)
return false;
}
if (!(g_crashhandler_event = CreateEventW(NULL, FALSE, FALSE, NULL)))
{
logging::W("Failed to create crash synchronization event: CreateEventW error 0x{:x}", GetLastError());
return false;
}
CloseHandle(pi.hThread);
g_crashhandler_process = pi.hProcess;
@ -321,6 +389,8 @@ bool veh::remove_handler()
if (g_veh_handle && RemoveVectoredExceptionHandler(g_veh_handle) != 0)
{
g_veh_handle = nullptr;
g_HookSetUnhandledExceptionFilter.reset();
SetUnhandledExceptionFilter(nullptr);
return true;
}
return false;

View file

@ -5,9 +5,8 @@
#include "DalamudStartInfo.h"
#include "hooks.h"
#include "logging.h"
#include "ntdll.h"
#include "utils.h"
#include <iphlpapi.h>
#include <icmpapi.h>
template<typename T>
static std::span<T> assume_nonempty_span(std::span<T> t, const char* descr) {
@ -513,50 +512,6 @@ void xivfixes::backup_userdata_save(bool bApply) {
}
}
void xivfixes::clr_failfast_hijack(bool bApply)
{
static const char* LogTag = "[xivfixes:clr_failfast_hijack]";
static std::optional<hooks::import_hook<decltype(RaiseFailFastException)>> s_HookClrFatalError;
static std::optional<hooks::import_hook<decltype(SetUnhandledExceptionFilter)>> s_HookSetUnhandledExceptionFilter;
if (bApply)
{
if (!g_startInfo.BootEnabledGameFixes.contains("clr_failfast_hijack")) {
logging::I("{} Turned off via environment variable.", LogTag);
return;
}
s_HookClrFatalError.emplace("kernel32.dll!RaiseFailFastException (import, backup_userdata_save)", "kernel32.dll", "RaiseFailFastException", 0);
s_HookSetUnhandledExceptionFilter.emplace("kernel32.dll!SetUnhandledExceptionFilter (lpTopLevelExceptionFilter)", "kernel32.dll", "SetUnhandledExceptionFilter", 0);
s_HookClrFatalError->set_detour([](PEXCEPTION_RECORD pExceptionRecord,
_In_opt_ PCONTEXT pContextRecord,
_In_ DWORD dwFlags)
{
MessageBoxW(nullptr, L"An error in a Dalamud plugin was detected and the game cannot continue.\n\nPlease take a screenshot of this error message and let us know about it.", L"Dalamud", MB_OK | MB_ICONERROR);
return s_HookClrFatalError->call_original(pExceptionRecord, pContextRecord, dwFlags);
});
s_HookSetUnhandledExceptionFilter->set_detour([](LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter) -> LPTOP_LEVEL_EXCEPTION_FILTER
{
logging::I("{} SetUnhandledExceptionFilter", LogTag);
return nullptr;
});
logging::I("{} Enable", LogTag);
}
else
{
if (s_HookClrFatalError) {
logging::I("{} Disable ClrFatalError", LogTag);
s_HookClrFatalError.reset();
s_HookSetUnhandledExceptionFilter.reset();
}
}
}
void xivfixes::prevent_icmphandle_crashes(bool bApply) {
static const char* LogTag = "[xivfixes:prevent_icmphandle_crashes]";
@ -590,6 +545,109 @@ void xivfixes::prevent_icmphandle_crashes(bool bApply) {
}
}
void xivfixes::symbol_load_patches(bool bApply) {
static const char* LogTag = "[xivfixes:symbol_load_patches]";
static std::optional<hooks::import_hook<decltype(SymInitialize)>> s_hookSymInitialize;
static PVOID s_dllNotificationCookie = nullptr;
static const auto RemoveFullPathPdbInfo = [](const utils::loaded_module& mod) {
const auto ddva = mod.data_directory(IMAGE_DIRECTORY_ENTRY_DEBUG).VirtualAddress;
if (!ddva)
return;
const auto& ddir = mod.ref_as<IMAGE_DEBUG_DIRECTORY>(ddva);
if (ddir.Type == IMAGE_DEBUG_TYPE_CODEVIEW) {
// The Visual C++ debug information.
// Ghidra calls it "DotNetPdbInfo".
static constexpr DWORD DotNetPdbInfoSignatureValue = 0x53445352;
struct DotNetPdbInfo {
DWORD Signature; // RSDS
GUID Guid;
DWORD Age;
char PdbPath[1];
};
const auto& pdbref = mod.ref_as<DotNetPdbInfo>(ddir.AddressOfRawData);
if (pdbref.Signature == DotNetPdbInfoSignatureValue) {
const auto pathSpan = std::string_view(pdbref.PdbPath, strlen(pdbref.PdbPath));
const auto pathWide = unicode::convert<std::wstring>(pathSpan);
std::wstring windowsDirectory(GetWindowsDirectoryW(nullptr, 0) + 1, L'\0');
windowsDirectory.resize(
GetWindowsDirectoryW(windowsDirectory.data(), static_cast<UINT>(windowsDirectory.size())));
if (!PathIsRelativeW(pathWide.c_str()) && !PathIsSameRootW(windowsDirectory.c_str(), pathWide.c_str())) {
utils::memory_tenderizer pathOverwrite(&pdbref.PdbPath, pathSpan.size(), PAGE_READWRITE);
auto sep = std::find(pathSpan.rbegin(), pathSpan.rend(), '/');
if (sep == pathSpan.rend())
sep = std::find(pathSpan.rbegin(), pathSpan.rend(), '\\');
if (sep != pathSpan.rend()) {
logging::I(
"{} Stripping pdb path folder: {} to {}",
LogTag,
pathSpan,
&*sep + 1);
memmove(const_cast<char*>(pathSpan.data()), &*sep + 1, sep - pathSpan.rbegin() + 1);
} else {
logging::I("{} Leaving pdb path unchanged: {}", LogTag, pathSpan);
}
} else {
logging::I("{} Leaving pdb path unchanged: {}", LogTag, pathSpan);
}
} else {
logging::I("{} CODEVIEW struct signature mismatch: got {:08X} instead.", LogTag, pdbref.Signature);
}
} else {
logging::I("{} Debug directory: type {} is unsupported.", LogTag, ddir.Type);
}
};
if (bApply) {
if (!g_startInfo.BootEnabledGameFixes.contains("symbol_load_patches")) {
logging::I("{} Turned off via environment variable.", LogTag);
return;
}
for (const auto& mod : utils::loaded_module::all_modules())
RemoveFullPathPdbInfo(mod);
if (!s_dllNotificationCookie) {
const auto res = LdrRegisterDllNotification(
0,
[](ULONG notiReason, const LDR_DLL_NOTIFICATION_DATA* pData, void* /* context */) {
if (notiReason == LDR_DLL_NOTIFICATION_REASON_LOADED)
RemoveFullPathPdbInfo(pData->Loaded.DllBase);
},
nullptr,
&s_dllNotificationCookie);
if (res != STATUS_SUCCESS) {
logging::E("{} LdrRegisterDllNotification failure: 0x{:08X}", LogTag, res);
s_dllNotificationCookie = nullptr;
}
}
s_hookSymInitialize.emplace("dbghelp.dll!SymInitialize (import, symbol_load_patches)", "dbghelp.dll", "SymInitialize", 0);
s_hookSymInitialize->set_detour([](HANDLE hProcess, PCSTR UserSearchPath, BOOL fInvadeProcess) noexcept {
logging::I("{} Suppressed SymInitialize.", LogTag);
SetLastError(ERROR_NOT_SUPPORTED);
return FALSE;
});
logging::I("{} Enable", LogTag);
}
else {
if (s_hookSymInitialize) {
logging::I("{} Disable", LogTag);
s_hookSymInitialize.reset();
}
if (s_dllNotificationCookie) {
(void)LdrUnregisterDllNotification(s_dllNotificationCookie);
s_dllNotificationCookie = nullptr;
}
}
}
void xivfixes::apply_all(bool bApply) {
for (const auto& [taskName, taskFunction] : std::initializer_list<std::pair<const char*, void(*)(bool)>>
{
@ -598,8 +656,8 @@ void xivfixes::apply_all(bool bApply) {
{ "disable_game_openprocess_access_check", &disable_game_openprocess_access_check },
{ "redirect_openprocess", &redirect_openprocess },
{ "backup_userdata_save", &backup_userdata_save },
{ "clr_failfast_hijack", &clr_failfast_hijack },
{ "prevent_icmphandle_crashes", &prevent_icmphandle_crashes }
{ "prevent_icmphandle_crashes", &prevent_icmphandle_crashes },
{ "symbol_load_patches", &symbol_load_patches },
}
) {
try {

View file

@ -6,8 +6,8 @@ namespace xivfixes {
void disable_game_openprocess_access_check(bool bApply);
void redirect_openprocess(bool bApply);
void backup_userdata_save(bool bApply);
void clr_failfast_hijack(bool bApply);
void prevent_icmphandle_crashes(bool bApply);
void symbol_load_patches(bool bApply);
void apply_all(bool bApply);
}