mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-30 12:23:39 +01:00
Merge branch 'master' into new_im_hooks
This commit is contained in:
commit
50f74c55a7
460 changed files with 40079 additions and 12893 deletions
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ struct exception_info
|
|||
CONTEXT ContextRecord;
|
||||
uint64_t nLifetime;
|
||||
HANDLE hThreadHandle;
|
||||
HANDLE hEventHandle;
|
||||
DWORD dwStackTraceLength;
|
||||
DWORD dwTroubleshootingPackDataLength;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
#pragma once
|
||||
|
||||
#include <limits>
|
||||
#include <map>
|
||||
|
||||
#include "utils.h"
|
||||
|
|
|
|||
5
Dalamud.Boot/module.def
Normal file
5
Dalamud.Boot/module.def
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
LIBRARY Dalamud.Boot
|
||||
EXPORTS
|
||||
Initialize @1
|
||||
RewriteRemoteEntryPointW @2
|
||||
RewrittenEntryPoint @3
|
||||
15
Dalamud.Boot/ntdll.cpp
Normal file
15
Dalamud.Boot/ntdll.cpp
Normal 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
33
Dalamud.Boot/ntdll.h
Normal 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);
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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*>(¶ms) + 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);
|
||||
}
|
||||
}, ¶ms, 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(¶ms, 0, MEM_RELEASE);
|
||||
}
|
||||
|
|
|
|||
82
Dalamud.Boot/rewrite_entrypoint_thunks.asm
Normal file
82
Dalamud.Boot/rewrite_entrypoint_thunks.asm
Normal 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
|
||||
|
|
@ -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, ®ion, sizeof region)) {
|
||||
if (!VirtualQueryEx(hProcess, 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),
|
||||
|
|
@ -423,7 +429,7 @@ utils::memory_tenderizer::memory_tenderizer(const void* pAddress, size_t length,
|
|||
GetLastError()));
|
||||
}
|
||||
|
||||
if (!VirtualProtect(region.BaseAddress, region.RegionSize, dwNewProtect, ®ion.Protect)) {
|
||||
if (!VirtualProtectEx(hProcess, 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),
|
||||
|
|
@ -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, ®ion.Protect)) {
|
||||
if (!VirtualProtectEx(hProcess, region.BaseAddress, region.RegionSize, region.Protect, ®ion.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, ®ion.Protect)) {
|
||||
if (!VirtualProtectEx(m_process, region.BaseAddress, region.RegionSize, region.Protect, ®ion.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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue