Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a new "Hybrid" Dalamud load method #1668

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions Dalamud.Boot/DalamudStartInfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,13 @@ void from_json(const nlohmann::json& json, DalamudStartInfo::LoadMethod& value)

}
else if (json.is_string()) {
const auto langstr = unicode::convert<std::string>(json.get<std::string>(), &unicode::lower);
if (langstr == "entrypoint")
const auto loadstr = unicode::convert<std::string>(json.get<std::string>(), &unicode::lower);
if (loadstr == "entrypoint")
value = DalamudStartInfo::LoadMethod::Entrypoint;
else if (langstr == "inject")
else if (loadstr == "inject")
value = DalamudStartInfo::LoadMethod::DllInject;
else if (loadstr == "hybrid")
value = DalamudStartInfo::LoadMethod::Hybrid;
}
}

Expand Down
1 change: 1 addition & 0 deletions Dalamud.Boot/DalamudStartInfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ struct DalamudStartInfo {
enum class LoadMethod : int {
Entrypoint,
DllInject,
Hybrid,
};
friend void from_json(const nlohmann::json&, LoadMethod&);

Expand Down
14 changes: 10 additions & 4 deletions Dalamud.Boot/dllmain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@
HMODULE g_hModule;
HINSTANCE g_hGameInstance = GetModuleHandleW(nullptr);

HRESULT WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue) {
HRESULT WINAPI InitializeImpl(char* pLoadInfo, HANDLE hMainThread) {
g_startInfo.from_envvars();

std::string jsonParseError;
try {
from_json(nlohmann::json::parse(std::string_view(static_cast<char*>(lpParam))), g_startInfo);
from_json(nlohmann::json::parse(std::string_view(pLoadInfo)), g_startInfo);
} catch (const std::exception& e) {
jsonParseError = e.what();
}
Expand Down Expand Up @@ -153,14 +153,20 @@ HRESULT WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue) {
// utils::wait_for_game_window();

logging::I("Initializing Dalamud...");
entrypoint_fn(lpParam, hMainThreadContinue);
entrypoint_fn(pLoadInfo, hMainThread);
logging::I("Done!");

return S_OK;
}

struct InitializeParams {
char* pLoadInfo;
HANDLE hMainThread;
};

extern "C" DWORD WINAPI Initialize(LPVOID lpParam) {
return InitializeImpl(lpParam, CreateEvent(nullptr, TRUE, FALSE, nullptr));
InitializeParams* params = static_cast<InitializeParams*>(lpParam);
return InitializeImpl(params->pLoadInfo, params->hMainThread);
}

BOOL APIENTRY DllMain(const HMODULE hModule, const DWORD dwReason, LPVOID lpReserved) {
Expand Down
5 changes: 3 additions & 2 deletions Dalamud.Boot/module.def
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
LIBRARY Dalamud.Boot
EXPORTS
Initialize @1
RewriteRemoteEntryPointW @2
RewrittenEntryPoint @3
GetRemoteEntryPointW @2
RewriteRemoteEntryPointW @3
RewrittenEntryPoint @4
65 changes: 38 additions & 27 deletions Dalamud.Boot/rewrite_entrypoint.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
#include "logging.h"
#include "utils.h"

HRESULT WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue);
HRESULT WINAPI InitializeImpl(char* pLoadInfo, HANDLE hMainThread);

struct RewrittenEntryPointParameters {
char* pEntrypoint;
Expand Down Expand Up @@ -226,21 +226,13 @@ void* get_mapped_image_base_address(HANDLE hProcess, const std::filesystem::path
throw std::runtime_error("corresponding base address not found");
}

/// @brief Rewrite target process' entry point so that this DLL can be loaded and executed first.
/// @brief Get the target process' entry point.
/// @param hProcess Process handle.
/// @param pcwzPath Path to target process.
/// @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.
/// @return address to entry point; null if unsuccessful.
///
extern "C" HRESULT WINAPI RewriteRemoteEntryPointW(HANDLE hProcess, const wchar_t* pcwzPath, const wchar_t* pcwzLoadInfo) {
std::wstring last_operation;
SetLastError(ERROR_SUCCESS);
extern "C" char* WINAPI GetRemoteEntryPointW(HANDLE hProcess, const wchar_t* pcwzPath) {
try {
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{};
Expand All @@ -249,14 +241,36 @@ extern "C" HRESULT WINAPI RewriteRemoteEntryPointW(HANDLE hProcess, const wchar_
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);
return entrypoint;
}
catch (const std::exception& e) {
logging::E("Failed to retrieve entry point for 0x{:X}: {}", hProcess, e.what());
return nullptr;
}
}

/// @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 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.
///
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 entrypoint = GetRemoteEntryPointW(hProcess, pcwzPath);
if (!entrypoint)
throw std::runtime_error("GetRemoteEntryPointW");

last_operation = L"get_path_from_local_module(g_hModule)";
auto local_module_path = get_path_from_local_module(g_hModule);
Expand Down Expand Up @@ -336,7 +350,7 @@ extern "C" HRESULT WINAPI RewriteRemoteEntryPointW(HANDLE hProcess, const wchar_
/// @brief Entry point function "called" instead of game's original main entry point.
/// @param params Parameters set up from RewriteRemoteEntryPoint.
extern "C" void WINAPI RewrittenEntryPoint_AdjustedStack(RewrittenEntryPointParameters & params) {
HANDLE hMainThreadContinue = nullptr;
HANDLE hMainThread = nullptr;
auto hr = S_OK;
std::wstring last_operation;
std::wstring exc_msg;
Expand All @@ -352,13 +366,13 @@ extern "C" void WINAPI RewrittenEntryPoint_AdjustedStack(RewrittenEntryPointPara
write_process_memory_or_throw(GetCurrentProcess(), params.pEntrypoint, pOriginalEntryPointBytes, params.entrypointLength);
FlushInstructionCache(GetCurrentProcess(), params.pEntrypoint, params.entrypointLength);

hMainThreadContinue = CreateEventW(nullptr, true, false, nullptr);
last_operation = L"hMainThreadContinue = CreateEventW";
if (!hMainThreadContinue)
throw std::runtime_error("CreateEventW");
last_operation = L"duplicate main thread handle";
DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), GetCurrentProcess(), &hMainThread, 0, FALSE, DUPLICATE_SAME_ACCESS);
if (!hMainThread)
throw std::runtime_error("DuplicateHandle");

last_operation = L"InitializeImpl";
hr = InitializeImpl(pLoadInfo, hMainThreadContinue);
hr = InitializeImpl(pLoadInfo, hMainThread);
} catch (const std::exception& e) {
if (hr == S_OK) {
const auto err = GetLastError();
Expand All @@ -385,14 +399,11 @@ extern "C" void WINAPI RewrittenEntryPoint_AdjustedStack(RewrittenEntryPointPara
desc.GetBSTR()).c_str(),
L"Dalamud.Boot", MB_OK | MB_YESNO) == IDNO)
ExitProcess(-1);
if (hMainThreadContinue) {
CloseHandle(hMainThreadContinue);
hMainThreadContinue = nullptr;
if (hMainThread) {
CloseHandle(hMainThread);
hMainThread = nullptr;
}
}

if (hMainThreadContinue)
WaitForSingleObject(hMainThreadContinue, INFINITE);

VirtualFree(&params, 0, MEM_RELEASE);
}
3 changes: 3 additions & 0 deletions Dalamud.Boot/veh.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,9 @@ static void append_injector_launch_args(std::vector<std::wstring>& args)
break;
case DalamudStartInfo::LoadMethod::DllInject:
args.emplace_back(L"--mode=inject");
break;
case DalamudStartInfo::LoadMethod::Hybrid:
args.emplace_back(L"--mode=hybrid");
}
args.emplace_back(L"--logpath=\"" + unicode::convert<std::wstring>(g_startInfo.BootLogPath) + L"\"");
args.emplace_back(L"--dalamud-working-directory=\"" + unicode::convert<std::wstring>(g_startInfo.WorkingDirectory) + L"\"");
Expand Down
5 changes: 5 additions & 0 deletions Dalamud.Common/LoadMethod.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,9 @@ public enum LoadMethod
/// Load Dalamud via DLL-injection.
/// </summary>
DllInject,

/// <summary>
/// Load Dalamud via DLL-injection at the suspended entrypoint.
/// </summary>
Hybrid,
}
Loading
Loading