From 7052de382ce8143d4425b227fe1ffcd76877964f Mon Sep 17 00:00:00 2001 From: moonshadow565 Date: Tue, 18 Jul 2023 00:20:51 +0200 Subject: [PATCH] cleanup timeouts, drop handle, configless patcher --- cslol-tools/CMakeLists.txt | 2 + .../patcher/asm/win32_hook_CreateFileA.asm | 50 ++- cslol-tools/lib/lol/patcher/patcher.hpp | 10 +- cslol-tools/lib/lol/patcher/patcher_dummy.cpp | 4 +- cslol-tools/lib/lol/patcher/patcher_macos.cpp | 39 +- cslol-tools/lib/lol/patcher/patcher_win32.cpp | 355 +++++++++++------- cslol-tools/lib/lol/patcher/utility/delay.hpp | 40 ++ cslol-tools/lib/lol/patcher/utility/peex.hpp | 234 ++++++++++++ .../lib/lol/patcher/utility/process.hpp | 18 +- .../lib/lol/patcher/utility/process_macos.cpp | 66 ++-- .../lib/lol/patcher/utility/process_win32.cpp | 117 +++--- cslol-tools/src/main_mod_tools.cpp | 46 ++- 12 files changed, 691 insertions(+), 290 deletions(-) create mode 100644 cslol-tools/lib/lol/patcher/utility/delay.hpp create mode 100644 cslol-tools/lib/lol/patcher/utility/peex.hpp diff --git a/cslol-tools/CMakeLists.txt b/cslol-tools/CMakeLists.txt index cfe3f259..f7f8fc15 100644 --- a/cslol-tools/CMakeLists.txt +++ b/cslol-tools/CMakeLists.txt @@ -37,8 +37,10 @@ add_library(cslol-lib STATIC lib/lol/patcher/patcher_dummy.cpp lib/lol/patcher/patcher_macos.cpp lib/lol/patcher/patcher_win32.cpp + lib/lol/patcher/utility/delay.hpp lib/lol/patcher/utility/lineconfig.hpp lib/lol/patcher/utility/macho.hpp + lib/lol/patcher/utility/peex.hpp lib/lol/patcher/utility/ppp.hpp lib/lol/patcher/utility/process.hpp lib/lol/patcher/utility/process_macos.cpp diff --git a/cslol-tools/lib/lol/patcher/asm/win32_hook_CreateFileA.asm b/cslol-tools/lib/lol/patcher/asm/win32_hook_CreateFileA.asm index 34f12f45..d674b3e9 100644 --- a/cslol-tools/lib/lol/patcher/asm/win32_hook_CreateFileA.asm +++ b/cslol-tools/lib/lol/patcher/asm/win32_hook_CreateFileA.asm @@ -26,6 +26,32 @@ mov arg_dwShareMode[rbp], r8 mov arg_dwDesiredAccess[rbp], rdx mov arg_lpFileName[rbp], rcx +; GENERIC_READ +cmp dword arg_dwDesiredAccess[rbp], 0x80000000 +jne original + +; FILE_SHARE_READ +cmp dword arg_dwShareMode[rbp], 1 +jne original + +; OPEN_EXISTING +cmp dword arg_dwCreationDisposition[rbp], 0x3 +jne original + +; FILE_ATTRIBUTE_NORMAL +cmp dword arg_dwFlagsAndAttributes[rbp], 0x80 +jne original + +; only DATA +cmp byte [rcx], 'D' +jne original +cmp byte [rcx+1], 'A' +jne original +cmp byte [rcx+2], 'T' +jne original +cmp byte [rcx+3], 'A' +jne original + ; prepare buffer for writing lea rdi, QWORD local_buffer[rbp] @@ -51,7 +77,6 @@ write_path_skip: test al, al jne write_path - ; call wide with modified buffer mov rax, arg_hTemplateFile[rbp] mov [rsp+48], rax @@ -71,17 +96,18 @@ cmp rax, -1 jne done ; just call original -mov rax, arg_hTemplateFile[rbp] -mov [rsp+48], rax -mov rax, arg_dwFlagsAndAttributes[rbp] -mov [rsp+40], rax -mov rax, arg_dwCreationDisposition[rbp] -mov [rsp+32], rax -mov r9, arg_lpSecurityAttributes[rbp] -mov r8, arg_dwShareMode[rbp] -mov rdx, arg_dwDesiredAccess[rbp] -mov rcx, arg_lpFileName[rbp] -call QWORD [rel ptr_CreateFileA] +original: + mov rax, arg_hTemplateFile[rbp] + mov [rsp+48], rax + mov rax, arg_dwFlagsAndAttributes[rbp] + mov [rsp+40], rax + mov rax, arg_dwCreationDisposition[rbp] + mov [rsp+32], rax + mov r9, arg_lpSecurityAttributes[rbp] + mov r8, arg_dwShareMode[rbp] + mov rdx, arg_dwDesiredAccess[rbp] + mov rcx, arg_lpFileName[rbp] + call QWORD [rel ptr_CreateFileA] ; epilogue done: add rsp, 72 diff --git a/cslol-tools/lib/lol/patcher/patcher.hpp b/cslol-tools/lib/lol/patcher/patcher.hpp index 44fb6623..03591a2a 100644 --- a/cslol-tools/lib/lol/patcher/patcher.hpp +++ b/cslol-tools/lib/lol/patcher/patcher.hpp @@ -30,8 +30,16 @@ namespace lol::patcher { "League exited", }; - extern auto run(std::function update, + extern auto run(std::function update, fs::path const& profile_path, fs::path const& config_path, fs::path const& game_path) -> void; + + struct PatcherTimeout : std::runtime_error { + using std::runtime_error::runtime_error; + }; + + struct PatcherAborted : std::runtime_error { + PatcherAborted() : std::runtime_error("Aborted as expected") {} + }; } \ No newline at end of file diff --git a/cslol-tools/lib/lol/patcher/patcher_dummy.cpp b/cslol-tools/lib/lol/patcher/patcher_dummy.cpp index 1aa67faa..9b3120f9 100644 --- a/cslol-tools/lib/lol/patcher/patcher_dummy.cpp +++ b/cslol-tools/lib/lol/patcher/patcher_dummy.cpp @@ -9,7 +9,7 @@ using namespace lol; using namespace lol::patcher; using namespace std::chrono_literals; -auto patcher::run(std::function update, +auto patcher::run(std::function update, fs::path const& profile_path, fs::path const& config_path, fs::path const& game_path) -> void { @@ -17,7 +17,7 @@ auto patcher::run(std::function update, (void)config_path; (void)game_path; for (;;) { - if (!update(M_WAIT_START, "")) return; + update(M_WAIT_START, ""); std::this_thread::sleep_for(250ms); } } diff --git a/cslol-tools/lib/lol/patcher/patcher_macos.cpp b/cslol-tools/lib/lol/patcher/patcher_macos.cpp index 1e1568fb..e00be64f 100644 --- a/cslol-tools/lib/lol/patcher/patcher_macos.cpp +++ b/cslol-tools/lib/lol/patcher/patcher_macos.cpp @@ -4,7 +4,7 @@ # include # include -# include "utility/lineconfig.hpp" +# include "utility/delay.hpp" # include "utility/macho.hpp" # include "utility/process.hpp" @@ -90,7 +90,7 @@ struct Context { } }; -auto patcher::run(std::function update, +auto patcher::run(std::function update, fs::path const& profile_path, fs::path const& config_path, fs::path const& game_path) -> void { @@ -99,29 +99,30 @@ auto patcher::run(std::function update, (void)config_path; (void)game_path; for (;;) { - auto process = Process::Find("/LeagueofLegends"); - if (!process) { - if (!update(M_WAIT_START, "")) return; - std::this_thread::sleep_for(250ms); + auto pid = Process::FindPid("/LeagueofLegends"); + if (!pid) { + update(M_WAIT_START, ""); + std::this_thread::sleep_for(10ms); continue; } - if (!update(M_FOUND, "")) return; + update(M_FOUND, ""); + auto process = Process::Open(pid); - if (!update(M_SCAN, "")) return; - ctx.scan(*process); + update(M_SCAN, ""); + ctx.scan(process); - if (!update(M_PATCH, "")) return; - ctx.patch(*process); + update(M_PATCH, ""); + ctx.patch(process); - for (std::uint32_t timeout = 3 * 60 * 60 * 1000; timeout; timeout -= 250) { - if (!update(M_WAIT_EXIT, "")) return; - if (process->WaitExit(0)) { - break; - } - std::this_thread::sleep_for(250ms); - } - if (!update(M_DONE, "")) return; + update(M_WAIT_EXIT, ""); + run_until_or( + 3h, + Intervals{5s, 10s, 15s}, + [&] { return process.IsExited(); }, + []() -> bool { throw PatcherTimeout(std::string("Timed out exit")); }); + + update(M_DONE, ""); } } diff --git a/cslol-tools/lib/lol/patcher/patcher_win32.cpp b/cslol-tools/lib/lol/patcher/patcher_win32.cpp index 2ed59194..b7d3b60a 100644 --- a/cslol-tools/lib/lol/patcher/patcher_win32.cpp +++ b/cslol-tools/lib/lol/patcher/patcher_win32.cpp @@ -5,9 +5,11 @@ # include # include # include +# include # include // do not reorder +# include "utility/delay.hpp" # include "utility/lineconfig.hpp" # include "utility/ppp.hpp" # include "utility/process.hpp" @@ -31,42 +33,29 @@ constexpr auto const find_ptr_CreateFileA = constexpr auto const find_ptr_CRYPTO_free = &ppp::any< - "48 83 C4 28 " - "C3 " - "C7 05 r[?? ?? ?? ??] 00 00 00 00 " - "48 83 C4 28 " - "E9 "_pattern, - "81 00 00 00 " - "00 00 00 00 " - "o[08] 00 00 00 " - "00 00 00 00 " - "?? ?? ?? ?? ?? ?? 00 00 " - "?? ?? ?? ?? ?? ?? 00 00 " - "?? ?? ?? ?? ?? ?? 00 00"_pattern, "80 00 00 00 " "01 00 00 00 " - "o[FF] FF FF FF " + "FF FF FF FF " "01 00 00 00 " "?? ?? ?? ?? ?? ?? 00 00 " "?? ?? ?? ?? ?? ?? 00 00 " - "?? ?? ?? ?? ?? ?? 00 00"_pattern>; - -constexpr auto const find_ret_CRYPTO_free = - &ppp::any< - "41 B9 ?? 00 00 00 " - "48 8B ?? " - "E8 ?? ?? ?? ?? " - "u[48 8B 7C 24 ?? 8B C3 ??]"_pattern>; + "o[??] ?? ?? ?? ?? ?? 00 00"_pattern>; // clang-format on struct ImportTrampoline { - uint8_t data[64] = {}; + uint8_t data[16] = {}; static ImportTrampoline make(Ptr where) { ImportTrampoline result = {{0x48, 0xB8u, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xE0}}; memcpy(result.data + 2, &where, sizeof(where)); return result; } + + static ImportTrampoline make_ind(Ptr where) { + ImportTrampoline result = {{0x48, 0xB8u, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x20}}; + memcpy(result.data + 2, &where, sizeof(where)); + return result; + } }; struct CodePayload { @@ -87,33 +76,43 @@ struct CodePayload { uint8_t ret_match[8] = {0x48, 0x8B, 0x7C, 0x24, 0x70, 0x8B, 0xC3, 0x48}; uint8_t hook_ret[8] = {0xbb, 0x01, 0x00, 0x00, 0x00, 0xc3}; + // Trampoline back to real CreateFileA + ImportTrampoline org_CreateFileA = {}; + // Hooking CreateFileA import trampoline uint8_t hook_CreateFileA[0x100] = { 0xc8, 0x00, 0x10, 0x00, 0x53, 0x57, 0x56, 0x48, 0x83, 0xec, 0x48, 0x4c, 0x89, 0x4d, 0x28, 0x4c, 0x89, 0x45, 0x20, 0x48, 0x89, 0x55, 0x18, 0x48, - 0x89, 0x4d, 0x10, 0x48, 0x8d, 0xbd, 0x00, 0xf0, - 0xff, 0xff, 0x48, 0x8d, 0x35, 0xe7, 0x00, 0x00, - 0x00, 0x66, 0xad, 0x66, 0xab, 0x66, 0x85, 0xc0, - 0x75, 0xf7, 0x48, 0x83, 0xef, 0x02, 0x48, 0x8b, - 0x75, 0x10, 0xb4, 0x00, 0xac, 0x3c, 0x2f, 0x75, - 0x02, 0xb0, 0x5c, 0x66, 0xab, 0x84, 0xc0, 0x75, - 0xf3, 0x48, 0x8b, 0x45, 0x48, 0x48, 0x89, 0x44, - 0x24, 0x30, 0x48, 0x8b, 0x45, 0x38, 0x48, 0x89, - 0x44, 0x24, 0x28, 0x48, 0x8b, 0x45, 0x30, 0x48, - 0x89, 0x44, 0x24, 0x20, 0x4c, 0x8b, 0x4d, 0x28, - 0x4c, 0x8b, 0x45, 0x20, 0x48, 0x8b, 0x55, 0x18, - 0x48, 0x8d, 0x8d, 0x00, 0xf0, 0xff, 0xff, 0x48, - 0x8b, 0x05, 0x8a, 0x00, 0x00, 0x00, 0xff, 0xd0, - 0x48, 0x83, 0xf8, 0xff, 0x75, 0x34, 0x48, 0x8b, - 0x45, 0x48, 0x48, 0x89, 0x44, 0x24, 0x30, 0x48, - 0x8b, 0x45, 0x38, 0x48, 0x89, 0x44, 0x24, 0x28, - 0x48, 0x8b, 0x45, 0x30, 0x48, 0x89, 0x44, 0x24, - 0x20, 0x4c, 0x8b, 0x4d, 0x28, 0x4c, 0x8b, 0x45, - 0x20, 0x48, 0x8b, 0x55, 0x18, 0x48, 0x8b, 0x4d, - 0x10, 0x48, 0x8b, 0x05, 0x48, 0x00, 0x00, 0x00, - 0xff, 0xd0, 0x48, 0x83, 0xc4, 0x48, 0x5e, 0x5f, - 0x5b, 0xc9, 0xc3, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x89, 0x4d, 0x10, 0x81, 0x7d, 0x18, 0x00, 0x00, + 0x00, 0x80, 0x0f, 0x85, 0x9c, 0x00, 0x00, 0x00, + 0x83, 0x7d, 0x20, 0x01, 0x0f, 0x85, 0x92, 0x00, + 0x00, 0x00, 0x83, 0x7d, 0x30, 0x03, 0x0f, 0x85, + 0x88, 0x00, 0x00, 0x00, 0x81, 0x7d, 0x38, 0x80, + 0x00, 0x00, 0x00, 0x75, 0x7f, 0x80, 0x39, 0x44, + 0x75, 0x7a, 0x80, 0x79, 0x01, 0x41, 0x75, 0x74, + 0x80, 0x79, 0x02, 0x54, 0x75, 0x6e, 0x80, 0x79, + 0x03, 0x41, 0x75, 0x68, 0x48, 0x8d, 0xbd, 0x00, + 0xf0, 0xff, 0xff, 0x48, 0x8d, 0x35, 0xa6, 0x00, + 0x00, 0x00, 0x66, 0xad, 0x66, 0xab, 0x66, 0x85, + 0xc0, 0x75, 0xf7, 0x48, 0x83, 0xef, 0x02, 0x48, + 0x8b, 0x75, 0x10, 0xb4, 0x00, 0xac, 0x3c, 0x2f, + 0x75, 0x02, 0xb0, 0x5c, 0x66, 0xab, 0x84, 0xc0, + 0x75, 0xf3, 0x48, 0x8b, 0x45, 0x48, 0x48, 0x89, + 0x44, 0x24, 0x30, 0x48, 0x8b, 0x45, 0x38, 0x48, + 0x89, 0x44, 0x24, 0x28, 0x48, 0x8b, 0x45, 0x30, + 0x48, 0x89, 0x44, 0x24, 0x20, 0x4c, 0x8b, 0x4d, + 0x28, 0x4c, 0x8b, 0x45, 0x20, 0x48, 0x8b, 0x55, + 0x18, 0x48, 0x8d, 0x8d, 0x00, 0xf0, 0xff, 0xff, + 0xff, 0x15, 0x4a, 0x00, 0x00, 0x00, 0x48, 0x83, + 0xf8, 0xff, 0x75, 0x31, 0x48, 0x8b, 0x45, 0x48, + 0x48, 0x89, 0x44, 0x24, 0x30, 0x48, 0x8b, 0x45, + 0x38, 0x48, 0x89, 0x44, 0x24, 0x28, 0x48, 0x8b, + 0x45, 0x30, 0x48, 0x89, 0x44, 0x24, 0x20, 0x4c, + 0x8b, 0x4d, 0x28, 0x4c, 0x8b, 0x45, 0x20, 0x48, + 0x8b, 0x55, 0x18, 0x48, 0x8b, 0x4d, 0x10, 0xff, + 0x15, 0x0b, 0x00, 0x00, 0x00, 0x48, 0x83, 0xc4, + 0x48, 0x5e, 0x5f, 0x5b, 0xc9, 0xc3, 0x90, 0x90, }; Ptr ptr_CreateFileA = {}; Ptr ptr_CreateFileW = {}; @@ -128,23 +127,44 @@ extern void* FindWindowExA(void* parent, void* after, char const* klass, char co } struct Kernel32 { - Ptr ptr_CreateFileA; + Ptr ptr_CreateFileA; + Ptr ptr_CreateFileA_iat; Ptr ptr_CreateFileW; Ptr ptr_GetProcessHeap; Ptr ptr_HeapFree; static auto load() -> Kernel32 { + // load kernel32.dll, we rely on game loading this same dll on same address static auto kernel32 = GetModuleHandleA("KERNEL32.dll"); + + // get CreateFileA function address static auto kernel32_ptr_CreateFileA = GetProcAddress(kernel32, "CreateFileA"); - static auto kernel32_ptr_CreateFileW = GetProcAddress(kernel32, "CreateFileW"); - static auto kernel32_ptr_GetProcessHeap = GetProcAddress(kernel32, "GetProcessHeap"); - static auto kernel32_ptr_HeapFree = GetProcAddress(kernel32, "HeapFree"); lol_throw_if_msg(!kernel32_ptr_CreateFileA, "Failed to find kernel32 ptr CreateFileA"); + + // parse stub instead of IAT cuz easier + static auto kernel32_ptr_CreateFileA_iat = [] { + std::uint16_t insn; + std::int32_t disp; + memcpy(&insn, (char const*)kernel32_ptr_CreateFileA, sizeof(insn)); + if (insn != 0x25FF) { + return (void*)nullptr; + } + memcpy(&disp, (char const*)kernel32_ptr_CreateFileA + sizeof(insn), sizeof(disp)); + return (void*)(((char const*)kernel32_ptr_CreateFileA + sizeof(insn) + sizeof(disp)) + disp); + }(); + + static auto kernel32_ptr_CreateFileW = GetProcAddress(kernel32, "CreateFileW"); lol_throw_if_msg(!kernel32_ptr_CreateFileW, "Failed to find kernel32 ptr CreateFileW"); + + static auto kernel32_ptr_GetProcessHeap = GetProcAddress(kernel32, "GetProcessHeap"); lol_throw_if_msg(!kernel32_ptr_GetProcessHeap, "Failed to find kernel32 ptr GetProcessHeap"); + + static auto kernel32_ptr_HeapFree = GetProcAddress(kernel32, "HeapFree"); lol_throw_if_msg(!kernel32_ptr_HeapFree, "Failed to find kernel32 ptr HeapFree"); + return Kernel32{ - .ptr_CreateFileA = Ptr(kernel32_ptr_CreateFileA), + .ptr_CreateFileA = Ptr(kernel32_ptr_CreateFileA), + .ptr_CreateFileA_iat = Ptr(kernel32_ptr_CreateFileA_iat), .ptr_CreateFileW = Ptr(kernel32_ptr_CreateFileW), .ptr_GetProcessHeap = Ptr(kernel32_ptr_GetProcessHeap), .ptr_HeapFree = Ptr(kernel32_ptr_HeapFree), @@ -195,7 +215,17 @@ struct Context { } } - auto scan(Process const& process) -> void { + auto is_initialized(Process const& process) -> bool { + if (auto base = process.TryBase()) { + auto data = std::array{}; + if (process.TryReadMemory((void*)*base, data.data(), data.size())) { + return true; + } + } + return false; + } + + auto scan_runtime(Process const& process, std::uint32_t expected_checksum) -> void { lol_trace_func(); auto const base = process.Base(); auto const data = process.Dump(); @@ -204,52 +234,95 @@ struct Context { auto const match_ptr_CreateFileA = find_ptr_CreateFileA(data_span, base); lol_throw_if_msg(!match_ptr_CreateFileA, "Failed to find ref to ptr to CreateFileA!"); - auto const match_ptr_CRYPTO_free = find_ptr_CRYPTO_free(data_span, base); - lol_throw_if_msg(!match_ptr_CRYPTO_free, "Failed to find ref to ptr to match_ptr_CRYPTO_free!"); - config.get<"ptr_CreateFileA">() = process.Debase((PtrStorage)std::get<1>(*match_ptr_CreateFileA)); - config.get<"ptr_CRYPTO_free">() = process.Debase((PtrStorage)std::get<1>(*match_ptr_CRYPTO_free)) + 0x18; - config.get<"checksum">() = process.Checksum(); + config.get<"checksum">() = expected_checksum; + config_str = config.to_string(); } - auto check(Process const& process) const -> bool { - return config.check() && process.Checksum() == config.get<"checksum">(); - } + auto scan_file(Process const& process, fs::path expected_game_path) -> std::uint32_t { + lol_trace_func(); + auto process_path = fs::absolute(process.Path().parent_path()); + + // verify game path + if (!expected_game_path.empty()) { + lol_trace_func(lol_trace_var("{}", process_path), lol_trace_var("{}", expected_game_path)); + expected_game_path = fs::absolute(expected_game_path); + lol_throw_if_msg(expected_game_path != process_path, "Wrong game directory!"); + } + + // read in executable + auto file_data = std::vector{}; + { + std::ifstream file(process.Path(), std::ios::binary); + file.seekg(0, std::ios::end); + auto end = file.tellg(); + file.seekg(0, std::ios::beg); + auto beg = file.tellg(); + file_data.resize((std::size_t)(end - beg)); + file.read(file_data.data(), file_data.size()); + lol_throw_if_msg(!file.good(), "Failed to read game executable"); + } + + // parse PE executable + auto peex = PeEx{}; + try { + peex.parse_data(file_data.data(), file_data.size()); + } catch (std::exception const& error) { + lol_throw_msg("Failed to parse game executable: {}", error.what()); + } + + // find crypto offset + auto const match_ptr_CRYPTO_free = find_ptr_CRYPTO_free(file_data, 0); + lol_throw_if_msg(!match_ptr_CRYPTO_free, "Failed to find ref to ptr to match_ptr_CRYPTO_free!"); - auto is_patchable(const Process& process) const noexcept -> bool { - auto is_valid_ptr = [](PtrStorage ptr) { return ptr > 0x10000 && ptr < (1ull << 48); }; + // convert file offset to virtual offset + auto const ptr_CRYPTO_free = peex.raw_to_virtual(std::get<1>(*match_ptr_CRYPTO_free)); + lol_throw_if_msg(!ptr_CRYPTO_free, "Failed to rebase file_off_CRYPTO_free!"); + + // store pointer and debug string + config.get<"ptr_CRYPTO_free">() = (PtrStorage)ptr_CRYPTO_free; + config_str = config.to_string(); + + return peex.checksum(); + } - auto const ptr_CRYPTO_free = process.Rebase(config.get<"ptr_CRYPTO_free">()); + auto is_patchable(Process const& process) const noexcept -> bool { + auto const is_valid_ptr = [](PtrStorage ptr) { return ptr > 0x10000 && ptr < (1ull << 48); }; + auto const ptr_CRYPTO_free = process.Rebase(config.get<"ptr_CreateFileA">()); if (auto result = process.TryRead(ptr_CRYPTO_free); !result || !is_valid_ptr(*result)) { return false; } else if (!process.TryRead(Ptr(*result))) { return false; } - auto const ptr_CreateFileA = process.Rebase(config.get<"ptr_CreateFileA">()); - if (auto result = process.TryRead(ptr_CreateFileA); !result || !is_valid_ptr(*result)) { - return false; - } else if (!process.TryRead(Ptr(*result))) { - return false; + if (!kernel32.ptr_CreateFileA_iat.storage) [[unlikely]] { + auto const ptr_CreateFileA = process.Rebase(config.get<"ptr_CreateFileA">()); + if (auto result = process.TryRead(ptr_CreateFileA); !result || !is_valid_ptr(*result)) { + return false; + } else if (!process.TryRead(Ptr(*result))) { + return false; + } } return true; } auto patch(Process const& process) const -> void { lol_trace_func(); - lol_throw_if_msg(!config.check(), "Config invalid"); // Prepare pointers auto ptr_payload = process.Allocate(); - auto ptr_CreateFileA = Ptr>(process.Rebase(config.get<"ptr_CreateFileA">())); auto ptr_CRYPTO_free = Ptr>(process.Rebase(config.get<"ptr_CRYPTO_free">())); - auto ptr_code_CreateFileA = process.Read(ptr_CreateFileA); // Prepare payload auto payload = CodePayload{}; payload.ptr_GetProcessHeap = kernel32.ptr_GetProcessHeap; payload.ptr_HeapFree = kernel32.ptr_HeapFree; - payload.ptr_CreateFileA = kernel32.ptr_CreateFileA; + payload.ptr_CreateFileA = Ptr(ptr_payload->org_CreateFileA.data); + if (kernel32.ptr_CreateFileA_iat.storage) [[likely]] { + payload.org_CreateFileA = ImportTrampoline::make_ind(kernel32.ptr_CreateFileA_iat); + } else { + payload.org_CreateFileA = ImportTrampoline::make(kernel32.ptr_CreateFileA.storage); + } payload.ptr_CreateFileW = kernel32.ptr_CreateFileW; std::copy_n(prefix.data(), prefix.size(), payload.prefix_open_data); @@ -259,15 +332,13 @@ struct Context { // Write hooks process.Write(ptr_CRYPTO_free, Ptr(ptr_payload->hook_CRYPTO_free)); - process.Write(ptr_code_CreateFileA, ImportTrampoline::make(ptr_payload->hook_CreateFileA)); - } - - auto verify_path(Process const& process, fs::path game_path) -> void { - if (game_path.empty()) return; - auto process_path = fs::absolute(process.Path().parent_path()); - game_path = fs::absolute(game_path); - lol_trace_func(lol_trace_var("{}", process_path), lol_trace_var("{}", game_path)); - lol_throw_if_msg(game_path != process_path, "Wrong game directory!"); + if (kernel32.ptr_CreateFileA_iat.storage) [[likely]] { + process.Write(kernel32.ptr_CreateFileA, ImportTrampoline::make(ptr_payload->hook_CreateFileA)); + } else { + auto ptr_CreateFileA = Ptr>(process.Rebase(config.get<"ptr_CreateFileA">())); + auto ptr_code_CreateFileA = process.Read(ptr_CreateFileA); + process.Write(ptr_code_CreateFileA, ImportTrampoline::make(ptr_payload->hook_CreateFileA)); + } } }; @@ -302,7 +373,7 @@ static auto skinhack_detected() -> char const* { lol_throw_msg("NEW PATCH DETECTED, THIS IS NOT AN ERROR!\n"); } -auto patcher::run(std::function update, +auto patcher::run(std::function update, fs::path const& profile_path, fs::path const& config_path, fs::path const& game_path) -> void { @@ -312,70 +383,82 @@ auto patcher::run(std::function update, ctx.load_config(config_path); (void)game_path; for (;;) { - auto process = Process::Find("League of Legends.exe", "League of Legends (TM) Client"); - if (!process) { - if (!update(M_WAIT_START, "")) return; - std::this_thread::sleep_for(250ms); + auto pid = run_until_or( + 30s, + Intervals{10ms}, + [&] { + update(M_WAIT_START, ""); + return Process::FindPid("League of Legends.exe"); + }, + []() -> std::uint32_t { + if (auto fallback_pid = Process::FindPidWindow("League of Legends (TM) Client")) { + auto pid = Process::FindPid("League of Legends.exe"); + // if we can find pid by window but not by normal means then league is running as admin + lol_throw_if_msg(!pid, "OpenProcess: League running as ADMIN!"); + return pid; + } + return 0; + }); + if (!pid) { continue; } - ctx.verify_path(*process, game_path); - - if (!update(M_FOUND, "")) return; - - auto patchable = false; - if (!ctx.check(*process)) { - for (std::uint32_t timeout = 3 * 60 * 1000; timeout; timeout -= 1) { - if (!update(M_WAIT_INIT, "")) return; - if (process->WaitInitialized(1)) { - break; + // We can drop the handle to the process after this. + { + // Signal that process has been found. + update(M_FOUND, ""); + auto process = Process::Open(pid); + + // Wait until process is actually loaded and has its base and data mapped. + update(M_WAIT_INIT, ""); + run_until_or( + 1min, + Intervals{1ms, 10ms, 100ms}, + [&] { return ctx.is_initialized(process); }, + []() -> bool { throw PatcherTimeout(std::string("Timed out initiliazation")); }); + + // Find necessary offsets and ensure game path. + update(M_SCAN, ""); + auto checksum = ctx.scan_file(process, game_path); + + // fallback for config based patcher if it ever becomes necessary + if (!ctx.kernel32.ptr_CreateFileA_iat.storage) [[unlikely]] { + if (!ctx.config.check() || ctx.config.get<"checksum">() != checksum) { + process.WaitInitialized((std::uint32_t)-1); + ctx.scan_runtime(process, checksum); + update(M_NEED_SAVE, ctx.config_str.c_str()); + ctx.save_config(config_path); + newpatch_detected(); } } - if (!update(M_SCAN, "")) return; - ctx.scan(*process); - - if (!update(M_NEED_SAVE, "")) return; - ctx.save_config(config_path); - - patchable = true; - // newpatch_detected(); - } else { - if (!update(M_WAIT_PATCHABLE, "")) return; - // Fast patch this will pin core to 100% for a few seconds. - for (auto const start = std::chrono::high_resolution_clock::now();;) { - if (ctx.is_patchable(*process)) { - patchable = true; - break; - } - auto const end = std::chrono::high_resolution_clock::now(); - auto const diff = std::chrono::duration_cast(end - start); - if (diff.count() > 10) { - break; - } - } - for (std::uint32_t timeout = 3 * 60 * 1000; !patchable && timeout; timeout -= 1) { - if (!update(M_WAIT_PATCHABLE, "")) return; - if (ctx.is_patchable(*process)) { - patchable = true; - break; - } - std::this_thread::sleep_for(1ms); - } - } - - lol_throw_if_msg(!patchable, "Patchable timeout!"); - if (!update(M_PATCH, ctx.config_str.c_str())) return; - ctx.patch(*process); - - for (std::uint32_t timeout = 3 * 60 * 60 * 1000; timeout; timeout -= 250) { - if (!update(M_WAIT_EXIT, "")) return; - if (process->WaitExit(250)) { - break; - } + // Wait until process is "patchable". + update(M_WAIT_PATCHABLE, ""); + run_until_or( + 2min, + Intervals{1ms, 10ms, 100ms}, + [&] { return ctx.is_patchable(process); }, + []() -> bool { throw PatcherTimeout(std::string("Timed out patchable")); }); + + // Perform paching. + update(M_PATCH, ctx.config_str.c_str()); + ctx.patch(process); } - if (!update(M_DONE, "")) return; + // This uses the dumb way of re-quering for pid to detect when process is finally shutdown. + // It used to use WaitForSingleObject but it turns out that is completly broken and unreliable piece of shit. + // First of it spuriously can "fail" which leaves us wondering what the fuck happend. + // Second it can spuriously succeed which means the process is still somewhat partially alive but not really. + // Thirdly it forces us to keep the handle alive for duration of process lifetime. + update(M_WAIT_EXIT, ""); + run_until_or( + 3h, + Intervals{5s, 10s, 15s}, + [&, pid] { return Process::FindPid("League of Legends.exe") != pid; }, + []() -> bool { throw PatcherTimeout(std::string("Timed out exit")); }); + + // Signal done. + update(M_DONE, ""); } } diff --git a/cslol-tools/lib/lol/patcher/utility/delay.hpp b/cslol-tools/lib/lol/patcher/utility/delay.hpp new file mode 100644 index 00000000..15f1b584 --- /dev/null +++ b/cslol-tools/lib/lol/patcher/utility/delay.hpp @@ -0,0 +1,40 @@ +#pragma once +#include +#include +#include +#include + +namespace lol { + template + struct Intervals { + Intervals() = default; + + template + Intervals(T... intervals) + : intervals{static_cast( + std::chrono::duration_cast(intervals).count())...} {} + + std::array intervals{1.0}; + }; + + template + Intervals(T...) -> Intervals; + + Intervals() -> Intervals<1>; + + template + inline auto run_until_or(auto deadline, Intervals intervals, auto&& poll, auto&& fail) { + auto total = static_cast(std::chrono::duration_cast(deadline).count()); + for (auto interval : intervals.intervals) { + auto slice = total / S; + while (slice > 0) { + if (auto result = poll()) { + return result; + } + std::this_thread::sleep_for(std::chrono::milliseconds{interval}); + slice -= interval; + } + } + return fail(); + } +} \ No newline at end of file diff --git a/cslol-tools/lib/lol/patcher/utility/peex.hpp b/cslol-tools/lib/lol/patcher/utility/peex.hpp new file mode 100644 index 00000000..4b9e9083 --- /dev/null +++ b/cslol-tools/lib/lol/patcher/utility/peex.hpp @@ -0,0 +1,234 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace lol { + struct PeEx { + using data_t = char const*; + + struct DosHeader { + static inline constexpr std::uint16_t MAGIC = 0x5A4D; + + std::uint16_t magic; + std::uint16_t count_bytes_last_page; + std::uint16_t count_pages; + std::uint16_t count_relocations; + std::uint16_t count_paragraphs_header; + std::uint16_t min_paragraphs_alloc; + std::uint16_t max_paragraphs_alloc; + std::uint16_t init_ss_relative; + std::uint16_t init_sp; + std::uint16_t checksum; + std::uint16_t init_ip; + std::uint16_t init_cs_relative; + std::uint16_t file_address_relocations; + std::uint16_t overlay_number; + std::uint16_t reserved[4]; + std::uint16_t oem_id; + std::uint16_t oem_info; + std::uint16_t reserved2[10]; + std::uint32_t file_address_new; + }; + + struct FileHeader { + std::uint16_t machine; + std::uint16_t number_of_sections; + std::uint32_t date_timestamp; + std::uint32_t pointer_to_symbols; + std::uint32_t number_of_symbols; + std::uint16_t size_optional_header; + std::uint16_t characteristics; + }; + + struct DataDirectory { + std::uint32_t address; + std::uint32_t size; + }; + + struct OptionalHeader { + static inline constexpr std::uint32_t MAGIC = 0x20B; + + std::uint16_t magic; + std::uint8_t major_linker_version; + std::uint8_t minor_linker_version; + std::uint32_t size_of_code; + std::uint32_t size_of_initialized_data; + std::uint32_t size_of_uninitialized_data; + std::uint32_t address_of_entry_point; + std::uint32_t base_of_code; + std::uint64_t image_base; + std::uint32_t section_alignment; + std::uint32_t file_alignment; + std::uint16_t major_operating_system_version; + std::uint16_t minor_operating_system_version; + std::uint16_t major_image_version; + std::uint16_t minor_image_version; + std::uint16_t major_subsystem_version; + std::uint16_t minor_subsystem_version; + std::uint32_t win32_version_value; + std::uint32_t size_of_image; + std::uint32_t size_of_headers; + std::uint32_t checksum; + std::uint16_t subsystem; + std::uint16_t dll_characteristics; + std::uint64_t size_of_stack_reserve; + std::uint64_t size_of_stack_commit; + std::uint64_t size_of_heap_reserve; + std::uint64_t size_of_heap_commit; + std::uint32_t loader_flags; + std::uint32_t number_of_rva_and_sizes; + DataDirectory export_directory; + DataDirectory import_directory; + DataDirectory resource_directory; + DataDirectory exception_directory; + DataDirectory security_directory; + DataDirectory base_relocation_directory; + DataDirectory debug_directory; + DataDirectory copyright_directory; + DataDirectory architecture_directory; + DataDirectory global_pointer_directory; + DataDirectory tls_directory; + DataDirectory load_configuration_directory; + DataDirectory bound_import_directory; + DataDirectory import_address_table_directory; + DataDirectory delay_import_descriptors_directory; + DataDirectory com_descriptor_directory; + }; + + struct NtHeader { + static inline constexpr std::uint32_t SIGNATURE = 0x4550; + + std::uint32_t signature; + FileHeader file_header; + OptionalHeader optional_header; + }; + + struct Section { + char name[8]; + std::uint32_t virtual_size; + std::uint32_t virtual_address; + std::uint32_t raw_size; + std::uint32_t raw_address; + std::uint32_t relocations_address; + std::uint32_t line_numbers_address; + std::uint16_t number_of_relocations; + std::uint16_t number_of_line_numbers; + std::uint32_t characterstics; + + operator std::string_view() const noexcept { + return std::string_view(name, (name + sizeof(name)) - std::find(name, name + sizeof(name), '\0')); + }; + }; + + void parse_data(data_t data, size_t size); + + DosHeader const& dos_header() const& noexcept { return dos_header_; } + + NtHeader const& nt_header() const& noexcept { return nt_header_; } + + std::uint32_t offset_rdata() const noexcept { + // assume rdata comes after text for now + return nt_header_.optional_header.base_of_code + nt_header_.optional_header.size_of_code; + } + + std::uint32_t checksum() const noexcept { return nt_header_.optional_header.checksum; } + + std::vector
const& sections() const noexcept { return sections_; } + + Section const* section(std::string_view name) const noexcept { + auto result = std::find(sections_.begin(), sections_.end(), name); + if (result == sections_.end()) { + return nullptr; + } + return &*result; + } + + std::uint64_t raw_to_virtual(std::uint32_t off) const noexcept { + for (auto const& section : sections_) { + if (off >= section.raw_address) { + if (auto disp = off - section.raw_address; disp < section.raw_size) { + return section.virtual_address + disp; + } + } + } + return 0; + } + + private: + struct Stream; + DosHeader dos_header_; + NtHeader nt_header_; + std::vector
sections_; + }; +} + +/*************************************************************************************************/ +/* Implementation */ +/*************************************************************************************************/ + +namespace lol { + struct PeEx::Stream { + data_t data_ = {}; + size_t size_ = {}; + + template + inline T read_raw() { + if (size_ < sizeof(T)) { + throw std::runtime_error("Failed to read raw: no more data!"); + } + T result; + memcpy(&result, data_, sizeof(T)); + data_ += sizeof(T); + size_ -= sizeof(T); + return result; + } + + template + inline std::vector read_vector(std::size_t count) { + auto size = sizeof(T) * count; + if (size_ < size) { + throw std::runtime_error("Failed to read raw: no more data!"); + } + auto results = std::vector(count); + memcpy(results.data(), data_, size); + data_ += size; + size_ -= size; + return results; + } + + inline Stream copy(std::size_t offset = 0, std::size_t count = (std::size_t)-1) const { + if (offset > size_) { + throw std::runtime_error("Failed to seek: out of range!"); + } + if (auto const remain_data = size_ - offset; count == (std::size_t)-1) { + count = remain_data; + } else { + if (remain_data < count) { + throw std::runtime_error("Failed to seek: out of range!"); + } + } + return Stream{data_ + offset, count}; + } + }; + + inline void PeEx::parse_data(data_t data, size_t data_size) { + auto const file_stream = Stream{data, data_size}; + dos_header_ = file_stream.copy().read_raw(); + if (dos_header_.magic != dos_header_.MAGIC) { + throw std::runtime_error("Bad dos_header magic!"); + } + nt_header_ = file_stream.copy(dos_header_.file_address_new).read_raw(); + if (nt_header_.signature != nt_header_.SIGNATURE) { + throw std::runtime_error("Bad nt_header signature!"); + } + auto const sections_offset = dos_header_.file_address_new + 0x18 + nt_header_.file_header.size_optional_header; + sections_ = file_stream.copy(sections_offset).read_vector
(nt_header_.file_header.number_of_sections); + } +} diff --git a/cslol-tools/lib/lol/patcher/utility/process.hpp b/cslol-tools/lib/lol/patcher/utility/process.hpp index 5242e7fc..9604c76c 100644 --- a/cslol-tools/lib/lol/patcher/utility/process.hpp +++ b/cslol-tools/lib/lol/patcher/utility/process.hpp @@ -40,13 +40,13 @@ namespace lol::patcher { private: void *handle_ = nullptr; mutable PtrStorage base_ = {}; - mutable uint32_t checksum_ = {}; mutable std::filesystem::path path_ = {}; mutable uint32_t pid_ = {}; + Process(void *handle, std::uint32_t pid) noexcept : handle_(handle), pid_(pid) {} + public: Process() noexcept; - Process(uint32_t pid) noexcept; Process(Process const &) = delete; Process &operator=(Process const &) = delete; @@ -60,20 +60,24 @@ namespace lol::patcher { inline bool operator!() const noexcept { return !handle_; } - static auto Find(char const *name, char const *window = nullptr) -> std::optional; + static auto Open(std::uint32_t pid) -> Process; + + static auto FindPid(char const *name) -> std::uint32_t; + + static auto FindPidWindow(char const *window) -> std::uint32_t; auto Base() const -> PtrStorage; - auto Path() const -> fs::path; + auto TryBase() const noexcept -> std::optional; - auto Checksum() const -> uint32_t; + auto Path() const -> fs::path; auto Dump() const -> std::vector; - auto WaitExit(uint32_t timeout = 1) const noexcept -> bool; - auto WaitInitialized(uint32_t timeout = 1) const noexcept -> bool; + auto IsExited() const noexcept -> bool; + auto TryReadMemory(void *address, void *dest, size_t size) const noexcept -> bool; auto ReadMemory(void *address, void *dest, size_t size) const -> void; diff --git a/cslol-tools/lib/lol/patcher/utility/process_macos.cpp b/cslol-tools/lib/lol/patcher/utility/process_macos.cpp index fa141f31..5616b07a 100644 --- a/cslol-tools/lib/lol/patcher/utility/process_macos.cpp +++ b/cslol-tools/lib/lol/patcher/utility/process_macos.cpp @@ -14,17 +14,9 @@ using namespace lol::patcher; Process::Process() noexcept = default; -Process::Process(std::uint32_t pid) noexcept { - if (mach_port_t task = {}; !task_for_pid(mach_task_self(), pid, &task)) { - handle_ = (void*)(std::uintptr_t)task; - pid_ = pid; - } -} - Process::Process(Process&& other) noexcept { std::swap(handle_, other.handle_); std::swap(base_, other.base_); - std::swap(checksum_, other.checksum_); std::swap(path_, other.path_); std::swap(pid_, other.pid_); } @@ -33,7 +25,6 @@ Process& Process::operator=(Process&& other) noexcept { if (this == &other) return *this; std::swap(handle_, other.handle_); std::swap(base_, other.base_); - std::swap(checksum_, other.checksum_); std::swap(path_, other.path_); std::swap(pid_, other.pid_); return *this; @@ -45,20 +36,30 @@ Process::~Process() noexcept { } } -auto Process::Find(char const* name, char const* window) -> std::optional { - lol_trace_func(); - pid_t pids[4096]; - int bytes = proc_listpids(PROC_ALL_PIDS, 0, pids, sizeof(pids)); - int n_proc = bytes / sizeof(pid_t); - char pathbuf[PROC_PIDPATHINFO_MAXSIZE] = {}; - for (auto p = pids; p != pids + n_proc; p++) { - if (auto const ret = proc_pidpath(*p, pathbuf, sizeof(pathbuf)); ret > 0) { - if (std::string_view{pathbuf, static_cast(ret)}.ends_with(name)) { - return Process{static_cast(*p)}; +auto Process::FindPid(char const* name) -> std::uint32_t { + if (name) { + pid_t pids[4096]; + int bytes = proc_listpids(PROC_ALL_PIDS, 0, pids, sizeof(pids)); + int n_proc = bytes / sizeof(pid_t); + char pathbuf[PROC_PIDPATHINFO_MAXSIZE] = {}; + for (auto p = pids; p != pids + n_proc; p++) { + if (auto const ret = proc_pidpath(*p, pathbuf, sizeof(pathbuf)); ret > 0) { + if (std::string_view{pathbuf, static_cast(ret)}.ends_with(name)) { + return static_cast(*p); + } } } } - return std::nullopt; + return 0; +} + +auto Process::FindPidWindow(char const* window) -> std::uint32_t { return 0; } + +auto Process::Open(std::uint32_t pid) -> Process { + if (mach_port_t task = {}; !task_for_pid(mach_task_self(), pid, &task)) { + return Process((void*)(std::uintptr_t)task, pid); + } + lol_throw_msg("task_for_pid"); } auto Process::Base() const -> PtrStorage { @@ -82,6 +83,27 @@ auto Process::Base() const -> PtrStorage { return base_; } +auto Process::TryBase() const noexcept -> std::optional { + if (!base_) { + vm_map_offset_t vmoffset = {}; + vm_map_size_t vmsize = {}; + uint32_t nesting_depth = 0; + struct vm_region_submap_info_64 vbr[16] = {}; + mach_msg_type_number_t vbrcount = 16; + kern_return_t kr; + if (auto const err = mach_vm_region_recurse((mach_port_t)(uintptr_t)handle_, + &vmoffset, + &vmsize, + &nesting_depth, + (vm_region_recurse_info_t)&vbr, + &vbrcount)) { + return std::nullopt; + } + base_ = vmoffset - 0x100000000; + } + return base_; +} + auto Process::Path() const -> fs::path { lol_trace_func(); if (!path_.empty()) return path_; @@ -94,8 +116,6 @@ auto Process::Path() const -> fs::path { return path_; } -auto Process::Checksum() const -> uint32_t { return 0; } - auto Process::Dump() const -> std::vector { std::vector buffer = {}; auto const path = Path().generic_string(); @@ -110,7 +130,7 @@ auto Process::Dump() const -> std::vector { return buffer; } -auto Process::WaitExit(uint32_t timeout) const noexcept -> bool { +auto Process::IsExited() const noexcept -> bool { int p = 0; pid_for_task((mach_port_t)(uintptr_t)handle_, &p); return p < 0; diff --git a/cslol-tools/lib/lol/patcher/utility/process_win32.cpp b/cslol-tools/lib/lol/patcher/utility/process_win32.cpp index 7d0d1fab..93d4c8ff 100644 --- a/cslol-tools/lib/lol/patcher/utility/process_win32.cpp +++ b/cslol-tools/lib/lol/patcher/utility/process_win32.cpp @@ -15,31 +15,8 @@ namespace { static inline constexpr DWORD PROCESS_NEEDED_ACCESS = - PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_QUERY_INFORMATION | SYNCHRONIZE; + PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_QUERY_INFORMATION; static inline constexpr size_t DUMP_SIZE = 0x4000000; - - static DWORD find_pid(char const* name, char const* window) { - if (name) { - auto entry = PROCESSENTRY32{.dwSize = sizeof(PROCESSENTRY32)}; - auto handle = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); - for (bool i = Process32First(handle, &entry); i; i = Process32Next(handle, &entry)) { - std::filesystem::path ExeFile = entry.szExeFile; - if (ExeFile.filename().generic_string() == name) { - CloseHandle(handle); - return entry.th32ProcessID; - } - } - CloseHandle(handle); - } - if (window) { - if (auto hwnd = FindWindowExA(nullptr, nullptr, nullptr, window)) { - auto pid = DWORD{}; - GetWindowThreadProcessId(hwnd, &pid); - return pid; - } - } - return 0; - } } using namespace lol; @@ -47,25 +24,19 @@ using namespace lol::patcher; Process::Process() noexcept = default; -Process::Process(uint32_t pid) noexcept : handle_(OpenProcess(PROCESS_NEEDED_ACCESS, false, pid)) { - if (handle_ == INVALID_HANDLE_VALUE) { - handle_ = nullptr; - } -} - Process::Process(Process&& other) noexcept { std::swap(handle_, other.handle_); std::swap(base_, other.base_); - std::swap(checksum_, other.checksum_); std::swap(path_, other.path_); + std::swap(pid_, other.pid_); } Process& Process::operator=(Process&& other) noexcept { if (this == &other) return *this; std::swap(handle_, other.handle_); std::swap(base_, other.base_); - std::swap(checksum_, other.checksum_); std::swap(path_, other.path_); + std::swap(pid_, other.pid_); return *this; } @@ -76,24 +47,39 @@ Process::~Process() noexcept { } } -auto Process::Find(char const* name, char const* window) -> std::optional { - if (auto pid = find_pid(name, window)) { - auto process = Process(pid); - if (!process.handle_) { - lol_throw_msg("OpenProcess: {}", last_error()); - } - HMODULE mod = {}; - DWORD modSize = {}; - if (!EnumProcessModules(process.handle_, &mod, sizeof(mod), &modSize)) { - return std::nullopt; +auto Process::FindPid(char const* name) -> std::uint32_t { + if (name) { + auto entry = PROCESSENTRY32{.dwSize = sizeof(PROCESSENTRY32)}; + auto handle = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + for (bool i = Process32First(handle, &entry); i; i = Process32Next(handle, &entry)) { + std::filesystem::path ExeFile = entry.szExeFile; + if (ExeFile.filename().generic_string() == name) { + CloseHandle(handle); + return entry.th32ProcessID; + } } - char dump[1024] = {}; - if (!ReadProcessMemory(process.handle_, mod, dump, sizeof(dump), nullptr)) { - return std::nullopt; + CloseHandle(handle); + } + return 0; +} + +auto Process::FindPidWindow(char const* window) -> std::uint32_t { + if (window) { + if (auto hwnd = FindWindowExA(nullptr, nullptr, nullptr, window)) { + auto pid = DWORD{}; + GetWindowThreadProcessId(hwnd, &pid); + return pid; } - return std::move(process); } - return std::nullopt; + return 0; +} + +auto Process::Open(std::uint32_t pid) -> Process { + auto handle = OpenProcess(PROCESS_NEEDED_ACCESS, false, pid); + if (!handle || handle == INVALID_HANDLE_VALUE) { + lol_throw_msg("OpenProcess: {}", last_error()); + } + return Process(handle, pid); } auto Process::Base() const -> PtrStorage { @@ -109,6 +95,18 @@ auto Process::Base() const -> PtrStorage { return base_; } +auto Process::TryBase() const noexcept -> std::optional { + if (!base_) { + HMODULE mod = {}; + DWORD modSize = {}; + if (!EnumProcessModules(handle_, &mod, sizeof(mod), &modSize)) { + return std::nullopt; + } + base_ = (PtrStorage)(uintptr_t)(mod); + } + return base_; +} + auto Process::Path() const -> fs::path { lol_trace_func(); if (!path_.empty()) return path_; @@ -122,27 +120,6 @@ auto Process::Path() const -> fs::path { return path_; } -auto Process::Checksum() const -> uint32_t { - lol_trace_func(); - if (!checksum_) { - char raw[1024] = {}; - auto base = this->Base(); - if (!TryReadMemory((void*)(uintptr_t)base, raw, sizeof(raw))) { - lol_throw_msg("RPM PE header: {}", last_error()); - } - auto const dos = (PIMAGE_DOS_HEADER)(raw); - if (dos->e_magic != IMAGE_DOS_SIGNATURE) { - lol_throw_msg("Failed to get dos header signature!"); - } - auto const nt = (PIMAGE_NT_HEADERS64)(raw + dos->e_lfanew); - if (nt->Signature != IMAGE_NT_SIGNATURE) { - lol_throw_msg("Failed to get nt header signature!"); - } - checksum_ = (uint32_t)(nt->OptionalHeader.CheckSum); - } - return checksum_; -} - auto Process::Dump() const -> std::vector { lol_trace_func(); auto base = this->Base(); @@ -153,8 +130,8 @@ auto Process::Dump() const -> std::vector { return buffer; } -auto Process::WaitExit(uint32_t timeout) const noexcept -> bool { - switch (WaitForSingleObject(handle_, timeout)) { +auto Process::IsExited() const noexcept -> bool { + switch (WaitForSingleObject(handle_, 1)) { case WAIT_OBJECT_0: return true; case WAIT_TIMEOUT: diff --git a/cslol-tools/src/main_mod_tools.cpp b/cslol-tools/src/main_mod_tools.cpp index 83f1fcbe..b55837a5 100644 --- a/cslol-tools/src/main_mod_tools.cpp +++ b/cslol-tools/src/main_mod_tools.cpp @@ -248,28 +248,34 @@ static auto mod_runoverlay(fs::path overlay, fs::path config_file, fs::path game fmtlog::setLogFile(stdout, false); auto old_msg = patcher::M_DONE; - patcher::run( - [lock, &old_msg](auto msg, char const* arg) { - if (msg != old_msg) { - old_msg = msg; - fprintf(stdout, "Status: %s\n", patcher::STATUS_MSG[msg]); - fflush(stdout); - if (msg == patcher::M_PATCH) { - *lock = true; - if (arg && *arg) { - fprintf(stdout, "Config: %s\n", arg); - fflush(stdout); + try { + patcher::run( + [lock, &old_msg](auto msg, char const* arg) { + if (msg != old_msg) { + old_msg = msg; + fprintf(stdout, "Status: %s\n", patcher::STATUS_MSG[msg]); + fflush(stdout); + if (msg == patcher::M_PATCH || msg == patcher::M_NEED_SAVE) { + *lock = true; + if (arg && *arg) { + fprintf(stdout, "Config: %s\n", arg); + fflush(stdout); + } + } else if (*lock) { + *lock = false; } } - if (msg == patcher::M_DONE) { - *lock = false; - } - } - return true; - }, - overlay, - config_file, - game); + }, + overlay, + config_file, + game); + } catch (patcher::PatcherAborted const&) { + // nothing to see here, lol + lol::error::stack().clear(); + return; + } catch (...) { + throw; + } } static auto help(fs::path cmd) -> void {