mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-12 18:27:23 +01:00
Make RewriteRemoteEntryPointW report IErrorInfo, VirtualProtectEx before WriteProcessMemory
This commit is contained in:
parent
0cc28fb39d
commit
7e78b6293b
10 changed files with 254 additions and 123 deletions
|
|
@ -199,4 +199,4 @@
|
||||||
<Delete Files="$(OutDir)$(TargetName).lib" />
|
<Delete Files="$(OutDir)$(TargetName).lib" />
|
||||||
<Delete Files="$(OutDir)$(TargetName).exp" />
|
<Delete Files="$(OutDir)$(TargetName).exp" />
|
||||||
</Target>
|
</Target>
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
HMODULE g_hModule;
|
HMODULE g_hModule;
|
||||||
HINSTANCE g_hGameInstance = GetModuleHandleW(nullptr);
|
HINSTANCE g_hGameInstance = GetModuleHandleW(nullptr);
|
||||||
|
|
||||||
DWORD WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue) {
|
HRESULT WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue) {
|
||||||
g_startInfo.from_envvars();
|
g_startInfo.from_envvars();
|
||||||
|
|
||||||
std::string jsonParseError;
|
std::string jsonParseError;
|
||||||
|
|
@ -114,7 +114,7 @@ DWORD WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue) {
|
||||||
logging::I("Calling InitializeClrAndGetEntryPoint");
|
logging::I("Calling InitializeClrAndGetEntryPoint");
|
||||||
|
|
||||||
void* entrypoint_vfn;
|
void* entrypoint_vfn;
|
||||||
int result = InitializeClrAndGetEntryPoint(
|
const auto result = InitializeClrAndGetEntryPoint(
|
||||||
g_hModule,
|
g_hModule,
|
||||||
g_startInfo.BootEnableEtw,
|
g_startInfo.BootEnableEtw,
|
||||||
runtimeconfig_path,
|
runtimeconfig_path,
|
||||||
|
|
@ -124,7 +124,7 @@ DWORD WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue) {
|
||||||
L"Dalamud.EntryPoint+InitDelegate, Dalamud",
|
L"Dalamud.EntryPoint+InitDelegate, Dalamud",
|
||||||
&entrypoint_vfn);
|
&entrypoint_vfn);
|
||||||
|
|
||||||
if (result != 0)
|
if (FAILED(result))
|
||||||
return result;
|
return result;
|
||||||
|
|
||||||
using custom_component_entry_point_fn = void (CORECLR_DELEGATE_CALLTYPE*)(LPVOID, HANDLE);
|
using custom_component_entry_point_fn = void (CORECLR_DELEGATE_CALLTYPE*)(LPVOID, HANDLE);
|
||||||
|
|
@ -156,7 +156,7 @@ DWORD WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue) {
|
||||||
entrypoint_fn(lpParam, hMainThreadContinue);
|
entrypoint_fn(lpParam, hMainThreadContinue);
|
||||||
logging::I("Done!");
|
logging::I("Done!");
|
||||||
|
|
||||||
return 0;
|
return S_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" DWORD WINAPI Initialize(LPVOID lpParam) {
|
extern "C" DWORD WINAPI Initialize(LPVOID lpParam) {
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,9 @@
|
||||||
// MSVC Compiler Intrinsic
|
// MSVC Compiler Intrinsic
|
||||||
#include <intrin.h>
|
#include <intrin.h>
|
||||||
|
|
||||||
|
// COM
|
||||||
|
#include <comdef.h>
|
||||||
|
|
||||||
// C++ Standard Libraries
|
// C++ Standard Libraries
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
#include "pch.h"
|
#include "pch.h"
|
||||||
|
|
||||||
#include "logging.h"
|
#include "logging.h"
|
||||||
|
#include "utils.h"
|
||||||
|
|
||||||
DWORD WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue);
|
HRESULT WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue);
|
||||||
|
|
||||||
struct RewrittenEntryPointParameters {
|
struct RewrittenEntryPointParameters {
|
||||||
char* pEntrypoint;
|
char* pEntrypoint;
|
||||||
|
|
@ -102,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) {
|
void write_process_memory_or_throw(HANDLE hProcess, void* pAddress, const void* data, size_t len) {
|
||||||
SIZE_T written = 0;
|
SIZE_T written = 0;
|
||||||
|
const utils::memory_tenderizer tenderizer(hProcess, pAddress, len, PAGE_EXECUTE_READWRITE);
|
||||||
if (!WriteProcessMemory(hProcess, pAddress, data, len, &written))
|
if (!WriteProcessMemory(hProcess, pAddress, data, len, &written))
|
||||||
throw std::runtime_error("WriteProcessMemory failure");
|
throw std::runtime_error("WriteProcessMemory failure");
|
||||||
if (written != len)
|
if (written != len)
|
||||||
|
|
@ -227,15 +229,18 @@ void* get_mapped_image_base_address(HANDLE hProcess, const std::filesystem::path
|
||||||
/// @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.
|
||||||
/// @param pcszLoadInfo JSON string to be passed to Initialize.
|
/// @param pcwzLoadInfo JSON string to be passed to Initialize.
|
||||||
/// @return 0 if successful; nonzero if unsuccessful
|
/// @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.
|
/// 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
|
/// 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.
|
||||||
///
|
///
|
||||||
extern "C" 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 {
|
try {
|
||||||
|
last_operation = L"get_mapped_image_base_address";
|
||||||
const auto base_address = static_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{};
|
||||||
|
|
@ -244,21 +249,34 @@ extern "C" DWORD WINAPI RewriteRemoteEntryPointW(HANDLE hProcess, const wchar_t*
|
||||||
IMAGE_NT_HEADERS64 nt_header64{};
|
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);
|
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);
|
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
|
const auto entrypoint = base_address + (nt_header32.OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC
|
||||||
? nt_header32.OptionalHeader.AddressOfEntryPoint
|
? nt_header32.OptionalHeader.AddressOfEntryPoint
|
||||||
: nt_header64.OptionalHeader.AddressOfEntryPoint);
|
: nt_header64.OptionalHeader.AddressOfEntryPoint);
|
||||||
|
|
||||||
auto standalone_rewrittenentrypoint = thunks::create_standalone_rewrittenentrypoint(get_path_from_local_module(g_hModule));
|
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);
|
||||||
|
|
||||||
|
last_operation = L"thunks::create_entrypointreplacement()";
|
||||||
auto entrypoint_replacement = thunks::create_entrypointreplacement();
|
auto entrypoint_replacement = thunks::create_entrypointreplacement();
|
||||||
|
|
||||||
|
last_operation = L"unicode::convert<std::string>(pcwzLoadInfo)";
|
||||||
auto load_info = unicode::convert<std::string>(pcwzLoadInfo);
|
auto load_info = unicode::convert<std::string>(pcwzLoadInfo);
|
||||||
load_info.resize(load_info.size() + 1); //ensure null termination
|
load_info.resize(load_info.size() + 1); //ensure null termination
|
||||||
|
|
||||||
std::vector<uint8_t> buffer(sizeof(RewrittenEntryPointParameters) + entrypoint_replacement.size() + load_info.size() + standalone_rewrittenentrypoint.size());
|
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.
|
// Allocate buffer in remote process, which will be used to fill addresses in the local buffer.
|
||||||
|
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));
|
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());
|
auto& params = *reinterpret_cast<RewrittenEntryPointParameters*>(buffer.data());
|
||||||
|
|
@ -266,24 +284,51 @@ extern "C" DWORD WINAPI RewriteRemoteEntryPointW(HANDLE hProcess, const wchar_t*
|
||||||
params.pEntrypoint = entrypoint;
|
params.pEntrypoint = entrypoint;
|
||||||
|
|
||||||
// Backup original entry point.
|
// 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());
|
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());
|
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);
|
thunks::fill_placeholders(standalone_rewrittenentrypoint.data(), remote_buffer);
|
||||||
memcpy(&buffer[sizeof params + entrypoint_replacement.size() + load_info.size()], standalone_rewrittenentrypoint.data(), standalone_rewrittenentrypoint.size());
|
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.
|
// 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());
|
write_process_memory_or_throw(hProcess, remote_buffer, buffer.data(), buffer.size());
|
||||||
|
|
||||||
|
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());
|
thunks::fill_placeholders(entrypoint_replacement.data(), remote_buffer + sizeof params + entrypoint_replacement.size() + load_info.size());
|
||||||
|
|
||||||
// Overwrite remote process' entry point with a thunk that will load our DLLs and call our trampoline function.
|
// 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());
|
write_process_memory_or_throw(hProcess, entrypoint, entrypoint_replacement.data(), entrypoint_replacement.size());
|
||||||
|
|
||||||
return 0;
|
return S_OK;
|
||||||
} catch (const std::exception& e) {
|
} catch (const std::exception& e) {
|
||||||
OutputDebugStringA(std::format("RewriteRemoteEntryPoint failure: {} (GetLastError: {})\n", e.what(), GetLastError()).c_str());
|
const auto err = GetLastError();
|
||||||
return 1;
|
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());
|
||||||
|
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -300,8 +345,9 @@ static void AbortRewrittenEntryPoint(DWORD err, const std::wstring& clue) {
|
||||||
nullptr);
|
nullptr);
|
||||||
|
|
||||||
if (MessageBoxW(nullptr, std::format(
|
if (MessageBoxW(nullptr, std::format(
|
||||||
L"Failed to load Dalamud. Load game without Dalamud(yes) or abort(no)?\n\nError: 0x{:08X} {}\n\n{}",
|
L"Failed to load Dalamud. Load game without Dalamud(yes) or abort(no)?\n\n{}\n\n{}",
|
||||||
err, pwszMsg ? pwszMsg : L"", clue).c_str(),
|
utils::format_win32_error(err),
|
||||||
|
clue).c_str(),
|
||||||
L"Dalamud.Boot", MB_OK | MB_YESNO) == IDNO)
|
L"Dalamud.Boot", MB_OK | MB_YESNO) == IDNO)
|
||||||
ExitProcess(-1);
|
ExitProcess(-1);
|
||||||
}
|
}
|
||||||
|
|
@ -309,21 +355,62 @@ static void AbortRewrittenEntryPoint(DWORD err, const std::wstring& clue) {
|
||||||
/// @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.
|
||||||
extern "C" void WINAPI RewrittenEntryPoint_AdjustedStack(RewrittenEntryPointParameters & params) {
|
extern "C" void WINAPI RewrittenEntryPoint_AdjustedStack(RewrittenEntryPointParameters & params) {
|
||||||
const auto pOriginalEntryPointBytes = reinterpret_cast<char*>(¶ms) + sizeof(params);
|
HANDLE hMainThreadContinue = nullptr;
|
||||||
const auto pLoadInfo = pOriginalEntryPointBytes + params.entrypointLength;
|
auto hr = S_OK;
|
||||||
|
std::wstring last_operation;
|
||||||
|
std::wstring exc_msg;
|
||||||
|
SetLastError(ERROR_SUCCESS);
|
||||||
|
|
||||||
// Restore original entry point.
|
try {
|
||||||
// Use WriteProcessMemory instead of memcpy to avoid having to fiddle with VirtualProtect.
|
const auto pOriginalEntryPointBytes = reinterpret_cast<char*>(¶ms) + sizeof(params);
|
||||||
if (SIZE_T written; !WriteProcessMemory(GetCurrentProcess(), params.pEntrypoint, pOriginalEntryPointBytes, params.entrypointLength, &written))
|
const auto pLoadInfo = pOriginalEntryPointBytes + params.entrypointLength;
|
||||||
return AbortRewrittenEntryPoint(GetLastError(), L"WriteProcessMemory(entrypoint restoration)");
|
|
||||||
|
|
||||||
const auto hMainThreadContinue = CreateEventW(nullptr, true, false, nullptr);
|
// Restore original entry point.
|
||||||
if (!hMainThreadContinue)
|
// Use WriteProcessMemory instead of memcpy to avoid having to fiddle with VirtualProtect.
|
||||||
return AbortRewrittenEntryPoint(GetLastError(), L"CreateEventW");
|
last_operation = L"restore original entry point";
|
||||||
|
write_process_memory_or_throw(GetCurrentProcess(), params.pEntrypoint, pOriginalEntryPointBytes, params.entrypointLength);
|
||||||
|
|
||||||
if (const auto result = InitializeImpl(pLoadInfo, hMainThreadContinue))
|
hMainThreadContinue = CreateEventW(nullptr, true, false, nullptr);
|
||||||
return AbortRewrittenEntryPoint(result, L"InitializeImpl");
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
WaitForSingleObject(hMainThreadContinue, INFINITE);
|
|
||||||
VirtualFree(¶ms, 0, MEM_RELEASE);
|
VirtualFree(¶ms, 0, MEM_RELEASE);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -408,14 +408,20 @@ utils::signature_finder::result utils::signature_finder::find_one() const {
|
||||||
return find(1, 1, false).front();
|
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 {
|
try {
|
||||||
for (auto pCoveredAddress = &m_data[0];
|
for (auto pCoveredAddress = m_data.data();
|
||||||
pCoveredAddress < &m_data[0] + m_data.size();
|
pCoveredAddress < m_data.data() + m_data.size();
|
||||||
pCoveredAddress = reinterpret_cast<char*>(m_regions.back().BaseAddress) + m_regions.back().RegionSize) {
|
pCoveredAddress = static_cast<char*>(m_regions.back().BaseAddress) + m_regions.back().RegionSize) {
|
||||||
|
|
||||||
MEMORY_BASIC_INFORMATION region{};
|
MEMORY_BASIC_INFORMATION region{};
|
||||||
if (!VirtualQuery(pCoveredAddress, ®ion, sizeof region)) {
|
if (!VirtualQueryEx(hProcess, pCoveredAddress, ®ion, sizeof region)) {
|
||||||
throw std::runtime_error(std::format(
|
throw std::runtime_error(std::format(
|
||||||
"VirtualQuery(addr=0x{:X}, ..., cb={}) failed with Win32 code 0x{:X}",
|
"VirtualQuery(addr=0x{:X}, ..., cb={}) failed with Win32 code 0x{:X}",
|
||||||
reinterpret_cast<size_t>(pCoveredAddress),
|
reinterpret_cast<size_t>(pCoveredAddress),
|
||||||
|
|
@ -423,7 +429,7 @@ utils::memory_tenderizer::memory_tenderizer(const void* pAddress, size_t length,
|
||||||
GetLastError()));
|
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(
|
throw std::runtime_error(std::format(
|
||||||
"(Change)VirtualProtect(addr=0x{:X}, size=0x{:X}, ..., ...) failed with Win32 code 0x{:X}",
|
"(Change)VirtualProtect(addr=0x{:X}, size=0x{:X}, ..., ...) failed with Win32 code 0x{:X}",
|
||||||
reinterpret_cast<size_t>(region.BaseAddress),
|
reinterpret_cast<size_t>(region.BaseAddress),
|
||||||
|
|
@ -436,7 +442,7 @@ utils::memory_tenderizer::memory_tenderizer(const void* pAddress, size_t length,
|
||||||
|
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
for (auto& region : std::ranges::reverse_view(m_regions)) {
|
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
|
// Could not restore; fast fail
|
||||||
__fastfail(GetLastError());
|
__fastfail(GetLastError());
|
||||||
}
|
}
|
||||||
|
|
@ -448,7 +454,7 @@ utils::memory_tenderizer::memory_tenderizer(const void* pAddress, size_t length,
|
||||||
|
|
||||||
utils::memory_tenderizer::~memory_tenderizer() {
|
utils::memory_tenderizer::~memory_tenderizer() {
|
||||||
for (auto& region : std::ranges::reverse_view(m_regions)) {
|
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
|
// Could not restore; fast fail
|
||||||
__fastfail(GetLastError());
|
__fastfail(GetLastError());
|
||||||
}
|
}
|
||||||
|
|
@ -654,3 +660,25 @@ std::wstring utils::escape_shell_arg(const std::wstring& arg) {
|
||||||
}
|
}
|
||||||
return res;
|
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 {
|
class memory_tenderizer {
|
||||||
|
HANDLE m_process;
|
||||||
std::span<char> m_data;
|
std::span<char> m_data;
|
||||||
std::vector<MEMORY_BASIC_INFORMATION> m_regions;
|
std::vector<MEMORY_BASIC_INFORMATION> m_regions;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
memory_tenderizer(HANDLE hProcess, const void* pAddress, size_t length, DWORD dwNewProtect);
|
||||||
|
|
||||||
memory_tenderizer(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>>>
|
template<typename T, typename = std::enable_if_t<std::is_trivial_v<T>&& std::is_standard_layout_v<T>>>
|
||||||
|
|
@ -275,4 +278,6 @@ namespace utils {
|
||||||
void wait_for_game_window();
|
void wait_for_game_window();
|
||||||
|
|
||||||
std::wstring escape_shell_arg(const std::wstring& arg);
|
std::wstring escape_shell_arg(const std::wstring& arg);
|
||||||
|
|
||||||
|
std::wstring format_win32_error(DWORD err);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ int wmain(int argc, wchar_t** argv)
|
||||||
// =========================================================================== //
|
// =========================================================================== //
|
||||||
|
|
||||||
void* entrypoint_vfn;
|
void* entrypoint_vfn;
|
||||||
int result = InitializeClrAndGetEntryPoint(
|
const auto result = InitializeClrAndGetEntryPoint(
|
||||||
GetModuleHandleW(nullptr),
|
GetModuleHandleW(nullptr),
|
||||||
false,
|
false,
|
||||||
runtimeconfig_path,
|
runtimeconfig_path,
|
||||||
|
|
@ -33,15 +33,15 @@ int wmain(int argc, wchar_t** argv)
|
||||||
L"Dalamud.Injector.EntryPoint+MainDelegate, Dalamud.Injector",
|
L"Dalamud.Injector.EntryPoint+MainDelegate, Dalamud.Injector",
|
||||||
&entrypoint_vfn);
|
&entrypoint_vfn);
|
||||||
|
|
||||||
if (result != 0)
|
if (FAILED(result))
|
||||||
return result;
|
return result;
|
||||||
|
|
||||||
typedef void (CORECLR_DELEGATE_CALLTYPE* custom_component_entry_point_fn)(int, wchar_t**);
|
typedef int (CORECLR_DELEGATE_CALLTYPE* custom_component_entry_point_fn)(int, wchar_t**);
|
||||||
custom_component_entry_point_fn entrypoint_fn = reinterpret_cast<custom_component_entry_point_fn>(entrypoint_vfn);
|
custom_component_entry_point_fn entrypoint_fn = reinterpret_cast<custom_component_entry_point_fn>(entrypoint_vfn);
|
||||||
|
|
||||||
logging::I("Running Dalamud Injector...");
|
logging::I("Running Dalamud Injector...");
|
||||||
entrypoint_fn(argc, argv);
|
const auto ret = entrypoint_fn(argc, argv);
|
||||||
logging::I("Done!");
|
logging::I("Done!");
|
||||||
|
|
||||||
return 0;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,89 +31,100 @@ namespace Dalamud.Injector
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="argc">Count of arguments.</param>
|
/// <param name="argc">Count of arguments.</param>
|
||||||
/// <param name="argvPtr">char** string arguments.</param>
|
/// <param name="argvPtr">char** string arguments.</param>
|
||||||
public delegate void MainDelegate(int argc, IntPtr argvPtr);
|
/// <returns>Return value (HRESULT).</returns>
|
||||||
|
public delegate int MainDelegate(int argc, IntPtr argvPtr);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Start the Dalamud injector.
|
/// Start the Dalamud injector.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="argc">Count of arguments.</param>
|
/// <param name="argc">Count of arguments.</param>
|
||||||
/// <param name="argvPtr">byte** string arguments.</param>
|
/// <param name="argvPtr">byte** string arguments.</param>
|
||||||
public static void Main(int argc, IntPtr argvPtr)
|
/// <returns>Return value (HRESULT).</returns>
|
||||||
|
public static int Main(int argc, IntPtr argvPtr)
|
||||||
{
|
{
|
||||||
List<string> args = new(argc);
|
try
|
||||||
|
|
||||||
unsafe
|
|
||||||
{
|
{
|
||||||
var argv = (IntPtr*)argvPtr;
|
List<string> args = new(argc);
|
||||||
for (var i = 0; i < argc; i++)
|
|
||||||
args.Add(Marshal.PtrToStringUni(argv[i]));
|
|
||||||
}
|
|
||||||
|
|
||||||
Init(args);
|
unsafe
|
||||||
args.Remove("-v"); // Remove "verbose" flag
|
|
||||||
|
|
||||||
if (args.Count >= 2 && args[1].ToLowerInvariant() == "launch-test")
|
|
||||||
{
|
|
||||||
Environment.Exit(ProcessLaunchTestCommand(args));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
DalamudStartInfo startInfo = null;
|
|
||||||
if (args.Count == 1)
|
|
||||||
{
|
|
||||||
// No command defaults to inject
|
|
||||||
args.Add("inject");
|
|
||||||
args.Add("--all");
|
|
||||||
|
|
||||||
#if !DEBUG
|
|
||||||
args.Add("--warn");
|
|
||||||
#endif
|
|
||||||
|
|
||||||
}
|
|
||||||
else if (int.TryParse(args[1], out var _))
|
|
||||||
{
|
|
||||||
// Assume that PID has been passed.
|
|
||||||
args.Insert(1, "inject");
|
|
||||||
|
|
||||||
// If originally second parameter exists, then assume that it's a base64 encoded start info.
|
|
||||||
// Dalamud.Injector.exe inject [pid] [base64]
|
|
||||||
if (args.Count == 4)
|
|
||||||
{
|
{
|
||||||
startInfo = JsonConvert.DeserializeObject<DalamudStartInfo>(Encoding.UTF8.GetString(Convert.FromBase64String(args[3])));
|
var argv = (IntPtr*)argvPtr;
|
||||||
args.RemoveAt(3);
|
for (var i = 0; i < argc; i++)
|
||||||
|
args.Add(Marshal.PtrToStringUni(argv[i]));
|
||||||
|
}
|
||||||
|
|
||||||
|
Init(args);
|
||||||
|
args.Remove("-v"); // Remove "verbose" flag
|
||||||
|
|
||||||
|
if (args.Count >= 2 && args[1].ToLowerInvariant() == "launch-test")
|
||||||
|
{
|
||||||
|
return ProcessLaunchTestCommand(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
DalamudStartInfo startInfo = null;
|
||||||
|
if (args.Count == 1)
|
||||||
|
{
|
||||||
|
// No command defaults to inject
|
||||||
|
args.Add("inject");
|
||||||
|
args.Add("--all");
|
||||||
|
|
||||||
|
#if !DEBUG
|
||||||
|
args.Add("--warn");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
}
|
||||||
|
else if (int.TryParse(args[1], out var _))
|
||||||
|
{
|
||||||
|
// Assume that PID has been passed.
|
||||||
|
args.Insert(1, "inject");
|
||||||
|
|
||||||
|
// If originally second parameter exists, then assume that it's a base64 encoded start info.
|
||||||
|
// Dalamud.Injector.exe inject [pid] [base64]
|
||||||
|
if (args.Count == 4)
|
||||||
|
{
|
||||||
|
startInfo = JsonConvert.DeserializeObject<DalamudStartInfo>(Encoding.UTF8.GetString(Convert.FromBase64String(args[3])));
|
||||||
|
args.RemoveAt(3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
startInfo = ExtractAndInitializeStartInfoFromArguments(startInfo, args);
|
||||||
|
// Remove already handled arguments
|
||||||
|
args.Remove("--console");
|
||||||
|
args.Remove("--msgbox1");
|
||||||
|
args.Remove("--msgbox2");
|
||||||
|
args.Remove("--msgbox3");
|
||||||
|
args.Remove("--etw");
|
||||||
|
args.Remove("--veh");
|
||||||
|
args.Remove("--veh-full");
|
||||||
|
args.Remove("--no-plugin");
|
||||||
|
args.Remove("--no-3rd-plugin");
|
||||||
|
args.Remove("--crash-handler-console");
|
||||||
|
args.Remove("--no-exception-handlers");
|
||||||
|
|
||||||
|
var mainCommand = args[1].ToLowerInvariant();
|
||||||
|
if (mainCommand.Length > 0 && mainCommand.Length <= 6 && "inject"[..mainCommand.Length] == mainCommand)
|
||||||
|
{
|
||||||
|
return ProcessInjectCommand(args, startInfo);
|
||||||
|
}
|
||||||
|
else if (mainCommand.Length > 0 && mainCommand.Length <= 6 &&
|
||||||
|
"launch"[..mainCommand.Length] == mainCommand)
|
||||||
|
{
|
||||||
|
return ProcessLaunchCommand(args, startInfo);
|
||||||
|
}
|
||||||
|
else if (mainCommand.Length > 0 && mainCommand.Length <= 4 &&
|
||||||
|
"help"[..mainCommand.Length] == mainCommand)
|
||||||
|
{
|
||||||
|
return ProcessHelpCommand(args, args.Count >= 3 ? args[2] : null);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new CommandLineException($"\"{mainCommand}\" is not a valid command.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch (Exception e)
|
||||||
startInfo = ExtractAndInitializeStartInfoFromArguments(startInfo, args);
|
|
||||||
// Remove already handled arguments
|
|
||||||
args.Remove("--console");
|
|
||||||
args.Remove("--msgbox1");
|
|
||||||
args.Remove("--msgbox2");
|
|
||||||
args.Remove("--msgbox3");
|
|
||||||
args.Remove("--etw");
|
|
||||||
args.Remove("--veh");
|
|
||||||
args.Remove("--veh-full");
|
|
||||||
args.Remove("--no-plugin");
|
|
||||||
args.Remove("--no-3rd-plugin");
|
|
||||||
args.Remove("--crash-handler-console");
|
|
||||||
args.Remove("--no-exception-handlers");
|
|
||||||
|
|
||||||
var mainCommand = args[1].ToLowerInvariant();
|
|
||||||
if (mainCommand.Length > 0 && mainCommand.Length <= 6 && "inject"[..mainCommand.Length] == mainCommand)
|
|
||||||
{
|
{
|
||||||
Environment.Exit(ProcessInjectCommand(args, startInfo));
|
Log.Error(e, "Operation failed.");
|
||||||
}
|
return e.HResult;
|
||||||
else if (mainCommand.Length > 0 && mainCommand.Length <= 6 && "launch"[..mainCommand.Length] == mainCommand)
|
|
||||||
{
|
|
||||||
Environment.Exit(ProcessLaunchCommand(args, startInfo));
|
|
||||||
}
|
|
||||||
else if (mainCommand.Length > 0 && mainCommand.Length <= 4 && "help"[..mainCommand.Length] == mainCommand)
|
|
||||||
{
|
|
||||||
Environment.Exit(ProcessHelpCommand(args, args.Count >= 3 ? args[2] : null));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new CommandLineException($"\"{mainCommand}\" is not a valid command.");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -189,6 +200,7 @@ namespace Dalamud.Injector
|
||||||
CullLogFile(logPath, 1 * 1024 * 1024);
|
CullLogFile(logPath, 1 * 1024 * 1024);
|
||||||
|
|
||||||
Log.Logger = new LoggerConfiguration()
|
Log.Logger = new LoggerConfiguration()
|
||||||
|
.WriteTo.Console(standardErrorFromLevel: LogEventLevel.Debug)
|
||||||
.WriteTo.File(logPath, fileSizeLimitBytes: null)
|
.WriteTo.File(logPath, fileSizeLimitBytes: null)
|
||||||
.MinimumLevel.ControlledBy(levelSwitch)
|
.MinimumLevel.ControlledBy(levelSwitch)
|
||||||
.CreateLogger();
|
.CreateLogger();
|
||||||
|
|
@ -800,12 +812,8 @@ namespace Dalamud.Injector
|
||||||
{
|
{
|
||||||
var startInfo = AdjustStartInfo(dalamudStartInfo, gamePath);
|
var startInfo = AdjustStartInfo(dalamudStartInfo, gamePath);
|
||||||
Log.Information("Using start info: {0}", JsonConvert.SerializeObject(startInfo));
|
Log.Information("Using start info: {0}", JsonConvert.SerializeObject(startInfo));
|
||||||
if (RewriteRemoteEntryPointW(p.Handle, gamePath, JsonConvert.SerializeObject(startInfo)) != 0)
|
Marshal.ThrowExceptionForHR(
|
||||||
{
|
RewriteRemoteEntryPointW(p.Handle, gamePath, JsonConvert.SerializeObject(startInfo)));
|
||||||
Log.Error("[HOOKS] RewriteRemoteEntryPointW failed");
|
|
||||||
throw new Exception("RewriteRemoteEntryPointW failed");
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.Verbose("RewriteRemoteEntryPointW called!");
|
Log.Verbose("RewriteRemoteEntryPointW called!");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ void ConsoleTeardown()
|
||||||
|
|
||||||
std::optional<CoreCLR> g_clr;
|
std::optional<CoreCLR> g_clr;
|
||||||
|
|
||||||
int InitializeClrAndGetEntryPoint(
|
HRESULT InitializeClrAndGetEntryPoint(
|
||||||
void* calling_module,
|
void* calling_module,
|
||||||
bool enable_etw,
|
bool enable_etw,
|
||||||
std::wstring runtimeconfig_path,
|
std::wstring runtimeconfig_path,
|
||||||
|
|
@ -76,7 +76,7 @@ int InitializeClrAndGetEntryPoint(
|
||||||
if (result != 0)
|
if (result != 0)
|
||||||
{
|
{
|
||||||
logging::E("Unable to get RoamingAppData path (err={})", result);
|
logging::E("Unable to get RoamingAppData path (err={})", result);
|
||||||
return result;
|
return HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::filesystem::path fs_app_data(_appdata);
|
std::filesystem::path fs_app_data(_appdata);
|
||||||
|
|
@ -92,7 +92,7 @@ int InitializeClrAndGetEntryPoint(
|
||||||
if (!std::filesystem::exists(dotnet_path))
|
if (!std::filesystem::exists(dotnet_path))
|
||||||
{
|
{
|
||||||
logging::E("Error: Unable to find .NET runtime path");
|
logging::E("Error: Unable to find .NET runtime path");
|
||||||
return 1;
|
return HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND);
|
||||||
}
|
}
|
||||||
|
|
||||||
get_hostfxr_parameters init_parameters
|
get_hostfxr_parameters init_parameters
|
||||||
|
|
@ -137,12 +137,12 @@ int InitializeClrAndGetEntryPoint(
|
||||||
entrypoint_delegate_type_name.c_str(),
|
entrypoint_delegate_type_name.c_str(),
|
||||||
nullptr, entrypoint_fn)) != 0)
|
nullptr, entrypoint_fn)) != 0)
|
||||||
{
|
{
|
||||||
logging::E("Failed to load module (err={})", result);
|
logging::E("Failed to load module (err=0x{:X})", static_cast<uint32_t>(result));
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
logging::I("Done!");
|
logging::I("Done!");
|
||||||
|
|
||||||
// =========================================================================== //
|
// =========================================================================== //
|
||||||
|
|
||||||
return 0;
|
return S_OK;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
void ConsoleSetup(const std::wstring console_name);
|
void ConsoleSetup(const std::wstring console_name);
|
||||||
void ConsoleTeardown();
|
void ConsoleTeardown();
|
||||||
|
|
||||||
int InitializeClrAndGetEntryPoint(
|
HRESULT InitializeClrAndGetEntryPoint(
|
||||||
void* calling_module,
|
void* calling_module,
|
||||||
bool enable_etw,
|
bool enable_etw,
|
||||||
std::wstring runtimeconfig_path,
|
std::wstring runtimeconfig_path,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue