From 7eca5e053e760a29412802ca7af2aa3cfb3b4341 Mon Sep 17 00:00:00 2001 From: praydog Date: Sun, 24 Mar 2024 19:43:58 -0700 Subject: [PATCH 01/33] ObjectExplorer: Fix method caller detection not working correctly --- src/mods/tools/ObjectExplorer.cpp | 49 ++++++++++++++++++++++++------- 1 file changed, 39 insertions(+), 10 deletions(-) diff --git a/src/mods/tools/ObjectExplorer.cpp b/src/mods/tools/ObjectExplorer.cpp index a07358f01..d589924e5 100644 --- a/src/mods/tools/ObjectExplorer.cpp +++ b/src/mods/tools/ObjectExplorer.cpp @@ -4467,7 +4467,11 @@ HookManager::PreHookResult ObjectExplorer::pre_hooked_method_internal(std::vecto size_t nearest_distance = UINT64_MAX; for (auto& it : m_method_map) { - const auto distance = std::abs(static_cast(it.first - ret_addr)); + if (it.first > ret_addr) { + continue; + } + + const auto distance = ret_addr - it.first; if (distance < nearest_distance) { nearest_distance = distance; nearest_method = it.second; @@ -4477,26 +4481,51 @@ HookManager::PreHookResult ObjectExplorer::pre_hooked_method_internal(std::vecto if (nearest_method != nullptr) { auto method_entry = utility::find_function_entry((uintptr_t)nearest_method->get_function()); + bool added = false; + auto add_method = [&]() { + hooked_method.return_addresses_to_methods[ret_addr] = nearest_method; + hooked_method.callers.insert(nearest_method); + + const auto decl_type = nearest_method->get_declaring_type(); + + if (decl_type != nullptr) { + spdlog::info("{} {}.{}", hooked_method.name, nearest_method->get_declaring_type()->get_full_name(), nearest_method->get_name()); + } else { + spdlog::info("{} {}", hooked_method.name, nearest_method->get_name()); + } + + added = true; + }; + if (method_entry != nullptr) { const auto module_addr = (uintptr_t)utility::get_module_within(ret_addr).value_or(nullptr); const auto ret_addr_rva = (uint32_t)(ret_addr - module_addr); const auto ret_addr_entry = utility::find_function_entry(ret_addr); + + // First condition isn't as heavy as fully disassembling the function which is the second condition if (ret_addr_entry == method_entry || (ret_addr_rva >= method_entry->BeginAddress && ret_addr_rva <= method_entry->EndAddress) || (ret_addr_entry != nullptr && ret_addr_entry->BeginAddress >= method_entry->BeginAddress && ret_addr_entry->BeginAddress <= method_entry->EndAddress + 1)) { - hooked_method.return_addresses_to_methods[ret_addr] = nearest_method; - hooked_method.callers.insert(nearest_method); + add_method(); + } else { + // Disassemble all possible code paths to see if we run into the return address + utility::exhaustive_decode((uint8_t*)nearest_method->get_function(), 5000, [&](utility::ExhaustionContext& ctx) -> utility::ExhaustionResult { + if (ctx.addr == ret_addr) { + add_method(); + return utility::ExhaustionResult::BREAK; + } - const auto decl_type = nearest_method->get_declaring_type(); + if (std::string_view{ctx.instrux.Mnemonic}.starts_with("CALL")) { + return utility::ExhaustionResult::STEP_OVER; + } - if (decl_type != nullptr) { - spdlog::info("{} {}.{}", hooked_method.name, nearest_method->get_declaring_type()->get_full_name(), nearest_method->get_name()); - } else { - spdlog::info("{} {}", hooked_method.name, nearest_method->get_name()); + return utility::ExhaustionResult::CONTINUE; + }); + + if (!added) { + spdlog::info("{} @ 0x{:x}", hooked_method.name, ret_addr); } - } else { - spdlog::info("{} @ 0x{:x}", hooked_method.name, ret_addr); } } else { hooked_method.return_addresses_to_methods[ret_addr] = nearest_method; From 929b0eb0c4b72196c2b1805c83acb7f4ffc4c994 Mon Sep 17 00:00:00 2001 From: praydog Date: Mon, 25 Mar 2024 14:05:04 -0700 Subject: [PATCH 02/33] Lua: Add support for System.Double --- src/mods/bindings/Sdk.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/mods/bindings/Sdk.cpp b/src/mods/bindings/Sdk.cpp index 4444a5cfc..0d28d5479 100644 --- a/src/mods/bindings/Sdk.cpp +++ b/src/mods/bindings/Sdk.cpp @@ -799,6 +799,10 @@ sol::object parse_data(lua_State* l, void* data, ::sdk::RETypeDefinition* data_t return sol::make_object(l, ret_val_f); } } + case "System.Double"_fnv: { + auto ret_val_d = *(double*)data; + return sol::make_object(l, ret_val_d); + } case "System.Boolean"_fnv: { auto ret_val_b = *(bool*)data; return sol::make_object(l, ret_val_b); @@ -929,6 +933,9 @@ void set_data(void* data, ::sdk::RETypeDefinition* data_type, sol::object& value case "System.Single"_fnv: *(float*)data = value.as(); return; + case "System.Double"_fnv: + *(double*)data = value.as(); + return; case "System.Boolean"_fnv: *(bool*)data = value.as(); return; From e0252113a1b6f830709410920d9b8ebfd0651981 Mon Sep 17 00:00:00 2001 From: praydog Date: Tue, 26 Mar 2024 10:02:44 -0700 Subject: [PATCH 03/33] ObjectExplorer: Add filtering options to method hooks --- src/mods/tools/ObjectExplorer.cpp | 64 ++++++++++++++++++++++++++++++- src/mods/tools/ObjectExplorer.hpp | 24 +++++++++++- 2 files changed, 85 insertions(+), 3 deletions(-) diff --git a/src/mods/tools/ObjectExplorer.cpp b/src/mods/tools/ObjectExplorer.cpp index d589924e5..42e56e372 100644 --- a/src/mods/tools/ObjectExplorer.cpp +++ b/src/mods/tools/ObjectExplorer.cpp @@ -685,9 +685,68 @@ void ObjectExplorer::display_pins() { } void ObjectExplorer::display_hooks() { - std::scoped_lock _{m_hooked_methods_mtx}; + std::scoped_lock _{m_hooks_context.mtx}; + ImGui::SetNextItemOpen(true, ImGuiCond_::ImGuiCond_Once); + if (ImGui::TreeNode("Options")) { + ImGui::Checkbox("Hide uncalled methods", &m_hooks_context.hide_uncalled_methods); + + // Combobox of the sort method instead + if (ImGui::BeginCombo("Sort by", HooksContext::s_sort_method_names[(uint8_t)m_hooks_context.sort_method])) { + for (int i = 0; i < HooksContext::s_sort_method_names.size(); i++) { + const bool is_selected = (m_hooks_context.sort_method == (HooksContext::SortMethod)i); + + if (ImGui::Selectable(HooksContext::s_sort_method_names[i], is_selected)) { + m_hooks_context.sort_method = (HooksContext::SortMethod)i; + } + + if (is_selected) { + ImGui::SetItemDefaultFocus(); + } + } + + ImGui::EndCombo(); + } + + ImGui::TreePop(); + } + + std::vector hooks_to_iterate{}; for (auto& h : m_hooked_methods) { + if (m_hooks_context.hide_uncalled_methods && h.call_count == 0) { + continue; + } + + hooks_to_iterate.push_back(&h); + } + + switch (m_hooks_context.sort_method) { + case HooksContext::SortMethod::CALL_COUNT: + std::sort(hooks_to_iterate.begin(), hooks_to_iterate.end(), [](const auto& a, const auto& b) { + return a->call_count > b->call_count; + }); + break; + case HooksContext::SortMethod::CALL_TIME: + std::sort(hooks_to_iterate.begin(), hooks_to_iterate.end(), [](const auto& a, const auto& b) { + return a->last_call_time > b->last_call_time; + }); + break; + case HooksContext::SortMethod::METHOD_NAME: + std::sort(hooks_to_iterate.begin(), hooks_to_iterate.end(), [](const auto& a, const auto& b) { + return a->name < b->name; + }); + break; + case HooksContext::SortMethod::NUMBER_OF_CALLERS: + std::sort(hooks_to_iterate.begin(), hooks_to_iterate.end(), [](const auto& a, const auto& b) { + return a->callers.size() > b->callers.size(); + }); + break; + default: + break; + }; + + for (auto& hp : hooks_to_iterate) { + auto& h = *hp; ImGui::PushID(h.method); ImGui::SetNextItemOpen(true, ImGuiCond_::ImGuiCond_Once); @@ -4455,8 +4514,9 @@ HookManager::PreHookResult ObjectExplorer::pre_hooked_method_internal(std::vecto auto& hooked_method = *it; - std::scoped_lock _{m_hooked_methods_mtx}; + std::scoped_lock _{m_hooks_context.mtx}; ++hooked_method.call_count; + hooked_method.last_call_time = std::chrono::high_resolution_clock::now(); if (!hooked_method.return_addresses.contains(ret_addr)) { spdlog::info("Creating new entry for {}", hooked_method.name); diff --git a/src/mods/tools/ObjectExplorer.hpp b/src/mods/tools/ObjectExplorer.hpp index 2f084434c..feb959ccc 100644 --- a/src/mods/tools/ObjectExplorer.hpp +++ b/src/mods/tools/ObjectExplorer.hpp @@ -226,7 +226,28 @@ class ObjectExplorer : public Tool { std::string path{}; }; - std::recursive_mutex m_hooked_methods_mtx{}; + struct HooksContext { + enum class SortMethod : uint8_t { + NONE, + CALL_COUNT, + CALL_TIME, + METHOD_NAME, + NUMBER_OF_CALLERS + }; + + static inline constexpr std::array s_sort_method_names { + "None", + "Call Count", + "Call Time", + "Method Name", + "Number of Callers" + }; + + std::recursive_mutex mtx{}; + bool hide_uncalled_methods{false}; + SortMethod sort_method{SortMethod::NONE}; + } m_hooks_context{}; + std::recursive_mutex m_job_mutex{}; std::vector> m_frame_jobs{}; @@ -246,6 +267,7 @@ class ObjectExplorer : public Tool { }; std::unordered_map callers_context{}; + std::chrono::high_resolution_clock::time_point last_call_time{}; }; asmjit::JitRuntime m_jit_runtime; From b125110e938364515e841bed1aabc529f3fb7789 Mon Sep 17 00:00:00 2001 From: praydog Date: Tue, 26 Mar 2024 14:43:50 -0700 Subject: [PATCH 04/33] DD2: Fix vignetting brightness option not working --- src/mods/Camera.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/mods/Camera.cpp b/src/mods/Camera.cpp index 82185451f..a4a92707c 100644 --- a/src/mods/Camera.cpp +++ b/src/mods/Camera.cpp @@ -196,8 +196,14 @@ void Camera::set_vignette_brightness(float value) noexcept { return; } - // Not a TDB method. - utility::re_managed_object::call_method((::REManagedObject*)m_tone_map, "setVignettingBrightness", (double)value); + static auto set_vignetting_brightness_method = sdk::find_method_definition("via.render.ToneMapping", "set_VignettingBrightness"); + + if (set_vignetting_brightness_method != nullptr) { + set_vignetting_brightness_method->call(sdk::get_thread_context(), m_tone_map, value); + } else { + // Not a TDB method. + utility::re_managed_object::call_method((::REManagedObject*)m_tone_map, "setVignettingBrightness", (double)value); + } } void Camera::set_fov(float fov, float aiming_fov) noexcept { From a1a241af826d2531b4bfd593c5b9367fd3aaf2af Mon Sep 17 00:00:00 2001 From: praydog Date: Tue, 26 Mar 2024 16:57:25 -0700 Subject: [PATCH 05/33] REMethodDefinition: Fix infinite log spam caused by 0 encoded_offset --- shared/sdk/RETypeDB.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/shared/sdk/RETypeDB.cpp b/shared/sdk/RETypeDB.cpp index ea697e03c..7292e1bcd 100644 --- a/shared/sdk/RETypeDB.cpp +++ b/shared/sdk/RETypeDB.cpp @@ -392,6 +392,9 @@ const char* REMethodDefinition::get_name() const { return tdb->get_string(name_offset); } +std::unordered_set logged_encoded_0_methods{}; +std::shared_mutex logged_encoded_0_methods_mtx{}; + void* REMethodDefinition::get_function() const { #if TDB_VER >= 71 if (this->encoded_offset == 0) { @@ -416,6 +419,17 @@ void* REMethodDefinition::get_function() const { } }*/ + { + std::shared_lock _{ logged_encoded_0_methods_mtx }; + + if (logged_encoded_0_methods.contains(const_cast(this))) { + return nullptr; + } + } + + std::unique_lock _{ logged_encoded_0_methods_mtx }; + logged_encoded_0_methods.insert(const_cast(this)); + auto decl_type = this->get_declaring_type(); auto name = decl_type != nullptr ? decl_type->get_full_name() : std::string{"null"}; spdlog::error("[REMethodDefinition::get_function] Encoded offset is 0 (vindex {}) (method: {}.{})", this->get_virtual_index(), name, this->get_name()); From d0d42bad454dfd4fdd18b0635fee8d9496d91b55 Mon Sep 17 00:00:00 2001 From: praydog Date: Tue, 26 Mar 2024 17:20:07 -0700 Subject: [PATCH 06/33] SDK: Dump callstack when Invoke access violations occur --- CMakeLists.txt | 2 + shared/sdk/REContext.cpp | 38 ++++++++++++- shared/utility/Exceptions.cpp | 101 ++++++++++++++++++++++++++++++++++ shared/utility/Exceptions.hpp | 7 +++ src/ExceptionHandler.cpp | 90 +----------------------------- 5 files changed, 148 insertions(+), 90 deletions(-) create mode 100644 shared/utility/Exceptions.cpp create mode 100644 shared/utility/Exceptions.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index b423f91e6..ea2834cae 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -490,8 +490,10 @@ set(CMKR_TARGET utility) set(utility_SOURCES "") list(APPEND utility_SOURCES + "shared/utility/Exceptions.cpp" "shared/utility/FunctionHook.cpp" "shared/utility/Relocate.cpp" + "shared/utility/Exceptions.hpp" "shared/utility/FunctionHook.hpp" "shared/utility/Relocate.hpp" ) diff --git a/shared/sdk/REContext.cpp b/shared/sdk/REContext.cpp index 21e583a73..ad5144dcc 100644 --- a/shared/sdk/REContext.cpp +++ b/shared/sdk/REContext.cpp @@ -1,8 +1,12 @@ +#include +#include + #include #include #include "utility/Scan.hpp" #include "utility/Module.hpp" +#include "utility/Exceptions.hpp" #include "reframework/API.hpp" #include "ReClass.hpp" @@ -397,9 +401,37 @@ namespace sdk { spdlog::info("VMContext: Caught exception code {:x}", code); switch (code) { - case EXCEPTION_ACCESS_VIOLATION: - spdlog::info("VMContext: Attempting to handle access violation."); - + case EXCEPTION_ACCESS_VIOLATION: { + spdlog::info("VMContext: Attempting to handle access violation. Attempting to dump callstack..."); + + spdlog::error("RIP: {:x}", exc->ContextRecord->Rip); + spdlog::error("RSP: {:x}", exc->ContextRecord->Rsp); + spdlog::error("RCX: {:x}", exc->ContextRecord->Rcx); + spdlog::error("RDX: {:x}", exc->ContextRecord->Rdx); + spdlog::error("R8: {:x}", exc->ContextRecord->R8); + spdlog::error("R9: {:x}", exc->ContextRecord->R9); + spdlog::error("R10: {:x}", exc->ContextRecord->R10); + spdlog::error("R11: {:x}", exc->ContextRecord->R11); + spdlog::error("R12: {:x}", exc->ContextRecord->R12); + spdlog::error("R13: {:x}", exc->ContextRecord->R13); + spdlog::error("R14: {:x}", exc->ContextRecord->R14); + spdlog::error("R15: {:x}", exc->ContextRecord->R15); + spdlog::error("RAX: {:x}", exc->ContextRecord->Rax); + spdlog::error("RBX: {:x}", exc->ContextRecord->Rbx); + spdlog::error("RBP: {:x}", exc->ContextRecord->Rbp); + spdlog::error("RSI: {:x}", exc->ContextRecord->Rsi); + spdlog::error("RDI: {:x}", exc->ContextRecord->Rdi); + spdlog::error("EFLAGS: {:x}", exc->ContextRecord->EFlags); + spdlog::error("CS: {:x}", exc->ContextRecord->SegCs); + spdlog::error("DS: {:x}", exc->ContextRecord->SegDs); + spdlog::error("ES: {:x}", exc->ContextRecord->SegEs); + spdlog::error("FS: {:x}", exc->ContextRecord->SegFs); + spdlog::error("GS: {:x}", exc->ContextRecord->SegGs); + spdlog::error("SS: {:x}", exc->ContextRecord->SegSs); + + utility::exceptions::dump_callstack(exc); + + } break; default: break; } diff --git a/shared/utility/Exceptions.cpp b/shared/utility/Exceptions.cpp new file mode 100644 index 000000000..cf9ec4415 --- /dev/null +++ b/shared/utility/Exceptions.cpp @@ -0,0 +1,101 @@ +#include +#include +#include + +#include + +#include "Exceptions.hpp" + +namespace utility { +namespace exceptions{ +static bool symbols_initialized = false; +static HANDLE process{}; + +void dump_callstack(EXCEPTION_POINTERS* exception) { + const auto dbghelp = LoadLibraryA("dbghelp.dll"); + + if (dbghelp == nullptr) { + spdlog::error("Failed to load dbghelp.dll"); + } + + const auto sym_initialize = (decltype(&SymInitialize))(dbghelp != nullptr ? GetProcAddress(dbghelp, "SymInitialize") : nullptr); + const auto sym_from_addr = (decltype(&SymFromAddr))(dbghelp != nullptr ? GetProcAddress(dbghelp, "SymFromAddr") : nullptr); + const auto sym_set_options = (decltype(&SymSetOptions))(dbghelp != nullptr ? GetProcAddress(dbghelp, "SymSetOptions") : nullptr); + const auto sym_get_line_from_addr64 = (decltype(&SymGetLineFromAddr))(dbghelp != nullptr ? GetProcAddress(dbghelp, "SymGetLineFromAddr64") : nullptr); + + const auto pid = GetCurrentProcessId(); + + if (process == nullptr) { + process = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid); + } + + if (sym_set_options != nullptr) { + sym_set_options(SYMOPT_LOAD_LINES | SYMOPT_UNDNAME | SYMOPT_DEFERRED_LOADS); + } + + if (!symbols_initialized && sym_initialize != nullptr) { + symbols_initialized = sym_initialize(process, NULL, TRUE); + if (!symbols_initialized) { + spdlog::error("Failed to initialize symbol handler"); + } + } + + constexpr auto max_stack_depth = 100; + uintptr_t stack[max_stack_depth]{}; + + const auto depth = RtlCaptureStackBackTrace(0, max_stack_depth, (void**)stack, nullptr); + + char symbol_data[sizeof(SYMBOL_INFO) + (256 * sizeof(char))]{}; + SYMBOL_INFO* symbol = (SYMBOL_INFO*)symbol_data; + + symbol->MaxNameLen = 255; + symbol->SizeOfStruct = sizeof(SYMBOL_INFO); + + spdlog::error("Call stack:"); + std::string stack_message{"\n"}; + for (auto i = 0; i < depth; ++i) { + bool symbol_found = false; + const auto module_within = utility::get_module_within(stack[i]); + + if (sym_from_addr != nullptr && symbols_initialized) { + symbol_found = sym_from_addr(process, (DWORD64)stack[i], 0, symbol); + } + + std::string symbol_name = symbol_found ? symbol->Name : "Unknown symbol"; + + if (sym_get_line_from_addr64 != nullptr && symbols_initialized && symbol_found) { + DWORD displacement = 0; + IMAGEHLP_LINE64 line{}; + + line.SizeOfStruct = sizeof(IMAGEHLP_LINE64); + + if (sym_get_line_from_addr64(process, (DWORD64)stack[i], &displacement, &line)) { + symbol_name += " ("; + symbol_name += line.FileName; + symbol_name += ":"; + symbol_name += std::to_string(line.LineNumber); + symbol_name += ")"; + } + } + + if (module_within) { + const auto module_path = utility::get_module_path(*module_within); + const auto relative = stack[i] - (uintptr_t)*module_within; + + if (module_path) { + stack_message += fmt::format(" {}\n {} + 0x{:x}\n\n", symbol_name, *module_path, relative); + continue; + } + + stack_message += fmt::format(" {}\n 0x{:x} + 0x{:x}\n\n", symbol_name, (uintptr_t)*module_within, relative); + continue; + } + + stack_message += fmt::format(" {}\n 0x{:x}\n\n", symbol_name, stack[i]); + } + + spdlog::error(stack_message); + //CloseHandle(process); +} +} +} \ No newline at end of file diff --git a/shared/utility/Exceptions.hpp b/shared/utility/Exceptions.hpp new file mode 100644 index 000000000..3f6a99c5b --- /dev/null +++ b/shared/utility/Exceptions.hpp @@ -0,0 +1,7 @@ +#pragma once + +namespace utility { +namespace exceptions { +void dump_callstack(struct ::_EXCEPTION_POINTERS* exception); +} +} \ No newline at end of file diff --git a/src/ExceptionHandler.cpp b/src/ExceptionHandler.cpp index 642fb160f..3e39d81d3 100644 --- a/src/ExceptionHandler.cpp +++ b/src/ExceptionHandler.cpp @@ -8,95 +8,11 @@ #include "utility/Scan.hpp" #include "utility/Patch.hpp" +#include "utility/Exceptions.hpp" + #include "REFramework.hpp" #include "ExceptionHandler.hpp" -void dump_call_stack(EXCEPTION_POINTERS* exception) { - const auto dbghelp = LoadLibraryA("dbghelp.dll"); - - if (dbghelp == nullptr) { - spdlog::error("Failed to load dbghelp.dll"); - } - - const auto sym_initialize = (decltype(&SymInitialize))(dbghelp != nullptr ? GetProcAddress(dbghelp, "SymInitialize") : nullptr); - const auto sym_from_addr = (decltype(&SymFromAddr))(dbghelp != nullptr ? GetProcAddress(dbghelp, "SymFromAddr") : nullptr); - const auto sym_set_options = (decltype(&SymSetOptions))(dbghelp != nullptr ? GetProcAddress(dbghelp, "SymSetOptions") : nullptr); - const auto sym_get_line_from_addr64 = (decltype(&SymGetLineFromAddr))(dbghelp != nullptr ? GetProcAddress(dbghelp, "SymGetLineFromAddr64") : nullptr); - - const auto pid = GetCurrentProcessId(); - const auto process = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid); - - if (sym_set_options != nullptr) { - sym_set_options(SYMOPT_LOAD_LINES | SYMOPT_UNDNAME | SYMOPT_DEFERRED_LOADS); - } - - bool initialized = false; - - if (sym_initialize != nullptr) { - initialized = sym_initialize(process, NULL, TRUE); - if (!initialized) { - spdlog::error("Failed to initialize symbol handler"); - } - } - - constexpr auto max_stack_depth = 100; - uintptr_t stack[max_stack_depth]{}; - - const auto depth = RtlCaptureStackBackTrace(0, max_stack_depth, (void**)stack, nullptr); - - char symbol_data[sizeof(SYMBOL_INFO) + (256 * sizeof(char))]{}; - SYMBOL_INFO* symbol = (SYMBOL_INFO*)symbol_data; - - symbol->MaxNameLen = 255; - symbol->SizeOfStruct = sizeof(SYMBOL_INFO); - - spdlog::error("Call stack:"); - std::string stack_message{"\n"}; - for (auto i = 0; i < depth; ++i) { - bool symbol_found = false; - const auto module_within = utility::get_module_within(stack[i]); - - if (sym_from_addr != nullptr && initialized) { - symbol_found = sym_from_addr(process, (DWORD64)stack[i], 0, symbol); - } - - std::string symbol_name = symbol_found ? symbol->Name : "Unknown symbol"; - - if (sym_get_line_from_addr64 != nullptr && initialized && symbol_found) { - DWORD displacement = 0; - IMAGEHLP_LINE64 line{}; - - line.SizeOfStruct = sizeof(IMAGEHLP_LINE64); - - if (sym_get_line_from_addr64(process, (DWORD64)stack[i], &displacement, &line)) { - symbol_name += " ("; - symbol_name += line.FileName; - symbol_name += ":"; - symbol_name += std::to_string(line.LineNumber); - symbol_name += ")"; - } - } - - if (module_within) { - const auto module_path = utility::get_module_path(*module_within); - const auto relative = stack[i] - (uintptr_t)*module_within; - - if (module_path) { - stack_message += fmt::format(" {}\n {} + 0x{:x}\n\n", symbol_name, *module_path, relative); - continue; - } - - stack_message += fmt::format(" {}\n 0x{:x} + 0x{:x}\n\n", symbol_name, (uintptr_t)*module_within, relative); - continue; - } - - stack_message += fmt::format(" {}\n 0x{:x}\n\n", symbol_name, stack[i]); - } - - spdlog::error(stack_message); - CloseHandle(process); -} - LONG WINAPI reframework::global_exception_handler(struct _EXCEPTION_POINTERS* ei) { static std::recursive_mutex mtx{}; std::scoped_lock _{ mtx }; @@ -129,7 +45,7 @@ LONG WINAPI reframework::global_exception_handler(struct _EXCEPTION_POINTERS* ei spdlog::error("GS: {:x}", ei->ContextRecord->SegGs); spdlog::error("SS: {:x}", ei->ContextRecord->SegSs); - dump_call_stack(ei); + utility::exceptions::dump_callstack(ei); const auto module_within = utility::get_module_within(ei->ContextRecord->Rip); From e5d7d24d0d96b32ccd263ba755d0d0d473c6618d Mon Sep 17 00:00:00 2001 From: praydog Date: Tue, 26 Mar 2024 19:29:00 -0700 Subject: [PATCH 07/33] DD2: Spoof DLLs at additional points to fix crashes --- src/Main.cpp | 2 ++ src/REFramework.cpp | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Main.cpp b/src/Main.cpp index e5c3b03c2..6e9743ba1 100644 --- a/src/Main.cpp +++ b/src/Main.cpp @@ -76,6 +76,8 @@ void startup_thread(HMODULE reframework_module) { utility::spoof_module_paths_in_exe_dir(); utility::unlink(*our_dll); } +#elif defined (DD2) + utility::spoof_module_paths_in_exe_dir(); #endif } } diff --git a/src/REFramework.cpp b/src/REFramework.cpp index ba341f7cb..e6f8a375d 100644 --- a/src/REFramework.cpp +++ b/src/REFramework.cpp @@ -1556,7 +1556,7 @@ bool REFramework::initialize_game_data() { std::scoped_lock _{this->m_startup_mutex}; try { -#if defined(MHRISE) +#if defined(MHRISE) || defined(DD2) utility::spoof_module_paths_in_exe_dir(); #endif reframework::initialize_sdk(); @@ -1623,7 +1623,7 @@ bool REFramework::initialize_game_data() { spdlog::error("Initialization of mods failed. Reason: exception thrown."); } -#if defined(MHRISE) +#if defined(MHRISE) || defined(DD2) utility::spoof_module_paths_in_exe_dir(); #endif spdlog::info("Game data initialization thread finished"); From 11e55c2cf8fe86a27115cead7dfb426a5595c7f7 Mon Sep 17 00:00:00 2001 From: praydog Date: Tue, 26 Mar 2024 22:23:35 -0700 Subject: [PATCH 08/33] ObjectExplorer: Add "Called Methods" to method displays --- src/mods/tools/ObjectExplorer.cpp | 78 +++++++++++++++++++++++++++++++ src/mods/tools/ObjectExplorer.hpp | 9 ++++ 2 files changed, 87 insertions(+) diff --git a/src/mods/tools/ObjectExplorer.cpp b/src/mods/tools/ObjectExplorer.cpp index 42e56e372..caa485007 100644 --- a/src/mods/tools/ObjectExplorer.cpp +++ b/src/mods/tools/ObjectExplorer.cpp @@ -3041,7 +3041,70 @@ void ObjectExplorer::display_native_fields(REManagedObject* obj, sdk::RETypeDefi //#endif } +void ObjectExplorer::populate_method_meta_info(sdk::REMethodDefinition& m) { + const auto method_ptr = m.get_function(); + + if (method_ptr == nullptr) { + return; + } + + if (m_method_meta_infos.contains((uintptr_t)method_ptr)) { + return; + } + + m_method_meta_infos[(uintptr_t)method_ptr] = std::make_unique(); + + if (IsBadReadPtr(method_ptr, sizeof(void*))) { + return; + } + + std::unordered_set called_functions{}; + + // Disassemble the function's instructions looking for calls to other functions, and add them to the list of methods this function calls + utility::exhaustive_decode((uint8_t*)method_ptr, 5000, [&](utility::ExhaustionContext& ctx) -> utility::ExhaustionResult { + if (ctx.addr != (uintptr_t)method_ptr) { + if (auto it = m_method_map.find(ctx.addr); it != m_method_map.end()) { + called_functions.insert(it->second); + } + } + + if (std::string_view{ctx.instrux.Mnemonic}.starts_with("CALL")) { + const auto resolved_addr = utility::resolve_displacement(ctx.addr); + + if (resolved_addr) { + if (auto it = m_method_map.find(*resolved_addr); it != m_method_map.end()) { + called_functions.insert(it->second); + } + } + + // Step over all calls, we don't want to go down a rabbit hole that freezes the program + return utility::ExhaustionResult::STEP_OVER; + } + + return utility::ExhaustionResult::CONTINUE; + }); + + for (auto& called_function : called_functions) { + m_method_meta_infos[(uintptr_t)method_ptr]->called_functions.push_back(called_function); + } + + std::sort( + m_method_meta_infos[(uintptr_t)method_ptr]->called_functions.begin(), + m_method_meta_infos[(uintptr_t)method_ptr]->called_functions.end(), + [](sdk::REMethodDefinition* a, sdk::REMethodDefinition* b) + { + const auto decltype_a = a->get_declaring_type(); + const auto decltype_b = b->get_declaring_type(); + const auto fullname_a = decltype_a != nullptr ? decltype_a->get_full_name() + "." + a->get_name() : a->get_name(); + const auto fullname_b = decltype_b != nullptr ? decltype_b->get_full_name() + "." + b->get_name() : b->get_name(); + + return fullname_a < fullname_b; + }); +} + void ObjectExplorer::attempt_display_method(REManagedObject* obj, sdk::REMethodDefinition& m, bool use_full_name) { + populate_method_meta_info(m); + const auto declaring_type = m.get_declaring_type(); const auto method_name = (use_full_name && declaring_type != nullptr) ? declaring_type->get_full_name() + "." + m.get_name() : m.get_name(); const auto method_return_type = m.get_return_type(); @@ -3181,6 +3244,21 @@ void ObjectExplorer::attempt_display_method(REManagedObject* obj, sdk::REMethodD } } + if (ImGui::BeginTable("##calls", 1, ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable)) { + ImGui::TableNextColumn(); + ImGui::Text("Called Method"); + + // Show a list of methods this method calls + if (m_method_meta_infos.contains((uintptr_t)method_ptr)) { + for (const auto& called_m : m_method_meta_infos[(uintptr_t)method_ptr]->called_functions) { + ImGui::TableNextColumn(); + attempt_display_method(nullptr, *called_m, true); + } + } + + ImGui::EndTable(); + } + if (ImGui::BeginTable("##disassembly", 3, ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable)) { ImGui::TableNextColumn(); ImGui::Text("Address"); diff --git a/src/mods/tools/ObjectExplorer.hpp b/src/mods/tools/ObjectExplorer.hpp index feb959ccc..c510a8706 100644 --- a/src/mods/tools/ObjectExplorer.hpp +++ b/src/mods/tools/ObjectExplorer.hpp @@ -161,6 +161,7 @@ class ObjectExplorer : public Tool { void display_reflection_properties(REManagedObject* obj, REType* type_info); void display_native_methods(REManagedObject* obj, sdk::RETypeDefinition* tdef); void display_native_fields(REManagedObject* obj, sdk::RETypeDefinition* tdef); + void populate_method_meta_info(sdk::REMethodDefinition& m); void attempt_display_method(REManagedObject* obj, sdk::REMethodDefinition& m, bool use_full_name = false); void attempt_display_field(REManagedObject* obj, VariableDescriptor* desc, REType* type_info); void display_data(void* data, void* real_data, std::string type_name, bool is_enum = false, bool managed_str = false, const sdk::RETypeDefinition* override_def = nullptr); @@ -270,6 +271,14 @@ class ObjectExplorer : public Tool { std::chrono::high_resolution_clock::time_point last_call_time{}; }; + // Contains extra information about the method + struct MethodMetaInfo { + std::vector called_functions{}; + }; + + // Function address -> meta info (not REMethodDefinition, since method defs can share function addresses) + std::unordered_map> m_method_meta_infos{}; + asmjit::JitRuntime m_jit_runtime; std::vector m_pinned_objects{}; From 6104cf0543a109f5fb3f3c37a77ecafe38d9a9b4 Mon Sep 17 00:00:00 2001 From: praydog Date: Tue, 26 Mar 2024 23:02:30 -0700 Subject: [PATCH 09/33] Lua: Fix sdk.create_* functions throwing exceptions --- shared/sdk/REContext.cpp | 99 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 89 insertions(+), 10 deletions(-) diff --git a/shared/sdk/REContext.cpp b/shared/sdk/REContext.cpp index ad5144dcc..898201c64 100644 --- a/shared/sdk/REContext.cpp +++ b/shared/sdk/REContext.cpp @@ -477,7 +477,15 @@ namespace sdk { ::REManagedObject* VM::create_sbyte(int8_t value) { static auto sbyte_type = ::sdk::find_type_definition("System.SByte"); - static auto value_field = sbyte_type->get_field("mValue"); + static auto value_field = [&]() { + auto f = sbyte_type->get_field("mValue"); + if (f == nullptr) { + f = sbyte_type->get_field("m_value"); + } + + return f; + }(); + auto new_obj = sbyte_type->create_instance_full(); if (new_obj == nullptr) { @@ -490,7 +498,14 @@ namespace sdk { ::REManagedObject* VM::create_byte(uint8_t value) { static auto byte_type = ::sdk::find_type_definition("System.Byte"); - static auto value_field = byte_type->get_field("mValue"); + static auto value_field = [&]() { + auto f = byte_type->get_field("mValue"); + if (f == nullptr) { + f = byte_type->get_field("m_value"); + } + + return f; + }(); auto new_obj = byte_type->create_instance_full(); if (new_obj == nullptr) { @@ -503,7 +518,15 @@ namespace sdk { ::REManagedObject* VM::create_int16(int16_t value) { static auto int16_type = ::sdk::find_type_definition("System.Int16"); - static auto value_field = int16_type->get_field("mValue"); + static auto value_field = [&]() { + auto f = int16_type->get_field("mValue"); + if (f == nullptr) { + f = int16_type->get_field("m_value"); + } + + return f; + }(); + auto new_obj = int16_type->create_instance_full(); if (new_obj == nullptr) { @@ -516,7 +539,15 @@ namespace sdk { ::REManagedObject* VM::create_uint16(uint16_t value) { static auto uint16_type = ::sdk::find_type_definition("System.UInt16"); - static auto value_field = uint16_type->get_field("mValue"); + static auto value_field = [&]() { + auto f = uint16_type->get_field("mValue"); + if (f == nullptr) { + f = uint16_type->get_field("m_value"); + } + + return f; + }(); + auto new_obj = uint16_type->create_instance_full(); if (new_obj == nullptr) { @@ -529,7 +560,15 @@ namespace sdk { ::REManagedObject* VM::create_int32(int32_t value) { static auto int32_type = ::sdk::find_type_definition("System.Int32"); - static auto value_field = int32_type->get_field("mValue"); + static auto value_field = [&]() { + auto f = int32_type->get_field("mValue"); + if (f == nullptr) { + f = int32_type->get_field("m_value"); + } + + return f; + }(); + auto new_obj = int32_type->create_instance_full(); if (new_obj == nullptr) { @@ -542,7 +581,15 @@ namespace sdk { ::REManagedObject* VM::create_uint32(uint32_t value) { static auto uint32_type = ::sdk::find_type_definition("System.UInt32"); - static auto value_field = uint32_type->get_field("mValue"); + static auto value_field = [&]() { + auto f = uint32_type->get_field("mValue"); + if (f == nullptr) { + f = uint32_type->get_field("m_value"); + } + + return f; + }(); + auto new_obj = uint32_type->create_instance_full(); if (new_obj == nullptr) { @@ -555,7 +602,15 @@ namespace sdk { ::REManagedObject* VM::create_int64(int64_t value) { static auto int64_type = ::sdk::find_type_definition("System.Int64"); - static auto value_field = int64_type->get_field("mValue"); + static auto value_field = [&]() { + auto f = int64_type->get_field("mValue"); + if (f == nullptr) { + f = int64_type->get_field("m_value"); + } + + return f; + }(); + auto new_obj = int64_type->create_instance_full(); if (new_obj == nullptr) { @@ -568,7 +623,15 @@ namespace sdk { ::REManagedObject* VM::create_uint64(uint64_t value) { static auto uint64_type = ::sdk::find_type_definition("System.UInt64"); - static auto value_field = uint64_type->get_field("mValue"); + static auto value_field = [&]() { + auto f = uint64_type->get_field("mValue"); + if (f == nullptr) { + f = uint64_type->get_field("m_value"); + } + + return f; + }(); + auto new_obj = uint64_type->create_instance_full(); if (new_obj == nullptr) { @@ -582,7 +645,15 @@ namespace sdk { ::REManagedObject* VM::create_single(float value) { static auto float_type = ::sdk::find_type_definition("System.Single"); - static auto value_field = float_type->get_field("mValue"); + static auto value_field = [&]() { + auto f = float_type->get_field("mValue"); + if (f == nullptr) { + f = float_type->get_field("m_value"); + } + + return f; + }(); + auto new_obj = float_type->create_instance_full(); if (new_obj == nullptr) { @@ -595,7 +666,15 @@ namespace sdk { ::REManagedObject* VM::create_double(double value) { static auto double_type = ::sdk::find_type_definition("System.Double"); - static auto value_field = double_type->get_field("mValue"); + static auto value_field = [&]() { + auto f = double_type->get_field("mValue"); + if (f == nullptr) { + f = double_type->get_field("m_value"); + } + + return f; + }(); + auto new_obj = double_type->create_instance_full(); if (new_obj == nullptr) { From 0a4a26d7f2f688134486df16e1589b120b6bd22f Mon Sep 17 00:00:00 2001 From: praydog Date: Tue, 26 Mar 2024 23:16:16 -0700 Subject: [PATCH 10/33] ObjectExplorer: Add performance metrics to hooked functions --- src/mods/tools/ObjectExplorer.cpp | 87 +++++++++++++++++++++++++------ src/mods/tools/ObjectExplorer.hpp | 19 +++++-- 2 files changed, 86 insertions(+), 20 deletions(-) diff --git a/src/mods/tools/ObjectExplorer.cpp b/src/mods/tools/ObjectExplorer.cpp index caa485007..d02a6315d 100644 --- a/src/mods/tools/ObjectExplorer.cpp +++ b/src/mods/tools/ObjectExplorer.cpp @@ -726,11 +726,21 @@ void ObjectExplorer::display_hooks() { return a->call_count > b->call_count; }); break; - case HooksContext::SortMethod::CALL_TIME: + case HooksContext::SortMethod::CALL_TIME_LAST: std::sort(hooks_to_iterate.begin(), hooks_to_iterate.end(), [](const auto& a, const auto& b) { return a->last_call_time > b->last_call_time; }); break; + case HooksContext::SortMethod::CALL_TIME_DELTA: + std::sort(hooks_to_iterate.begin(), hooks_to_iterate.end(), [](const auto& a, const auto& b) { + return a->last_call_delta > b->last_call_delta; + }); + break; + case HooksContext::SortMethod::CALL_TIME_TOTAL: + std::sort(hooks_to_iterate.begin(), hooks_to_iterate.end(), [](const auto& a, const auto& b) { + return a->total_call_time > b->total_call_time; + }); + break; case HooksContext::SortMethod::METHOD_NAME: std::sort(hooks_to_iterate.begin(), hooks_to_iterate.end(), [](const auto& a, const auto& b) { return a->name < b->name; @@ -756,6 +766,9 @@ void ObjectExplorer::display_hooks() { if (made_node) { ImGui::Checkbox("Skip function call", &h.skip); ImGui::TextWrapped("Call count: %i", h.call_count); + const float delta_ms = std::chrono::duration_cast>(h.last_call_delta).count(); + const float total_ms = std::chrono::duration_cast>(h.total_call_time).count(); + ImGui::TextWrapped("Call Time (ms): Delta %f, Total %f", delta_ms, total_ms); if (ImGui::TreeNode("Callers")) { for (auto& caller : h.callers) { const auto& context = h.callers_context[caller]; @@ -4001,32 +4014,47 @@ void ObjectExplorer::hook_method(sdk::REMethodDefinition* method, std::optional< } auto& hooked = m_hooked_methods.emplace_back(); + hooked.method = method; + + if (name) { + hooked.name = method->get_declaring_type()->get_full_name() + "." + *name; + } else { + hooked.name = method->get_declaring_type()->get_full_name() + "." + method->get_name(); + } using namespace asmjit; using namespace asmjit::x86; - CodeHolder code{}; - code.init(m_jit_runtime.environment()); - - Assembler a{&code}; + // Pre + { + CodeHolder code{}; + code.init(m_jit_runtime.environment()); + Assembler a{&code}; - a.mov(r9, method); - a.movabs(r10, &ObjectExplorer::pre_hooked_method); - a.jmp(r10); + a.mov(r9, method); + a.movabs(r10, &ObjectExplorer::pre_hooked_method); + a.jmp(r10); - m_jit_runtime.add(&hooked.jitted_function, &code); + m_jit_runtime.add(&hooked.jitted_function, &code); + } - using MT = HookManager::PreHookResult(*)(std::vector& args, std::vector& arg_tys, uintptr_t ret_addr); + // Post + { + CodeHolder code{}; + code.init(m_jit_runtime.environment()); + Assembler a{&code}; - hooked.method = method; + a.mov(r9, method); + a.movabs(r10, &ObjectExplorer::post_hooked_method); + a.jmp(r10); - if (name) { - hooked.name = method->get_declaring_type()->get_full_name() + "." + *name; - } else { - hooked.name = method->get_declaring_type()->get_full_name() + "." + method->get_name(); + m_jit_runtime.add(&hooked.jitted_function_post, &code); } - hooked.hook_id = g_hookman.add(method, (MT)hooked.jitted_function, nullptr); + using MT = HookManager::PreHookResult(*)(std::vector& args, std::vector& arg_tys, uintptr_t ret_addr); + using MTPost = void (*)(uintptr_t& ret_val, sdk::RETypeDefinition* ret_ty, uintptr_t ret_addr); + + hooked.hook_id = g_hookman.add(method, (MT)hooked.jitted_function, (MTPost)hooked.jitted_function_post); } void ObjectExplorer::hook_all_methods(sdk::RETypeDefinition* t) { @@ -4699,3 +4727,30 @@ HookManager::PreHookResult ObjectExplorer::pre_hooked_method_internal(std::vecto HookManager::PreHookResult ObjectExplorer::pre_hooked_method(std::vector& args, std::vector& arg_tys, uintptr_t ret_addr, sdk::REMethodDefinition* method) { return ObjectExplorer::get()->pre_hooked_method_internal(args, arg_tys, ret_addr, method); } + +void ObjectExplorer::post_hooked_method_internal(uintptr_t& ret_val, sdk::RETypeDefinition*& ret_ty, uintptr_t ret_addr, sdk::REMethodDefinition* method) { + auto it = std::find_if(m_hooked_methods.begin(), m_hooked_methods.end(), [method](auto& a) { return a.method == method; }); + + if (it == m_hooked_methods.end()) { + return; + } + + auto& hooked_method = *it; + + std::scoped_lock _{m_hooks_context.mtx}; + + // Reset the last call time if this is the first time we're calling it + // because in between this, there will be a large hitch + // that the game is not causing. + if (hooked_method.call_count <= 1) { + hooked_method.last_call_time = std::chrono::high_resolution_clock::now(); + } + + hooked_method.last_call_end_time = std::chrono::high_resolution_clock::now(); + hooked_method.last_call_delta = hooked_method.last_call_end_time - hooked_method.last_call_time; + hooked_method.total_call_time += hooked_method.last_call_delta; +} + +void ObjectExplorer::post_hooked_method(uintptr_t& ret_val, sdk::RETypeDefinition*& ret_ty, uintptr_t ret_addr, sdk::REMethodDefinition* method) { + ObjectExplorer::get()->post_hooked_method_internal(ret_val, ret_ty, ret_addr, method); +} diff --git a/src/mods/tools/ObjectExplorer.hpp b/src/mods/tools/ObjectExplorer.hpp index c510a8706..32a5b9b31 100644 --- a/src/mods/tools/ObjectExplorer.hpp +++ b/src/mods/tools/ObjectExplorer.hpp @@ -221,6 +221,9 @@ class ObjectExplorer : public Tool { HookManager::PreHookResult pre_hooked_method_internal(std::vector& args, std::vector& arg_tys, uintptr_t ret_addr, sdk::REMethodDefinition* method); static HookManager::PreHookResult pre_hooked_method(std::vector& args, std::vector& arg_tys, uintptr_t ret_addr, sdk::REMethodDefinition* method); + void post_hooked_method_internal(uintptr_t& ret_val, sdk::RETypeDefinition*& ret_ty, uintptr_t ret_addr, sdk::REMethodDefinition* method); + static void post_hooked_method(uintptr_t& ret_val, sdk::RETypeDefinition*& ret_ty, uintptr_t ret_addr, sdk::REMethodDefinition* method); + struct PinnedObject { Address address{}; std::string name{}; @@ -231,15 +234,19 @@ class ObjectExplorer : public Tool { enum class SortMethod : uint8_t { NONE, CALL_COUNT, - CALL_TIME, + CALL_TIME_LAST, + CALL_TIME_DELTA, + CALL_TIME_TOTAL, METHOD_NAME, NUMBER_OF_CALLERS }; - static inline constexpr std::array s_sort_method_names { + static inline constexpr std::array s_sort_method_names { "None", "Call Count", - "Call Time", + "Call Time (Last)", + "Call Time (Delta)", + "Call Time (Total)", "Method Name", "Number of Callers" }; @@ -256,6 +263,7 @@ class ObjectExplorer : public Tool { std::string name{}; sdk::REMethodDefinition* method{nullptr}; uintptr_t jitted_function{}; + uintptr_t jitted_function_post{}; bool skip{false}; size_t hook_id{}; uint32_t call_count{}; @@ -268,7 +276,10 @@ class ObjectExplorer : public Tool { }; std::unordered_map callers_context{}; - std::chrono::high_resolution_clock::time_point last_call_time{}; + std::chrono::high_resolution_clock::time_point last_call_time{std::chrono::high_resolution_clock::now()}; + std::chrono::high_resolution_clock::time_point last_call_end_time{std::chrono::high_resolution_clock::now()}; // assuming not recursive... + std::chrono::high_resolution_clock::duration last_call_delta{}; + std::chrono::high_resolution_clock::duration total_call_time{}; }; // Contains extra information about the method From c6ebac26b43f4524ae3933fbb9970c5fb4b9c4d5 Mon Sep 17 00:00:00 2001 From: praydog Date: Wed, 27 Mar 2024 00:12:35 -0700 Subject: [PATCH 11/33] ObjectExplorer: Add sortable callers for method hooks --- src/mods/tools/ObjectExplorer.cpp | 88 +++++++++++++++++++++++-------- src/mods/tools/ObjectExplorer.hpp | 13 +++++ 2 files changed, 80 insertions(+), 21 deletions(-) diff --git a/src/mods/tools/ObjectExplorer.cpp b/src/mods/tools/ObjectExplorer.cpp index d02a6315d..101ac212d 100644 --- a/src/mods/tools/ObjectExplorer.cpp +++ b/src/mods/tools/ObjectExplorer.cpp @@ -645,7 +645,7 @@ void ObjectExplorer::on_frame() { // on_frame is just going to be a way to display // the pinned objects in a separate window - ImGui::SetNextWindowSize(ImVec2(400, 400), ImGuiCond_::ImGuiCond_Once); + ImGui::SetNextWindowSize(ImVec2(400, 800), ImGuiCond_::ImGuiCond_Once); if (ImGui::Begin("Hooked methods", &open)) { display_hooks(); @@ -766,33 +766,79 @@ void ObjectExplorer::display_hooks() { if (made_node) { ImGui::Checkbox("Skip function call", &h.skip); ImGui::TextWrapped("Call count: %i", h.call_count); + + ImGui::SameLine(); const float delta_ms = std::chrono::duration_cast>(h.last_call_delta).count(); const float total_ms = std::chrono::duration_cast>(h.total_call_time).count(); - ImGui::TextWrapped("Call Time (ms): Delta %f, Total %f", delta_ms, total_ms); - if (ImGui::TreeNode("Callers")) { - for (auto& caller : h.callers) { - const auto& context = h.callers_context[caller]; - const auto declaring_type = caller->get_declaring_type(); - //auto method_name = declaring_type != nullptr ? declaring_type->get_full_name() + "." + caller->get_name() : caller->get_name(); - //method_name += " [" + std::to_string(context.call_count) + "]"; - const auto call_count = std::string("[") + std::to_string(context.call_count) + "]"; - - ImGui::TextUnformatted(call_count.c_str()); - ImGui::SameLine(); - this->attempt_display_method(nullptr, *caller, true); - } + ImGui::TextWrapped("Time (ms): Delta %f, Total %f", delta_ms, total_ms); + + if (ImGui::TreeNode("Info")) { + ImGui::SetNextItemOpen(true, ImGuiCond_::ImGuiCond_Once); + if (ImGui::TreeNode("Callers")) { + // sort callers combo + if (ImGui::BeginCombo("Sort Callers by", HookedMethod::s_sort_callers_names[(uint8_t)h.sort_callers_method])) { + for (int i = 0; i < HookedMethod::s_sort_callers_names.size(); i++) { + const bool is_selected = (h.sort_callers_method == (HookedMethod::SortCallersMethod)i); + + if (ImGui::Selectable(HookedMethod::s_sort_callers_names[i], is_selected)) { + h.sort_callers_method = (HookedMethod::SortCallersMethod)i; + } - ImGui::TreePop(); - } + if (is_selected) { + ImGui::SetItemDefaultFocus(); + } + } - if (ImGui::TreeNode("Return Addresses")) { - for (auto addr : h.return_addresses) { - ImGui::Text("0x%p", addr); + ImGui::EndCombo(); + } + + std::vector callers_to_iterate{}; - if (ImGui::IsItemClicked()) { - ImGui::SetClipboardText((std::stringstream{} << std::hex << addr).str().c_str()); + for (auto& caller : h.callers) { + callers_to_iterate.push_back(caller); } + + switch (h.sort_callers_method) { + case HookedMethod::SortCallersMethod::CALL_COUNT: + std::sort(callers_to_iterate.begin(), callers_to_iterate.end(), [&h](const auto& a, const auto& b) { + return h.callers_context[a].call_count > h.callers_context[b].call_count; + }); + break; + case HookedMethod::SortCallersMethod::METHOD_NAME: + std::sort(callers_to_iterate.begin(), callers_to_iterate.end(), [&h](const auto& a, const auto& b) { + return a->get_name() < b->get_name(); + }); + break; + default: + break; + }; + + for (auto& caller : callers_to_iterate) { + const auto& context = h.callers_context[caller]; + const auto declaring_type = caller->get_declaring_type(); + //auto method_name = declaring_type != nullptr ? declaring_type->get_full_name() + "." + caller->get_name() : caller->get_name(); + //method_name += " [" + std::to_string(context.call_count) + "]"; + const auto call_count = std::string("[") + std::to_string(context.call_count) + "]"; + + ImGui::TextUnformatted(call_count.c_str()); + ImGui::SameLine(); + this->attempt_display_method(nullptr, *caller, true); + } + + ImGui::TreePop(); } + + if (ImGui::TreeNode("Return Addresses")) { + for (auto addr : h.return_addresses) { + ImGui::Text("0x%p", addr); + + if (ImGui::IsItemClicked()) { + ImGui::SetClipboardText((std::stringstream{} << std::hex << addr).str().c_str()); + } + } + ImGui::TreePop(); + } + ImGui::TreePop(); } diff --git a/src/mods/tools/ObjectExplorer.hpp b/src/mods/tools/ObjectExplorer.hpp index 32a5b9b31..5b52c0f8d 100644 --- a/src/mods/tools/ObjectExplorer.hpp +++ b/src/mods/tools/ObjectExplorer.hpp @@ -260,6 +260,18 @@ class ObjectExplorer : public Tool { std::vector> m_frame_jobs{}; struct HookedMethod { + enum class SortCallersMethod : uint8_t { + NONE, + CALL_COUNT, + METHOD_NAME + }; + + static inline constexpr std::array s_sort_callers_names { + "None", + "Call Count", + "Method Name" + }; + std::string name{}; sdk::REMethodDefinition* method{nullptr}; uintptr_t jitted_function{}; @@ -267,6 +279,7 @@ class ObjectExplorer : public Tool { bool skip{false}; size_t hook_id{}; uint32_t call_count{}; + SortCallersMethod sort_callers_method{SortCallersMethod::NONE}; std::unordered_set return_addresses{}; std::unordered_map return_addresses_to_methods{}; From 0721d018ec0cd2d5677c23133c32fc023ee8908e Mon Sep 17 00:00:00 2001 From: praydog Date: Thu, 28 Mar 2024 15:41:04 -0700 Subject: [PATCH 12/33] HookManager: Modify to use thread based storage, safer stack behavior --- src/HookManager.cpp | 181 +++++++++++++++++++++++++++++--------------- src/HookManager.hpp | 43 ++++++++++- 2 files changed, 159 insertions(+), 65 deletions(-) diff --git a/src/HookManager.cpp b/src/HookManager.cpp index f6a15a196..2be6f38c6 100644 --- a/src/HookManager.cpp +++ b/src/HookManager.cpp @@ -57,11 +57,16 @@ HookManager::HookedFn::~HookedFn() { } HookManager::PreHookResult HookManager::HookedFn::on_pre_hook() { + std::shared_lock _{this->access_mux}; + auto any_skipped = false; + auto storage = get_storage(this); + const auto ret_addr_pre = storage->ret_addr_pre; + for (const auto& cb : cbs) { if (cb.pre_fn) { - if (cb.pre_fn(args, arg_tys, ret_addr_pre) == PreHookResult::SKIP_ORIGINAL) { + if (cb.pre_fn(storage->args_impl, arg_tys, ret_addr_pre) == PreHookResult::SKIP_ORIGINAL) { any_skipped = true; } } @@ -71,6 +76,12 @@ HookManager::PreHookResult HookManager::HookedFn::on_pre_hook() { } void HookManager::HookedFn::on_post_hook() { + std::shared_lock _{this->access_mux}; + + auto storage = get_storage(this); + auto& ret_val = storage->ret_val; + auto& ret_addr = storage->ret_addr; + for (const auto& cb : cbs) { if (cb.post_fn) { cb.post_fn(ret_val, ret_ty, ret_addr); @@ -79,7 +90,7 @@ void HookManager::HookedFn::on_post_hook() { } void HookManager::create_jitted_facilitator(std::unique_ptr& hook, sdk::REMethodDefinition* fn, std::function hook_initialization, std::function hook_create) { - auto& args = hook->args; + auto& args = hook->get_storage(hook.get())->args_impl; auto& arg_tys = hook->arg_tys; auto& fn_hook = hook->fn_hook; @@ -96,33 +107,42 @@ void HookManager::create_jitted_facilitator(std::unique_ptr