diff --git a/CMakeLists.txt b/CMakeLists.txt index b423f91e6..d14f29826 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -55,6 +55,7 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP") set(ASMJIT_STATIC ON CACHE BOOL "" FORCE) set(DYNAMIC_LOADER ON CACHE BOOL "" FORCE) # OpenXR set(BUILD_TOOLS OFF CACHE BOOL "" FORCE) # DirectXTK +set(SAFETYHOOK_FETCH_ZYDIS ON) if ("${CMAKE_BUILD_TYPE}" MATCHES "Release") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /MT") @@ -113,6 +114,16 @@ FetchContent_Declare( ) FetchContent_MakeAvailable(directxtk12) +message(STATUS "Fetching safetyhook (44200343bf803f78862426e301e9382e5b28ea2c)...") +FetchContent_Declare( + safetyhook + GIT_REPOSITORY + https://github.com/cursey/safetyhook + GIT_TAG + 44200343bf803f78862426e301e9382e5b28ea2c +) +FetchContent_MakeAvailable(safetyhook) + message(STATUS "Fetching bddisasm (v1.34.10)...") FetchContent_Declare( bddisasm @@ -490,9 +501,13 @@ set(CMKR_TARGET utility) set(utility_SOURCES "") list(APPEND utility_SOURCES + "shared/utility/Exceptions.cpp" "shared/utility/FunctionHook.cpp" + "shared/utility/FunctionHookMinHook.cpp" "shared/utility/Relocate.cpp" + "shared/utility/Exceptions.hpp" "shared/utility/FunctionHook.hpp" + "shared/utility/FunctionHookMinHook.hpp" "shared/utility/Relocate.hpp" ) @@ -520,6 +535,7 @@ target_compile_options(utility PUBLIC target_link_libraries(utility PUBLIC spdlog minhook + safetyhook kananlib ) @@ -12804,7 +12820,7 @@ if(REF_BUILD_FRAMEWORK AND CMAKE_SIZEOF_VOID_P EQUAL 8) # build-framework source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${example_plugin_SOURCES}) target_compile_features(example_plugin PUBLIC - cxx_std_20 + cxx_std_23 ) target_include_directories(example_plugin PUBLIC @@ -12859,7 +12875,7 @@ if(REF_BUILD_FRAMEWORK AND CMAKE_SIZEOF_VOID_P EQUAL 8) # build-framework source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${weapon_stay_big_plugin_SOURCES}) target_compile_features(weapon_stay_big_plugin PUBLIC - cxx_std_20 + cxx_std_23 ) target_include_directories(weapon_stay_big_plugin PUBLIC diff --git a/cmake.toml b/cmake.toml index c6d34a4af..d377ca8b2 100644 --- a/cmake.toml +++ b/cmake.toml @@ -14,6 +14,7 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP") set(ASMJIT_STATIC ON CACHE BOOL "" FORCE) set(DYNAMIC_LOADER ON CACHE BOOL "" FORCE) # OpenXR set(BUILD_TOOLS OFF CACHE BOOL "" FORCE) # DirectXTK +set(SAFETYHOOK_FETCH_ZYDIS ON) if ("${CMAKE_BUILD_TYPE}" MATCHES "Release") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /MT") @@ -111,6 +112,9 @@ include-directories = [ ] condition = "build-framework-dependencies" +[fetch-content.safetyhook] +git = "https://github.com/cursey/safetyhook" +tag = "44200343bf803f78862426e301e9382e5b28ea2c" [target.imgui] type = "static" @@ -182,6 +186,7 @@ compile-features = ["cxx_std_23"] link-libraries = [ "spdlog", "minhook", + "safetyhook", "kananlib" ] @@ -342,7 +347,7 @@ type = "game" [template.plugin] type = "shared" include-directories = ["include/"] -compile-features = ["cxx_std_20"] +compile-features = ["cxx_std_23"] condition = "build-framework" [template.plugin.properties] diff --git a/shared/sdk/REContext.cpp b/shared/sdk/REContext.cpp index 21e583a73..898201c64 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; } @@ -445,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) { @@ -458,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) { @@ -471,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) { @@ -484,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) { @@ -497,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) { @@ -510,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) { @@ -523,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) { @@ -536,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) { @@ -550,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) { @@ -563,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) { 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()); 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/shared/utility/FunctionHook.cpp b/shared/utility/FunctionHook.cpp index 514e0e69f..9b007afbc 100644 --- a/shared/utility/FunctionHook.cpp +++ b/shared/utility/FunctionHook.cpp @@ -1,76 +1,66 @@ #include -#include +#include #include "FunctionHook.hpp" using namespace std; -bool g_isMinHookInitialized{ false }; - FunctionHook::FunctionHook(Address target, Address destination) - : m_target{ 0 }, - m_destination{ 0 }, - m_original{ 0 } + : m_target{ target }, + m_destination{ destination } { spdlog::info("Attempting to hook {:p}->{:p}", target.ptr(), destination.ptr()); - // Initialize MinHook if it hasn't been already. - if (!g_isMinHookInitialized && MH_Initialize() == MH_OK) { - g_isMinHookInitialized = true; + try { + auto expect = safetyhook::InlineHook::create(safetyhook::Allocator::global(), m_target, m_destination); + + if (!expect) { + std::string error = ""; + switch (expect.error().type) { + case safetyhook::InlineHook::Error::BAD_ALLOCATION: + error = "bad allocation"; + break; + case safetyhook::InlineHook::Error::FAILED_TO_DECODE_INSTRUCTION: + error = "failed to decode instruction"; + break; + case safetyhook::InlineHook::Error::SHORT_JUMP_IN_TRAMPOLINE: + error = "short jump in trampoline"; + break; + case safetyhook::InlineHook::Error::IP_RELATIVE_INSTRUCTION_OUT_OF_RANGE: + error = "IP relative instruction out of range"; + break; + case safetyhook::InlineHook::Error::UNSUPPORTED_INSTRUCTION_IN_TRAMPOLINE: + error = "unsupported instruction in trampoline"; + break; + case safetyhook::InlineHook::Error::FAILED_TO_UNPROTECT: + error = "failed to unprotect memory"; + break; + case safetyhook::InlineHook::Error::NOT_ENOUGH_SPACE: + error = "not enough space"; + break; + default: + error = std::format("unknown error {}", (int32_t)expect.error().type); + break; + }; + + spdlog::error("Failed to hook {:x}: {}", m_target, error); + return; + } + + m_inline_hook = std::move(*expect); + } catch (const std::exception& e) { + spdlog::error("Failed to hook {:x}: {}", m_target, e.what()); + } catch (...) { + spdlog::error("Failed to hook {:x}: unknown exception", m_target); } - // Create the hook. Call create afterwards to prevent race conditions accessing FunctionHook before it leaves its constructor. - if (auto status = MH_CreateHook(target.as(), destination.as(), (LPVOID*)&m_original); status == MH_OK) { - m_target = target; - m_destination = destination; - - spdlog::info("Hook init successful {:p}->{:p}", target.ptr(), destination.ptr()); - } - else { - spdlog::error("Failed to hook {:p}: {}", target.ptr(), MH_StatusToString(status)); + if (m_inline_hook) { + spdlog::info("Hooked {:x}->{:x}", m_target, m_destination); + } else { + spdlog::error("Failed to hook {:x}", m_target); } } FunctionHook::~FunctionHook() { - remove(); -} - -bool FunctionHook::create() { - if (m_target == 0 || m_destination == 0 || m_original == 0) { - spdlog::error("FunctionHook not initialized"); - return false; - } - - if (auto status = MH_EnableHook((LPVOID)m_target); status != MH_OK) { - m_original = 0; - m_destination = 0; - m_target = 0; - - spdlog::error("Failed to hook {:x}: {}", m_target, MH_StatusToString(status)); - return false; - } - - spdlog::info("Hooked {:x}->{:x}", m_target, m_destination); - return true; } - -bool FunctionHook::remove() { - // Don't try to remove invalid hooks. - if (m_original == 0) { - return true; - } - - // Disable then remove the hook. - if (MH_DisableHook((LPVOID)m_target) != MH_OK || - MH_RemoveHook((LPVOID)m_target) != MH_OK) { - return false; - } - - // Invalidate the members. - m_target = 0; - m_destination = 0; - m_original = 0; - - return true; -} \ No newline at end of file diff --git a/shared/utility/FunctionHook.hpp b/shared/utility/FunctionHook.hpp index 1bdcc7456..9813139b0 100644 --- a/shared/utility/FunctionHook.hpp +++ b/shared/utility/FunctionHook.hpp @@ -5,6 +5,8 @@ #include +#include + class FunctionHook { public: FunctionHook() = delete; @@ -13,30 +15,25 @@ class FunctionHook { FunctionHook(Address target, Address destination); virtual ~FunctionHook(); - bool create(); - - // Called automatically by the destructor, but you can call it explicitly - // if you need to remove the hook. - bool remove(); - auto get_original() const { - return m_original; + return m_inline_hook.trampoline().address(); } template T* get_original() const { - return (T*)m_original; + return m_inline_hook.original(); } auto is_valid() const { - return m_original != 0; + return m_inline_hook.operator bool(); } FunctionHook& operator=(const FunctionHook& other) = delete; FunctionHook& operator=(FunctionHook&& other) = delete; private: + SafetyHookInline m_inline_hook; + uintptr_t m_target{ 0 }; uintptr_t m_destination{ 0 }; - uintptr_t m_original{ 0 }; }; \ No newline at end of file diff --git a/shared/utility/FunctionHookMinHook.cpp b/shared/utility/FunctionHookMinHook.cpp new file mode 100644 index 000000000..2718e7c59 --- /dev/null +++ b/shared/utility/FunctionHookMinHook.cpp @@ -0,0 +1,76 @@ +#include +#include + +#include "FunctionHookMinHook.hpp" + +using namespace std; + + +bool g_isMinHookInitialized{ false }; + +FunctionHookMinHook::FunctionHookMinHook(Address target, Address destination) + : m_target{ 0 }, + m_destination{ 0 }, + m_original{ 0 } +{ + spdlog::info("Attempting to hook {:p}->{:p}", target.ptr(), destination.ptr()); + + // Initialize MinHook if it hasn't been already. + if (!g_isMinHookInitialized && MH_Initialize() == MH_OK) { + g_isMinHookInitialized = true; + } + + // Create the hook. Call create afterwards to prevent race conditions accessing FunctionHookMinHook before it leaves its constructor. + if (auto status = MH_CreateHook(target.as(), destination.as(), (LPVOID*)&m_original); status == MH_OK) { + m_target = target; + m_destination = destination; + + spdlog::info("Hook init successful {:p}->{:p}", target.ptr(), destination.ptr()); + } + else { + spdlog::error("Failed to hook {:p}: {}", target.ptr(), MH_StatusToString(status)); + } +} + +FunctionHookMinHook::~FunctionHookMinHook() { + remove(); +} + +bool FunctionHookMinHook::create() { + if (m_target == 0 || m_destination == 0 || m_original == 0) { + spdlog::error("FunctionHookMinHook not initialized"); + return false; + } + + if (auto status = MH_EnableHook((LPVOID)m_target); status != MH_OK) { + m_original = 0; + m_destination = 0; + m_target = 0; + + spdlog::error("Failed to hook {:x}: {}", m_target, MH_StatusToString(status)); + return false; + } + + spdlog::info("Hooked {:x}->{:x}", m_target, m_destination); + return true; +} + +bool FunctionHookMinHook::remove() { + // Don't try to remove invalid hooks. + if (m_original == 0) { + return true; + } + + // Disable then remove the hook. + if (MH_DisableHook((LPVOID)m_target) != MH_OK || + MH_RemoveHook((LPVOID)m_target) != MH_OK) { + return false; + } + + // Invalidate the members. + m_target = 0; + m_destination = 0; + m_original = 0; + + return true; +} \ No newline at end of file diff --git a/shared/utility/FunctionHookMinHook.hpp b/shared/utility/FunctionHookMinHook.hpp new file mode 100644 index 000000000..b9a48d83b --- /dev/null +++ b/shared/utility/FunctionHookMinHook.hpp @@ -0,0 +1,42 @@ +#pragma once + +#include +#include + +#include + +class FunctionHookMinHook { +public: + FunctionHookMinHook() = delete; + FunctionHookMinHook(const FunctionHookMinHook& other) = delete; + FunctionHookMinHook(FunctionHookMinHook&& other) = delete; + FunctionHookMinHook(Address target, Address destination); + virtual ~FunctionHookMinHook(); + + bool create(); + + // Called automatically by the destructor, but you can call it explicitly + // if you need to remove the hook. + bool remove(); + + auto get_original() const { + return m_original; + } + + template + T* get_original() const { + return (T*)m_original; + } + + auto is_valid() const { + return m_original != 0; + } + + FunctionHookMinHook& operator=(const FunctionHookMinHook& other) = delete; + FunctionHookMinHook& operator=(FunctionHookMinHook&& other) = delete; + +private: + uintptr_t m_target{ 0 }; + uintptr_t m_destination{ 0 }; + uintptr_t m_original{ 0 }; +}; \ No newline at end of file diff --git a/src/DInputHook.cpp b/src/DInputHook.cpp index 875f1bcb8..2e96dd093 100644 --- a/src/DInputHook.cpp +++ b/src/DInputHook.cpp @@ -81,7 +81,7 @@ bool DInputHook::hook() { // Hook them. m_get_device_state_hook = make_unique(get_device_state, (uintptr_t)&DInputHook::get_device_state); - return m_get_device_state_hook->create(); + return m_get_device_state_hook->is_valid(); } HRESULT DInputHook::get_device_state_internal(IDirectInputDevice* device, DWORD size, LPVOID data) { 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); diff --git a/src/HookManager.cpp b/src/HookManager.cpp index f6a15a196..16186561b 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