From 7e78b6293b0de07083d8c216dd76843e3aafa159 Mon Sep 17 00:00:00 2001 From: Soreepeong Date: Tue, 13 Feb 2024 07:31:49 +0900 Subject: [PATCH] Make RewriteRemoteEntryPointW report IErrorInfo, VirtualProtectEx before WriteProcessMemory --- Dalamud.Boot/Dalamud.Boot.vcxproj | 2 +- Dalamud.Boot/dllmain.cpp | 8 +- Dalamud.Boot/pch.h | 3 + Dalamud.Boot/rewrite_entrypoint.cpp | 133 +++++++++++++++++++---- Dalamud.Boot/utils.cpp | 44 ++++++-- Dalamud.Boot/utils.h | 5 + Dalamud.Injector.Boot/main.cpp | 10 +- Dalamud.Injector/EntryPoint.cs | 160 +++++++++++++++------------- lib/CoreCLR/boot.cpp | 10 +- lib/CoreCLR/boot.h | 2 +- 10 files changed, 254 insertions(+), 123 deletions(-) diff --git a/Dalamud.Boot/Dalamud.Boot.vcxproj b/Dalamud.Boot/Dalamud.Boot.vcxproj index dd3f57632..ab68c1ec0 100644 --- a/Dalamud.Boot/Dalamud.Boot.vcxproj +++ b/Dalamud.Boot/Dalamud.Boot.vcxproj @@ -199,4 +199,4 @@ - \ No newline at end of file + diff --git a/Dalamud.Boot/dllmain.cpp b/Dalamud.Boot/dllmain.cpp index cf31b7016..e6aa9c4ac 100644 --- a/Dalamud.Boot/dllmain.cpp +++ b/Dalamud.Boot/dllmain.cpp @@ -9,7 +9,7 @@ HMODULE g_hModule; HINSTANCE g_hGameInstance = GetModuleHandleW(nullptr); -DWORD WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue) { +HRESULT WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue) { g_startInfo.from_envvars(); std::string jsonParseError; @@ -114,7 +114,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, @@ -124,7 +124,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); @@ -156,7 +156,7 @@ DWORD WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue) { entrypoint_fn(lpParam, hMainThreadContinue); logging::I("Done!"); - return 0; + return S_OK; } extern "C" DWORD WINAPI Initialize(LPVOID lpParam) { diff --git a/Dalamud.Boot/pch.h b/Dalamud.Boot/pch.h index 6dda9d03b..a09882c74 100644 --- a/Dalamud.Boot/pch.h +++ b/Dalamud.Boot/pch.h @@ -26,6 +26,9 @@ // MSVC Compiler Intrinsic #include +// COM +#include + // C++ Standard Libraries #include #include diff --git a/Dalamud.Boot/rewrite_entrypoint.cpp b/Dalamud.Boot/rewrite_entrypoint.cpp index a47254701..6ece3665c 100644 --- a/Dalamud.Boot/rewrite_entrypoint.cpp +++ b/Dalamud.Boot/rewrite_entrypoint.cpp @@ -1,8 +1,9 @@ #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 { 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) { 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) @@ -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. /// @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. /// -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 { + last_operation = L"get_mapped_image_base_address"; const auto base_address = static_cast(get_mapped_image_base_address(hProcess, pcwzPath)); IMAGE_DOS_HEADER dos_header{}; @@ -244,21 +249,34 @@ extern "C" 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 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(); + last_operation = L"unicode::convert(pcwzLoadInfo)"; auto load_info = unicode::convert(pcwzLoadInfo); load_info.resize(load_info.size() + 1); //ensure null termination - std::vector 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 buffer(bufferSize); // 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(VirtualAllocEx(hProcess, nullptr, buffer.size(), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE)); auto& params = *reinterpret_cast(buffer.data()); @@ -266,24 +284,51 @@ extern "C" DWORD WINAPI RewriteRemoteEntryPointW(HANDLE hProcess, const wchar_t* params.pEntrypoint = entrypoint; // 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()); + 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()); + // 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(entrypoint), buffer.size()); write_process_memory_or_throw(hProcess, entrypoint, entrypoint_replacement.data(), entrypoint_replacement.size()); - return 0; + 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(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(L"Dalamud.Boot")))) + return hr; + if (FAILED(cei->SetDescription(const_cast(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); 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"Failed to load Dalamud. Load game without Dalamud(yes) or abort(no)?\n\n{}\n\n{}", + utils::format_win32_error(err), + clue).c_str(), L"Dalamud.Boot", MB_OK | MB_YESNO) == IDNO) 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. /// @param params Parameters set up from RewriteRemoteEntryPoint. extern "C" void WINAPI RewrittenEntryPoint_AdjustedStack(RewrittenEntryPointParameters & params) { - const auto pOriginalEntryPointBytes = reinterpret_cast(¶ms) + sizeof(params); - const auto pLoadInfo = pOriginalEntryPointBytes + params.entrypointLength; + HANDLE hMainThreadContinue = nullptr; + auto hr = S_OK; + std::wstring last_operation; + std::wstring exc_msg; + SetLastError(ERROR_SUCCESS); - // Restore original entry point. - // Use WriteProcessMemory instead of memcpy to avoid having to fiddle with VirtualProtect. - if (SIZE_T written; !WriteProcessMemory(GetCurrentProcess(), params.pEntrypoint, pOriginalEntryPointBytes, params.entrypointLength, &written)) - return AbortRewrittenEntryPoint(GetLastError(), L"WriteProcessMemory(entrypoint restoration)"); + try { + const auto pOriginalEntryPointBytes = reinterpret_cast(¶ms) + sizeof(params); + const auto pLoadInfo = pOriginalEntryPointBytes + params.entrypointLength; - const auto hMainThreadContinue = CreateEventW(nullptr, true, false, nullptr); - if (!hMainThreadContinue) - return AbortRewrittenEntryPoint(GetLastError(), L"CreateEventW"); + // 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); - if (const auto result = InitializeImpl(pLoadInfo, hMainThreadContinue)) - return AbortRewrittenEntryPoint(result, L"InitializeImpl"); + 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); + } + + ICreateErrorInfoPtr cei; + IErrorInfoPtr ei; + if (SUCCEEDED(CreateErrorInfo(&cei)) + && SUCCEEDED(cei->SetDescription(const_cast(unicode::convert(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); } diff --git a/Dalamud.Boot/utils.cpp b/Dalamud.Boot/utils.cpp index 62a9d7055..419ee6397 100644 --- a/Dalamud.Boot/utils.cpp +++ b/Dalamud.Boot/utils.cpp @@ -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(const_cast(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(const_cast(pAddress)), length) { try { - for (auto pCoveredAddress = &m_data[0]; - pCoveredAddress < &m_data[0] + m_data.size(); - pCoveredAddress = reinterpret_cast(m_regions.back().BaseAddress) + m_regions.back().RegionSize) { + for (auto pCoveredAddress = m_data.data(); + pCoveredAddress < m_data.data() + m_data.size(); + pCoveredAddress = static_cast(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(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(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()); } @@ -654,3 +660,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(&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); +} diff --git a/Dalamud.Boot/utils.h b/Dalamud.Boot/utils.h index ebf48a294..fef920f60 100644 --- a/Dalamud.Boot/utils.h +++ b/Dalamud.Boot/utils.h @@ -111,10 +111,13 @@ namespace utils { }; class memory_tenderizer { + HANDLE m_process; std::span m_data; std::vector 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&& std::is_standard_layout_v>> @@ -275,4 +278,6 @@ namespace utils { void wait_for_game_window(); std::wstring escape_shell_arg(const std::wstring& arg); + + std::wstring format_win32_error(DWORD err); } diff --git a/Dalamud.Injector.Boot/main.cpp b/Dalamud.Injector.Boot/main.cpp index 741505d08..7fc44f5e1 100644 --- a/Dalamud.Injector.Boot/main.cpp +++ b/Dalamud.Injector.Boot/main.cpp @@ -23,7 +23,7 @@ int wmain(int argc, wchar_t** argv) // =========================================================================== // void* entrypoint_vfn; - int result = InitializeClrAndGetEntryPoint( + const auto result = InitializeClrAndGetEntryPoint( GetModuleHandleW(nullptr), false, runtimeconfig_path, @@ -33,15 +33,15 @@ int wmain(int argc, wchar_t** argv) L"Dalamud.Injector.EntryPoint+MainDelegate, Dalamud.Injector", &entrypoint_vfn); - if (result != 0) + if (FAILED(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(entrypoint_vfn); logging::I("Running Dalamud Injector..."); - entrypoint_fn(argc, argv); + const auto ret = entrypoint_fn(argc, argv); logging::I("Done!"); - return 0; + return ret; } diff --git a/Dalamud.Injector/EntryPoint.cs b/Dalamud.Injector/EntryPoint.cs index f839d9656..9e2b95657 100644 --- a/Dalamud.Injector/EntryPoint.cs +++ b/Dalamud.Injector/EntryPoint.cs @@ -31,89 +31,100 @@ namespace Dalamud.Injector /// /// Count of arguments. /// char** string arguments. - public delegate void MainDelegate(int argc, IntPtr argvPtr); + /// Return value (HRESULT). + public delegate int MainDelegate(int argc, IntPtr argvPtr); /// /// Start the Dalamud injector. /// /// Count of arguments. /// byte** string arguments. - public static void Main(int argc, IntPtr argvPtr) + /// Return value (HRESULT). + public static int Main(int argc, IntPtr argvPtr) { - List args = new(argc); - - unsafe + try { - var argv = (IntPtr*)argvPtr; - for (var i = 0; i < argc; i++) - args.Add(Marshal.PtrToStringUni(argv[i])); - } + List args = new(argc); - Init(args); - 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) + unsafe { - startInfo = JsonConvert.DeserializeObject(Encoding.UTF8.GetString(Convert.FromBase64String(args[3]))); - args.RemoveAt(3); + var argv = (IntPtr*)argvPtr; + 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(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."); } } - - 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) + catch (Exception e) { - Environment.Exit(ProcessInjectCommand(args, startInfo)); - } - 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."); + Log.Error(e, "Operation failed."); + return e.HResult; } } @@ -189,6 +200,7 @@ namespace Dalamud.Injector CullLogFile(logPath, 1 * 1024 * 1024); Log.Logger = new LoggerConfiguration() + .WriteTo.Console(standardErrorFromLevel: LogEventLevel.Debug) .WriteTo.File(logPath, fileSizeLimitBytes: null) .MinimumLevel.ControlledBy(levelSwitch) .CreateLogger(); @@ -800,12 +812,8 @@ namespace Dalamud.Injector { var startInfo = AdjustStartInfo(dalamudStartInfo, gamePath); Log.Information("Using start info: {0}", JsonConvert.SerializeObject(startInfo)); - if (RewriteRemoteEntryPointW(p.Handle, gamePath, JsonConvert.SerializeObject(startInfo)) != 0) - { - Log.Error("[HOOKS] RewriteRemoteEntryPointW failed"); - throw new Exception("RewriteRemoteEntryPointW failed"); - } - + Marshal.ThrowExceptionForHR( + RewriteRemoteEntryPointW(p.Handle, gamePath, JsonConvert.SerializeObject(startInfo))); Log.Verbose("RewriteRemoteEntryPointW called!"); } }, diff --git a/lib/CoreCLR/boot.cpp b/lib/CoreCLR/boot.cpp index e3db99c4f..54276aad1 100644 --- a/lib/CoreCLR/boot.cpp +++ b/lib/CoreCLR/boot.cpp @@ -27,7 +27,7 @@ void ConsoleTeardown() std::optional g_clr; -int InitializeClrAndGetEntryPoint( +HRESULT InitializeClrAndGetEntryPoint( void* calling_module, bool enable_etw, std::wstring runtimeconfig_path, @@ -76,7 +76,7 @@ int InitializeClrAndGetEntryPoint( if (result != 0) { 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); @@ -92,7 +92,7 @@ int InitializeClrAndGetEntryPoint( if (!std::filesystem::exists(dotnet_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 @@ -137,12 +137,12 @@ int InitializeClrAndGetEntryPoint( entrypoint_delegate_type_name.c_str(), nullptr, entrypoint_fn)) != 0) { - logging::E("Failed to load module (err={})", result); + logging::E("Failed to load module (err=0x{:X})", static_cast(result)); return result; } logging::I("Done!"); // =========================================================================== // - return 0; + return S_OK; } diff --git a/lib/CoreCLR/boot.h b/lib/CoreCLR/boot.h index f75077edd..33bc58bbf 100644 --- a/lib/CoreCLR/boot.h +++ b/lib/CoreCLR/boot.h @@ -1,7 +1,7 @@ void ConsoleSetup(const std::wstring console_name); void ConsoleTeardown(); -int InitializeClrAndGetEntryPoint( +HRESULT InitializeClrAndGetEntryPoint( void* calling_module, bool enable_etw, std::wstring runtimeconfig_path,