mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-12 18:27:23 +01:00
Changes to Dalamud Boot DLL so that it works in WINE (#1111)
* Changes to Dalamud Boot DLL so that it works in WINE * Make asm clearer
This commit is contained in:
parent
386e5f245c
commit
0cc28fb39d
7 changed files with 235 additions and 182 deletions
|
|
@ -32,6 +32,9 @@
|
||||||
<IntDir>obj\$(Configuration)\</IntDir>
|
<IntDir>obj\$(Configuration)\</IntDir>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||||
|
<ImportGroup Label="ExtensionSettings">
|
||||||
|
<Import Project="$(VCTargetsPath)\BuildCustomizations\masm.props" />
|
||||||
|
</ImportGroup>
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||||
<UseDebugLibraries>true</UseDebugLibraries>
|
<UseDebugLibraries>true</UseDebugLibraries>
|
||||||
<LibraryPath>$(SolutionDir)bin\lib\$(Configuration)\libMinHook\;$(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64)</LibraryPath>
|
<LibraryPath>$(SolutionDir)bin\lib\$(Configuration)\libMinHook\;$(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64)</LibraryPath>
|
||||||
|
|
@ -70,6 +73,7 @@
|
||||||
<Link>
|
<Link>
|
||||||
<EnableCOMDATFolding>false</EnableCOMDATFolding>
|
<EnableCOMDATFolding>false</EnableCOMDATFolding>
|
||||||
<OptimizeReferences>false</OptimizeReferences>
|
<OptimizeReferences>false</OptimizeReferences>
|
||||||
|
<ModuleDefinitionFile Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">module.def</ModuleDefinitionFile>
|
||||||
</Link>
|
</Link>
|
||||||
</ItemDefinitionGroup>
|
</ItemDefinitionGroup>
|
||||||
<ItemDefinitionGroup Condition="'$(Configuration)'=='Release'">
|
<ItemDefinitionGroup Condition="'$(Configuration)'=='Release'">
|
||||||
|
|
@ -83,9 +87,13 @@
|
||||||
<Link>
|
<Link>
|
||||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||||
<OptimizeReferences>true</OptimizeReferences>
|
<OptimizeReferences>true</OptimizeReferences>
|
||||||
|
<ModuleDefinitionFile Condition="'$(Configuration)|$(Platform)'=='Release|x64'">module.def</ModuleDefinitionFile>
|
||||||
</Link>
|
</Link>
|
||||||
</ItemDefinitionGroup>
|
</ItemDefinitionGroup>
|
||||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||||
|
<ImportGroup Label="ExtensionTargets">
|
||||||
|
<Import Project="$(VCTargetsPath)\BuildCustomizations\masm.targets" />
|
||||||
|
</ImportGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Content Include="..\lib\CoreCLR\nethost\nethost.dll">
|
<Content Include="..\lib\CoreCLR\nethost\nethost.dll">
|
||||||
<Link>nethost.dll</Link>
|
<Link>nethost.dll</Link>
|
||||||
|
|
@ -181,6 +189,12 @@
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Image Include="dalamud.ico" />
|
<Image Include="dalamud.ico" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<MASM Include="rewrite_entrypoint_thunks.asm" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<None Include="module.def" />
|
||||||
|
</ItemGroup>
|
||||||
<Target Name="RemoveExtraFiles" AfterTargets="PostBuildEvent">
|
<Target Name="RemoveExtraFiles" AfterTargets="PostBuildEvent">
|
||||||
<Delete Files="$(OutDir)$(TargetName).lib" />
|
<Delete Files="$(OutDir)$(TargetName).lib" />
|
||||||
<Delete Files="$(OutDir)$(TargetName).exp" />
|
<Delete Files="$(OutDir)$(TargetName).exp" />
|
||||||
|
|
|
||||||
|
|
@ -147,4 +147,14 @@
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Image Include="dalamud.ico" />
|
<Image Include="dalamud.ico" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
<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>
|
</Project>
|
||||||
|
|
@ -159,7 +159,7 @@ DWORD WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
DllExport DWORD WINAPI Initialize(LPVOID lpParam) {
|
extern "C" DWORD WINAPI Initialize(LPVOID lpParam) {
|
||||||
return InitializeImpl(lpParam, CreateEvent(nullptr, TRUE, FALSE, nullptr));
|
return InitializeImpl(lpParam, CreateEvent(nullptr, TRUE, FALSE, nullptr));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
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
|
||||||
|
|
@ -61,9 +61,6 @@
|
||||||
|
|
||||||
#include "unicode.h"
|
#include "unicode.h"
|
||||||
|
|
||||||
// Commonly used macros
|
|
||||||
#define DllExport extern "C" __declspec(dllexport)
|
|
||||||
|
|
||||||
// Global variables
|
// Global variables
|
||||||
extern HMODULE g_hModule;
|
extern HMODULE g_hModule;
|
||||||
extern HINSTANCE g_hGameInstance;
|
extern HINSTANCE g_hGameInstance;
|
||||||
|
|
|
||||||
|
|
@ -5,111 +5,87 @@
|
||||||
DWORD WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue);
|
DWORD WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue);
|
||||||
|
|
||||||
struct RewrittenEntryPointParameters {
|
struct RewrittenEntryPointParameters {
|
||||||
void* pAllocation;
|
|
||||||
char* pEntrypoint;
|
char* pEntrypoint;
|
||||||
char* pEntrypointBytes;
|
|
||||||
size_t entrypointLength;
|
size_t entrypointLength;
|
||||||
char* pLoadInfo;
|
|
||||||
HANDLE hMainThread;
|
|
||||||
HANDLE hMainThreadContinue;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#pragma pack(push, 1)
|
namespace thunks {
|
||||||
struct EntryPointThunkTemplate {
|
constexpr uint64_t Terminator = 0xCCCCCCCCCCCCCCCCu;
|
||||||
struct DUMMYSTRUCTNAME {
|
constexpr uint64_t Placeholder = 0x0606060606060606u;
|
||||||
struct {
|
|
||||||
const uint8_t op_mov_rdi[2]{ 0x48, 0xbf };
|
|
||||||
void* ptr = nullptr;
|
|
||||||
} fn;
|
|
||||||
|
|
||||||
const uint8_t op_call_rdi[2]{ 0xff, 0xd7 };
|
extern "C" void EntryPointReplacement();
|
||||||
} CallTrampoline;
|
extern "C" void RewrittenEntryPoint_Standalone();
|
||||||
};
|
|
||||||
|
|
||||||
struct TrampolineTemplate {
|
void* resolve_thunk_address(void (*pfn)()) {
|
||||||
const struct {
|
const auto ptr = reinterpret_cast<uint8_t*>(pfn);
|
||||||
const uint8_t op_sub_rsp_imm[3]{ 0x48, 0x81, 0xec };
|
if (*ptr == 0xe9)
|
||||||
const uint32_t length = 0x80;
|
return ptr + 5 + *reinterpret_cast<int32_t*>(ptr + 1);
|
||||||
} stack_alloc;
|
return ptr;
|
||||||
|
}
|
||||||
|
|
||||||
struct DUMMYSTRUCTNAME {
|
size_t get_thunk_length(void (*pfn)()) {
|
||||||
struct {
|
size_t length = 0;
|
||||||
const uint8_t op_mov_rcx_imm[2]{ 0x48, 0xb9 };
|
for (auto ptr = reinterpret_cast<char*>(resolve_thunk_address(pfn)); *reinterpret_cast<uint64_t*>(ptr) != Terminator; ptr++)
|
||||||
void* val = nullptr;
|
length++;
|
||||||
} lpLibFileName;
|
return length;
|
||||||
|
}
|
||||||
|
|
||||||
struct {
|
template<typename T>
|
||||||
const uint8_t op_mov_rdi_imm[2]{ 0x48, 0xbf };
|
void* fill_placeholders(void* pfn, const T& value) {
|
||||||
decltype(&LoadLibraryW) ptr = nullptr;
|
auto ptr = static_cast<char*>(pfn);
|
||||||
} fn;
|
|
||||||
|
|
||||||
const uint8_t op_call_rdi[2]{ 0xff, 0xd7 };
|
while (*reinterpret_cast<uint64_t*>(ptr) != Placeholder)
|
||||||
} CallLoadLibrary_nethost;
|
ptr++;
|
||||||
|
|
||||||
struct DUMMYSTRUCTNAME {
|
*reinterpret_cast<uint64_t*>(ptr) = 0;
|
||||||
struct {
|
*reinterpret_cast<T*>(ptr) = value;
|
||||||
const uint8_t op_mov_rcx_imm[2]{ 0x48, 0xb9 };
|
return ptr + sizeof(value);
|
||||||
void* val = nullptr;
|
}
|
||||||
} lpLibFileName;
|
|
||||||
|
|
||||||
struct {
|
template<typename T, typename...TArgs>
|
||||||
const uint8_t op_mov_rdi_imm[2]{ 0x48, 0xbf };
|
void* fill_placeholders(void* ptr, const T& value, TArgs&&...more_values) {
|
||||||
decltype(&LoadLibraryW) ptr = nullptr;
|
return fill_placeholders(fill_placeholders(ptr, value), std::forward<TArgs>(more_values)...);
|
||||||
} fn;
|
}
|
||||||
|
|
||||||
const uint8_t op_call_rdi[2]{ 0xff, 0xd7 };
|
std::vector<char> create_entrypointreplacement() {
|
||||||
} CallLoadLibrary_DalamudBoot;
|
std::vector<char> buf(get_thunk_length(&EntryPointReplacement));
|
||||||
|
memcpy(buf.data(), resolve_thunk_address(&EntryPointReplacement), buf.size());
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
struct {
|
std::vector<char> create_standalone_rewrittenentrypoint(const std::filesystem::path& dalamud_path) {
|
||||||
const uint8_t hModule_op_mov_rcx_rax[3]{ 0x48, 0x89, 0xc1 };
|
const auto nethost_path = std::filesystem::path(dalamud_path).replace_filename(L"nethost.dll");
|
||||||
|
|
||||||
struct {
|
// These are null terminated, since pointers are returned from .c_str()
|
||||||
const uint8_t op_mov_rdx_imm[2]{ 0x48, 0xba };
|
const auto dalamud_path_wview = std::wstring_view(dalamud_path.c_str());
|
||||||
void* val = nullptr;
|
const auto nethost_path_wview = std::wstring_view(nethost_path.c_str());
|
||||||
} lpProcName;
|
|
||||||
|
|
||||||
struct {
|
// +2 is for null terminator
|
||||||
const uint8_t op_mov_rdi_imm[2]{ 0x48, 0xbf };
|
const auto dalamud_path_view = std::span(reinterpret_cast<const char*>(dalamud_path_wview.data()), dalamud_path_wview.size() * 2 + 2);
|
||||||
decltype(&GetProcAddress) ptr = nullptr;
|
const auto nethost_path_view = std::span(reinterpret_cast<const char*>(nethost_path_wview.data()), nethost_path_wview.size() * 2 + 2);
|
||||||
} fn;
|
|
||||||
|
|
||||||
const uint8_t op_call_rdi[2]{ 0xff, 0xd7 };
|
std::vector<char> buffer;
|
||||||
} CallGetProcAddress;
|
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);
|
||||||
|
|
||||||
struct {
|
// &::GetProcAddress will return Dalamud.dll's import table entry.
|
||||||
const uint8_t op_add_rsp_imm[3]{ 0x48, 0x81, 0xc4 };
|
// GetProcAddress(..., "GetProcAddress") returns the address inside kernel32.dll.
|
||||||
const uint32_t length = 0x80;
|
const auto kernel32 = GetModuleHandleA("kernel32.dll");
|
||||||
} stack_release;
|
|
||||||
|
|
||||||
struct DUMMYSTRUCTNAME2 {
|
thunks::fill_placeholders(buffer.data(),
|
||||||
// rdi := returned value from GetProcAddress
|
/* pfnLoadLibraryW = */ GetProcAddress(kernel32, "LoadLibraryW"),
|
||||||
const uint8_t op_mov_rdi_rax[3]{ 0x48, 0x89, 0xc7 };
|
/* pfnGetProcAddress = */ GetProcAddress(kernel32, "GetProcAddress"),
|
||||||
// rax := return address
|
/* pRewrittenEntryPointParameters = */ Placeholder,
|
||||||
const uint8_t op_pop_rax[1]{ 0x58 };
|
/* nNethostOffset = */ 0,
|
||||||
|
/* nDalamudOffset = */ nethost_path_view.size_bytes()
|
||||||
// rax := rax - sizeof thunk (last instruction must be call)
|
);
|
||||||
struct {
|
buffer.insert(buffer.end(), nethost_path_view.begin(), nethost_path_view.end());
|
||||||
const uint8_t op_sub_rax_imm4[2]{ 0x48, 0x2d };
|
buffer.insert(buffer.end(), dalamud_path_view.begin(), dalamud_path_view.end());
|
||||||
const uint32_t displacement = static_cast<uint32_t>(sizeof EntryPointThunkTemplate);
|
return buffer;
|
||||||
} 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)
|
|
||||||
|
|
||||||
void read_process_memory_or_throw(HANDLE hProcess, void* pAddress, void* data, size_t len) {
|
void read_process_memory_or_throw(HANDLE hProcess, void* pAddress, void* data, size_t len) {
|
||||||
SIZE_T read = 0;
|
SIZE_T read = 0;
|
||||||
|
|
@ -171,9 +147,16 @@ void* get_mapped_image_base_address(HANDLE hProcess, const std::filesystem::path
|
||||||
if (!exe)
|
if (!exe)
|
||||||
throw std::runtime_error("Game executable is corrupt (Truncated section header).");
|
throw std::runtime_error("Game executable is corrupt (Truncated section header).");
|
||||||
|
|
||||||
|
SYSTEM_INFO sysinfo;
|
||||||
|
GetSystemInfo(&sysinfo);
|
||||||
|
|
||||||
for (MEMORY_BASIC_INFORMATION mbi{};
|
for (MEMORY_BASIC_INFORMATION mbi{};
|
||||||
VirtualQueryEx(hProcess, mbi.BaseAddress, &mbi, sizeof mbi);
|
VirtualQueryEx(hProcess, mbi.BaseAddress, &mbi, sizeof mbi);
|
||||||
mbi.BaseAddress = static_cast<char*>(mbi.BaseAddress) + mbi.RegionSize) {
|
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)
|
if (!(mbi.State & MEM_COMMIT) || mbi.Type != MEM_IMAGE)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
|
@ -241,18 +224,6 @@ void* get_mapped_image_base_address(HANDLE hProcess, const std::filesystem::path
|
||||||
throw std::runtime_error("corresponding base address not found");
|
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.
|
/// @brief Rewrite target process' entry point so that this DLL can be loaded and executed first.
|
||||||
/// @param hProcess Process handle.
|
/// @param hProcess Process handle.
|
||||||
/// @param pcwzPath Path to target process.
|
/// @param pcwzPath Path to target process.
|
||||||
|
|
@ -263,9 +234,9 @@ std::wstring to_utf16(const std::string& str, UINT codePage = CP_UTF8, bool erro
|
||||||
/// Instead, we have to enumerate through all the files mapped into target process' virtual address space and find the base address
|
/// 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.
|
/// of memory region corresponding to the path given.
|
||||||
///
|
///
|
||||||
DllExport DWORD WINAPI RewriteRemoteEntryPointW(HANDLE hProcess, const wchar_t* pcwzPath, const wchar_t* pcwzLoadInfo) {
|
extern "C" DWORD WINAPI RewriteRemoteEntryPointW(HANDLE hProcess, const wchar_t* pcwzPath, const wchar_t* pcwzLoadInfo) {
|
||||||
try {
|
try {
|
||||||
const auto base_address = reinterpret_cast<char*>(get_mapped_image_base_address(hProcess, pcwzPath));
|
const auto base_address = static_cast<char*>(get_mapped_image_base_address(hProcess, pcwzPath));
|
||||||
|
|
||||||
IMAGE_DOS_HEADER dos_header{};
|
IMAGE_DOS_HEADER dos_header{};
|
||||||
union {
|
union {
|
||||||
|
|
@ -279,60 +250,35 @@ DllExport DWORD WINAPI RewriteRemoteEntryPointW(HANDLE hProcess, const wchar_t*
|
||||||
? nt_header32.OptionalHeader.AddressOfEntryPoint
|
? nt_header32.OptionalHeader.AddressOfEntryPoint
|
||||||
: nt_header64.OptionalHeader.AddressOfEntryPoint);
|
: nt_header64.OptionalHeader.AddressOfEntryPoint);
|
||||||
|
|
||||||
auto path = get_path_from_local_module(g_hModule).wstring();
|
auto standalone_rewrittenentrypoint = thunks::create_standalone_rewrittenentrypoint(get_path_from_local_module(g_hModule));
|
||||||
path.resize(path.size() + 1); // ensure null termination
|
auto entrypoint_replacement = thunks::create_entrypointreplacement();
|
||||||
auto path_bytes = std::span(reinterpret_cast<const char*>(&path[0]), std::span(path).size_bytes());
|
|
||||||
|
|
||||||
auto nethost_path = (get_path_from_local_module(g_hModule).parent_path() / L"nethost.dll").wstring();
|
auto load_info = unicode::convert<std::string>(pcwzLoadInfo);
|
||||||
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());
|
|
||||||
|
|
||||||
auto load_info = from_utf16(pcwzLoadInfo);
|
|
||||||
load_info.resize(load_info.size() + 1); //ensure null termination
|
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(RewrittenEntryPointParameters) + entrypoint_replacement.size() + load_info.size() + standalone_rewrittenentrypoint.size());
|
||||||
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);
|
|
||||||
|
|
||||||
// Allocate buffer in remote process, which will be used to fill addresses in the local buffer.
|
// 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));
|
const auto remote_buffer = static_cast<char*>(VirtualAllocEx(hProcess, nullptr, buffer.size(), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE));
|
||||||
|
|
||||||
// Fill the values to be used in RewrittenEntryPoint
|
auto& params = *reinterpret_cast<RewrittenEntryPointParameters*>(buffer.data());
|
||||||
trampoline.parameters = {
|
params.entrypointLength = entrypoint_replacement.size();
|
||||||
.pAllocation = remote_buffer,
|
params.pEntrypoint = entrypoint;
|
||||||
.pEntrypoint = entrypoint,
|
|
||||||
.pEntrypointBytes = remote_buffer + offsetof(TrampolineTemplate, buf_EntryPointBackup),
|
|
||||||
.entrypointLength = sizeof trampoline.buf_EntryPointBackup,
|
|
||||||
.pLoadInfo = remote_buffer + (&load_info_buffer[0] - &buffer[0]),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Fill the addresses referred in machine code.
|
// Backup original entry point.
|
||||||
trampoline.CallLoadLibrary_nethost.lpLibFileName.val = remote_buffer + (&nethost_path_buffer[0] - &buffer[0]);
|
read_process_memory_or_throw(hProcess, entrypoint, &buffer[sizeof params], entrypoint_replacement.size());
|
||||||
trampoline.CallLoadLibrary_nethost.fn.ptr = LoadLibraryW;
|
|
||||||
trampoline.CallLoadLibrary_DalamudBoot.lpLibFileName.val = remote_buffer + (&dalamud_path_buffer[0] - &buffer[0]);
|
memcpy(&buffer[sizeof params + entrypoint_replacement.size()], load_info.data(), load_info.size());
|
||||||
trampoline.CallLoadLibrary_DalamudBoot.fn.ptr = LoadLibraryW;
|
|
||||||
trampoline.CallGetProcAddress.lpProcName.val = remote_buffer + offsetof(TrampolineTemplate, buf_CallGetProcAddress_lpProcName);
|
thunks::fill_placeholders(standalone_rewrittenentrypoint.data(), remote_buffer);
|
||||||
trampoline.CallGetProcAddress.fn.ptr = GetProcAddress;
|
memcpy(&buffer[sizeof params + entrypoint_replacement.size() + load_info.size()], standalone_rewrittenentrypoint.data(), standalone_rewrittenentrypoint.size());
|
||||||
trampoline.CallInjectEntryPoint.param.val = remote_buffer + offsetof(TrampolineTemplate, parameters);
|
|
||||||
|
|
||||||
// Write the local buffer into the buffer in remote process.
|
// Write the local buffer into the buffer in remote process.
|
||||||
write_process_memory_or_throw(hProcess, remote_buffer, buffer.data(), 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.
|
thunks::fill_placeholders(entrypoint_replacement.data(), remote_buffer + sizeof params + entrypoint_replacement.size() + load_info.size());
|
||||||
EntryPointThunkTemplate thunk{};
|
// Overwrite remote process' entry point with a thunk that will load our DLLs and call our trampoline function.
|
||||||
thunk.CallTrampoline.fn.ptr = remote_buffer;
|
write_process_memory_or_throw(hProcess, entrypoint, entrypoint_replacement.data(), entrypoint_replacement.size());
|
||||||
write_process_memory_or_throw(hProcess, entrypoint, thunk);
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
} catch (const std::exception& e) {
|
} catch (const std::exception& e) {
|
||||||
|
|
@ -341,44 +287,43 @@ DllExport DWORD WINAPI RewriteRemoteEntryPointW(HANDLE hProcess, const wchar_t*
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @deprecated
|
static void AbortRewrittenEntryPoint(DWORD err, const std::wstring& clue) {
|
||||||
DllExport DWORD WINAPI RewriteRemoteEntryPoint(HANDLE hProcess, const wchar_t* pcwzPath, const char* pcszLoadInfo) {
|
wchar_t* pwszMsg = nullptr;
|
||||||
return RewriteRemoteEntryPointW(hProcess, pcwzPath, to_utf16(pcszLoadInfo).c_str());
|
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 (MessageBoxW(nullptr, std::format(
|
||||||
|
L"Failed to load Dalamud. Load game without Dalamud(yes) or abort(no)?\n\nError: 0x{:08X} {}\n\n{}",
|
||||||
|
err, pwszMsg ? pwszMsg : L"", clue).c_str(),
|
||||||
|
L"Dalamud.Boot", MB_OK | MB_YESNO) == IDNO)
|
||||||
|
ExitProcess(-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @brief Entry point function "called" instead of game's original main entry point.
|
/// @brief Entry point function "called" instead of game's original main entry point.
|
||||||
/// @param params Parameters set up from RewriteRemoteEntryPoint.
|
/// @param params Parameters set up from RewriteRemoteEntryPoint.
|
||||||
DllExport void WINAPI RewrittenEntryPoint(RewrittenEntryPointParameters& params) {
|
extern "C" void WINAPI RewrittenEntryPoint_AdjustedStack(RewrittenEntryPointParameters & params) {
|
||||||
params.hMainThreadContinue = CreateEventW(nullptr, true, false, nullptr);
|
const auto pOriginalEntryPointBytes = reinterpret_cast<char*>(¶ms) + sizeof(params);
|
||||||
if (!params.hMainThreadContinue)
|
const auto pLoadInfo = pOriginalEntryPointBytes + params.entrypointLength;
|
||||||
ExitProcess(-1);
|
|
||||||
|
|
||||||
// Do whatever the work in a separate thread to minimize the stack usage at this context,
|
// Restore original entry point.
|
||||||
// as this function really should have been a naked procedure but __declspec(naked) isn't supported in x64 version of msvc.
|
// Use WriteProcessMemory instead of memcpy to avoid having to fiddle with VirtualProtect.
|
||||||
params.hMainThread = CreateThread(nullptr, 0, [](void* p) -> DWORD {
|
if (SIZE_T written; !WriteProcessMemory(GetCurrentProcess(), params.pEntrypoint, pOriginalEntryPointBytes, params.entrypointLength, &written))
|
||||||
try {
|
return AbortRewrittenEntryPoint(GetLastError(), L"WriteProcessMemory(entrypoint restoration)");
|
||||||
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);
|
|
||||||
|
|
||||||
// Make a copy of load info, as the whole params will be freed after this code block.
|
const auto hMainThreadContinue = CreateEventW(nullptr, true, false, nullptr);
|
||||||
loadInfo = params.pLoadInfo;
|
if (!hMainThreadContinue)
|
||||||
}
|
return AbortRewrittenEntryPoint(GetLastError(), L"CreateEventW");
|
||||||
|
|
||||||
InitializeImpl(&loadInfo[0], params.hMainThreadContinue);
|
if (const auto result = InitializeImpl(pLoadInfo, hMainThreadContinue))
|
||||||
return 0;
|
return AbortRewrittenEntryPoint(result, L"InitializeImpl");
|
||||||
} 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);
|
|
||||||
}
|
|
||||||
}, ¶ms, 0, nullptr);
|
|
||||||
if (!params.hMainThread)
|
|
||||||
ExitProcess(-1);
|
|
||||||
|
|
||||||
CloseHandle(params.hMainThread);
|
WaitForSingleObject(hMainThreadContinue, INFINITE);
|
||||||
WaitForSingleObject(params.hMainThreadContinue, INFINITE);
|
VirtualFree(¶ms, 0, MEM_RELEASE);
|
||||||
VirtualFree(params.pAllocation, 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
|
||||||
Loading…
Add table
Add a link
Reference in a new issue