diff --git a/.github/workflows/dev-release.yml b/.github/workflows/dev-release.yml index c0c15267e..131d9280f 100644 --- a/.github/workflows/dev-release.yml +++ b/.github/workflows/dev-release.yml @@ -10,9 +10,10 @@ jobs: target: [RE2, RE2_TDB66, RE3, RE3_TDB67, RE4, RE7, RE7_TDB49, RE8, DMC5, MHRISE, SF6, DD2] steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 with: submodules: recursive + fetch-depth: 0 - name: Configure CMake run: cmake -S ${{github.workspace}} -B ${{github.workspace}}/build -G "Visual Studio 17 2022" -A x64 -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DDEVELOPER_MODE=ON @@ -34,7 +35,7 @@ jobs: 7z rn ${{github.workspace}}/${{matrix.target}}.zip scripts reframework/autorun - name: Upload artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 with: name: ${{matrix.target}} path: ${{github.workspace}}/${{matrix.target}}.zip diff --git a/.gitignore b/.gitignore index abb2d0232..743ef9a33 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ src/sdk/generated cmake-build* .idea/ build_vs2019_devmode_DMC5.bat +src/CommitHash.autogenerated \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 20e9dd340..4892a2279 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 @@ -525,12 +536,15 @@ 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/Profiler.hpp" "shared/utility/Relocate.hpp" "shared/utility/ScopeGuard.hpp" + "shared/utility/Profiler.hpp" ) list(APPEND utility_SOURCES @@ -557,6 +571,7 @@ target_compile_options(utility PUBLIC target_link_libraries(utility PUBLIC spdlog minhook + safetyhook kananlib ) @@ -2454,6 +2469,12 @@ if(REF_BUILD_FRAMEWORK AND CMAKE_SIZEOF_VOID_P EQUAL 8) # build-framework "${CMAKE_BINARY_DIR}/lib/${CMKR_TARGET}" ) + add_custom_command( + TARGET ${CMKR_TARGET} PRE_BUILD + COMMAND ${CMAKE_COMMAND} -E echo "Generating commit hash..." + COMMAND ${CMAKE_COMMAND} -E chdir ${CMAKE_SOURCE_DIR} ${CMAKE_SOURCE_DIR}/MakeCommitHash.bat + ) + unset(CMKR_TARGET) unset(CMKR_SOURCES) endif() @@ -2658,6 +2679,12 @@ if(REF_BUILD_FRAMEWORK AND CMAKE_SIZEOF_VOID_P EQUAL 8) # build-framework "${CMAKE_BINARY_DIR}/lib/${CMKR_TARGET}" ) + add_custom_command( + TARGET ${CMKR_TARGET} PRE_BUILD + COMMAND ${CMAKE_COMMAND} -E echo "Generating commit hash..." + COMMAND ${CMAKE_COMMAND} -E chdir ${CMAKE_SOURCE_DIR} ${CMAKE_SOURCE_DIR}/MakeCommitHash.bat + ) + unset(CMKR_TARGET) unset(CMKR_SOURCES) endif() @@ -4553,6 +4580,12 @@ if(REF_BUILD_FRAMEWORK AND CMAKE_SIZEOF_VOID_P EQUAL 8) # build-framework "${CMAKE_BINARY_DIR}/lib/${CMKR_TARGET}" ) + add_custom_command( + TARGET ${CMKR_TARGET} PRE_BUILD + COMMAND ${CMAKE_COMMAND} -E echo "Generating commit hash..." + COMMAND ${CMAKE_COMMAND} -E chdir ${CMAKE_SOURCE_DIR} ${CMAKE_SOURCE_DIR}/MakeCommitHash.bat + ) + unset(CMKR_TARGET) unset(CMKR_SOURCES) endif() @@ -4757,6 +4790,12 @@ if(REF_BUILD_FRAMEWORK AND CMAKE_SIZEOF_VOID_P EQUAL 8) # build-framework "${CMAKE_BINARY_DIR}/lib/${CMKR_TARGET}" ) + add_custom_command( + TARGET ${CMKR_TARGET} PRE_BUILD + COMMAND ${CMAKE_COMMAND} -E echo "Generating commit hash..." + COMMAND ${CMAKE_COMMAND} -E chdir ${CMAKE_SOURCE_DIR} ${CMAKE_SOURCE_DIR}/MakeCommitHash.bat + ) + unset(CMKR_TARGET) unset(CMKR_SOURCES) endif() @@ -4961,6 +5000,12 @@ if(REF_BUILD_FRAMEWORK AND CMAKE_SIZEOF_VOID_P EQUAL 8) # build-framework "${CMAKE_BINARY_DIR}/lib/${CMKR_TARGET}" ) + add_custom_command( + TARGET ${CMKR_TARGET} PRE_BUILD + COMMAND ${CMAKE_COMMAND} -E echo "Generating commit hash..." + COMMAND ${CMAKE_COMMAND} -E chdir ${CMAKE_SOURCE_DIR} ${CMAKE_SOURCE_DIR}/MakeCommitHash.bat + ) + unset(CMKR_TARGET) unset(CMKR_SOURCES) endif() @@ -7703,6 +7748,12 @@ if(REF_BUILD_FRAMEWORK AND CMAKE_SIZEOF_VOID_P EQUAL 8) # build-framework "${CMAKE_BINARY_DIR}/lib/${CMKR_TARGET}" ) + add_custom_command( + TARGET ${CMKR_TARGET} PRE_BUILD + COMMAND ${CMAKE_COMMAND} -E echo "Generating commit hash..." + COMMAND ${CMAKE_COMMAND} -E chdir ${CMAKE_SOURCE_DIR} ${CMAKE_SOURCE_DIR}/MakeCommitHash.bat + ) + unset(CMKR_TARGET) unset(CMKR_SOURCES) endif() @@ -7907,6 +7958,12 @@ if(REF_BUILD_FRAMEWORK AND CMAKE_SIZEOF_VOID_P EQUAL 8) # build-framework "${CMAKE_BINARY_DIR}/lib/${CMKR_TARGET}" ) + add_custom_command( + TARGET ${CMKR_TARGET} PRE_BUILD + COMMAND ${CMAKE_COMMAND} -E echo "Generating commit hash..." + COMMAND ${CMAKE_COMMAND} -E chdir ${CMAKE_SOURCE_DIR} ${CMAKE_SOURCE_DIR}/MakeCommitHash.bat + ) + unset(CMKR_TARGET) unset(CMKR_SOURCES) endif() @@ -8958,6 +9015,12 @@ if(REF_BUILD_FRAMEWORK AND CMAKE_SIZEOF_VOID_P EQUAL 8) # build-framework "${CMAKE_BINARY_DIR}/lib/${CMKR_TARGET}" ) + add_custom_command( + TARGET ${CMKR_TARGET} PRE_BUILD + COMMAND ${CMAKE_COMMAND} -E echo "Generating commit hash..." + COMMAND ${CMAKE_COMMAND} -E chdir ${CMAKE_SOURCE_DIR} ${CMAKE_SOURCE_DIR}/MakeCommitHash.bat + ) + unset(CMKR_TARGET) unset(CMKR_SOURCES) endif() @@ -10007,6 +10070,12 @@ if(REF_BUILD_FRAMEWORK AND CMAKE_SIZEOF_VOID_P EQUAL 8) # build-framework "${CMAKE_BINARY_DIR}/lib/${CMKR_TARGET}" ) + add_custom_command( + TARGET ${CMKR_TARGET} PRE_BUILD + COMMAND ${CMAKE_COMMAND} -E echo "Generating commit hash..." + COMMAND ${CMAKE_COMMAND} -E chdir ${CMAKE_SOURCE_DIR} ${CMAKE_SOURCE_DIR}/MakeCommitHash.bat + ) + unset(CMKR_TARGET) unset(CMKR_SOURCES) endif() @@ -11058,6 +11127,12 @@ if(REF_BUILD_FRAMEWORK AND CMAKE_SIZEOF_VOID_P EQUAL 8) # build-framework "${CMAKE_BINARY_DIR}/lib/${CMKR_TARGET}" ) + add_custom_command( + TARGET ${CMKR_TARGET} PRE_BUILD + COMMAND ${CMAKE_COMMAND} -E echo "Generating commit hash..." + COMMAND ${CMAKE_COMMAND} -E chdir ${CMAKE_SOURCE_DIR} ${CMAKE_SOURCE_DIR}/MakeCommitHash.bat + ) + unset(CMKR_TARGET) unset(CMKR_SOURCES) endif() @@ -12109,6 +12184,12 @@ if(REF_BUILD_FRAMEWORK AND CMAKE_SIZEOF_VOID_P EQUAL 8) # build-framework "${CMAKE_BINARY_DIR}/lib/${CMKR_TARGET}" ) + add_custom_command( + TARGET ${CMKR_TARGET} PRE_BUILD + COMMAND ${CMAKE_COMMAND} -E echo "Generating commit hash..." + COMMAND ${CMAKE_COMMAND} -E chdir ${CMAKE_SOURCE_DIR} ${CMAKE_SOURCE_DIR}/MakeCommitHash.bat + ) + unset(CMKR_TARGET) unset(CMKR_SOURCES) endif() @@ -13160,6 +13241,12 @@ if(REF_BUILD_FRAMEWORK AND CMAKE_SIZEOF_VOID_P EQUAL 8) # build-framework "${CMAKE_BINARY_DIR}/lib/${CMKR_TARGET}" ) + add_custom_command( + TARGET ${CMKR_TARGET} PRE_BUILD + COMMAND ${CMAKE_COMMAND} -E echo "Generating commit hash..." + COMMAND ${CMAKE_COMMAND} -E chdir ${CMAKE_SOURCE_DIR} ${CMAKE_SOURCE_DIR}/MakeCommitHash.bat + ) + unset(CMKR_TARGET) unset(CMKR_SOURCES) endif() @@ -13201,7 +13288,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 @@ -13256,7 +13343,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/CMakePresets.json b/CMakePresets.json index 04be9663f..d6030a6a4 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -1,18 +1,6 @@ { "version": 2, "configurePresets": [ - { - "name": "vs2019", - "displayName": "Visual Studio 2019 - amd64", - "description": "Using compilers for Visual Studio 16 2019 (x64 architecture)", - "generator": "Visual Studio 16 2019", - "toolset": "host=x64", - "architecture": "x64", - "binaryDir": "${sourceDir}/build", - "cacheVariables": { - "CMAKE_INSTALL_PREFIX": "${sourceDir}/out/install/" - } - }, { "name": "vs2022", "displayName": "Visual Studio 2022 - amd64", @@ -27,20 +15,6 @@ } ], "buildPresets": [ - { - "name": "Release 2019", - "description": "", - "displayName": "Release", - "configuration": "Release", - "configurePreset": "vs2019" - }, - { - "name": "RelWithDebInfo 2019", - "description": "", - "displayName": "RelWithDebInfo", - "configuration": "RelWithDebInfo", - "configurePreset": "vs2019" - }, { "name": "Release 2022", "description": "", diff --git a/COMPILING.md b/COMPILING.md new file mode 100644 index 000000000..c1e4816de --- /dev/null +++ b/COMPILING.md @@ -0,0 +1,58 @@ +# Compiling REFramework + +## Necessary prerequisites + +A C++23 compatible compiler is required. Visual Studio 2022 is recommended. Compilers other than MSVC have not been tested. + +CMake is required. + +## Compiling + +### Clone the repository + +#### SSH +``` +git clone git@github.com:praydog/REFramework.git +``` + +#### HTTPS +``` +git clone https://github.com/praydog/REFramework +``` + +### Initialize the submodules + +``` +git submodule update --init --recursive +``` + +### Set up CMake + +#### Command line + +``` +cmake -S . -B build ./build -G "Visual Studio 17 2022" -A x64 -DCMAKE_BUILD_TYPE=Release +cmake --build ./build --config Release +``` + +Keep in mind that not supplying a target will build multiple targets, when you may only need one of them. + +For example, to build only the RE2 target, you can use the following command: + +``` +cmake --build ./build --config Release --target RE2 +``` + +#### VSCode + +1. Install the [CMake Tools](https://marketplace.visualstudio.com/items?itemName=ms-vscode.cmake-tools) extension +2. Open the REFramework folder in VSCode +3. Press `Ctrl+Shift+P` and select `CMake: Configure` +4. When "Select a kit" appears, select `Visual Studio Community 2022 Release - amd64` +5. Select the desired build config (usually `Release` or `RelWithDebInfo`) near the bottom of the window +6. Select the desired build target near the bottom of the window, otherwise all targets will be built +6. You should now be able to compile REFramework by pressing `Ctrl+Shift+P` and selecting `CMake: Build` or by pressing `F7` + +#### Batch script + +In the root of the repository there is a `build_vs2022.bat` script that will build all targets in Release mode. \ No newline at end of file diff --git a/MakeCommitHash.bat b/MakeCommitHash.bat new file mode 100644 index 000000000..c4d3f5f2a --- /dev/null +++ b/MakeCommitHash.bat @@ -0,0 +1,41 @@ +@echo off + +IF EXIST "src/CommitHash.autogenerated" ( +echo The file "src/CommitHash.autogenerated" already exists. +exit /b 0 +) + +FOR /F "tokens=*" %%g IN ('git rev-parse HEAD') DO (SET REF_COMMIT_HASH=%%g) + +FOR /F "tokens=*" %%t IN ('git describe --tags --abbrev^=0') DO (SET REF_TAG=%%t) + +FOR /F "tokens=*" %%c IN ('git describe --tags --long') DO ( +FOR /F "tokens=1,2 delims=-" %%a IN ("%%c") DO ( +SET REF_TAG_LONG=%%a +SET REF_COMMITS_PAST_TAG=%%b +) +) + +FOR /F "tokens=*" %%b IN ('git rev-parse --abbrev-ref HEAD') DO (SET REF_BRANCH=%%b) + +FOR /F "tokens=*" %%n IN ('git rev-list --count HEAD') DO (SET REF_TOTAL_COMMITS=%%n) + +FOR /F "tokens=2 delims==" %%a IN ('wmic OS get localdatetime /value') DO ( +SET datetime=%%a +) + +SET year=%datetime:~0,4% +SET month=%datetime:~4,2% +SET day=%datetime:~6,2% +SET hour=%datetime:~8,2% +SET minute=%datetime:~10,2% + +echo #pragma once > src/CommitHash.autogenerated +echo #define REF_COMMIT_HASH "%REF_COMMIT_HASH%" >> src/CommitHash.autogenerated +echo #define REF_TAG "%REF_TAG%" >> src/CommitHash.autogenerated +echo #define REF_TAG_LONG "%REF_TAG_LONG%" >> src/CommitHash.autogenerated +echo #define REF_COMMITS_PAST_TAG %REF_COMMITS_PAST_TAG% >> src/CommitHash.autogenerated +echo #define REF_BRANCH "%REF_BRANCH%" >> src/CommitHash.autogenerated +echo #define REF_TOTAL_COMMITS %REF_TOTAL_COMMITS% >> src/CommitHash.autogenerated +echo #define REF_BUILD_DATE "%day%.%month%.%year%" >> src/CommitHash.autogenerated +echo #define REF_BUILD_TIME "%hour%:%minute%" >> src/CommitHash.autogenerated \ No newline at end of file diff --git a/README.md b/README.md index a52d46d29..ea5a844b1 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,7 @@ Supports both DirectX 11 and DirectX 12. * Devil May Cry 5 * Street Fighter 6 * Monster Hunter Rise +* Dragon's Dogma 2 ## Thanks [SkacikPL](https://github.com/SkacikPL) for originally creating the Manual Flashlight mod. diff --git a/cmake.toml b/cmake.toml index 89c56c3a1..15a54b379 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") @@ -120,6 +121,9 @@ include-directories = [ ] condition = "build-framework-dependencies" +[fetch-content.safetyhook] +git = "https://github.com/cursey/safetyhook" +tag = "44200343bf803f78862426e301e9382e5b28ea2c" [target.imgui] type = "static" @@ -191,6 +195,7 @@ compile-features = ["cxx_std_23"] link-libraries = [ "spdlog", "minhook", + "safetyhook", "kananlib" ] @@ -242,6 +247,13 @@ link-libraries = [ "DirectXTK12", "pdperfmod" ] +cmake-after=""" +add_custom_command( + TARGET ${CMKR_TARGET} PRE_BUILD + COMMAND ${CMAKE_COMMAND} -E echo "Generating commit hash..." + COMMAND ${CMAKE_COMMAND} -E chdir ${CMAKE_SOURCE_DIR} ${CMAKE_SOURCE_DIR}/MakeCommitHash.bat +) +""" [template.game.properties] OUTPUT_NAME = "dinput8" @@ -352,7 +364,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/dependencies/CMakeLists.txt b/dependencies/CMakeLists.txt deleted file mode 100644 index e69de29bb..000000000 diff --git a/dependencies/spdlog b/dependencies/spdlog index 069a2e8fc..7c02e204c 160000 --- a/dependencies/spdlog +++ b/dependencies/spdlog @@ -1 +1 @@ -Subproject commit 069a2e8fc947f63855d770fdc3c3eb427f19988f +Subproject commit 7c02e204c92545f869e2f04edaab1f19fe8b19fd 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..2df8d65f9 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()); @@ -483,7 +497,9 @@ reframework::InvokeRet sdk::REMethodDefinition::invoke(void* object, const std:: if (num_params != args.size()) { //throw std::runtime_error("Invalid number of arguments"); - spdlog::warn("Invalid number of arguments passed to REMethodDefinition::invoke for {}", get_name()); + const auto declaring_type = get_declaring_type(); + const auto decltype_name = declaring_type != nullptr ? declaring_type->get_full_name() : "unknownclass"; + spdlog::warn("Invalid number of arguments passed to REMethodDefinition::invoke for {}.{}", decltype_name, get_name()); return reframework::InvokeRet{}; } @@ -537,7 +553,9 @@ reframework::InvokeRet sdk::REMethodDefinition::invoke(void* object, const std:: // exception pointer if (context->unkPtr->unkPtr != nullptr) { - spdlog::error("Internal game exception thrown in REMethodDefinition::invoke for {}", get_name()); + const auto declaring_type = get_declaring_type(); + const auto decltype_name = declaring_type != nullptr ? declaring_type->get_full_name() : "unknownclass"; + spdlog::error("Internal game exception thrown in REMethodDefinition::invoke for {}.{}", decltype_name, get_name()); const auto exception_managed_object = (::REManagedObject*)context->unkPtr->unkPtr; @@ -559,7 +577,10 @@ reframework::InvokeRet sdk::REMethodDefinition::invoke(void* object, const std:: context->unkPtr->unkPtr = nullptr; } } catch (sdk::VMContext::Exception&) { - spdlog::error("Exception thrown in REMethodDefinition::invoke for {}", get_name()); + const auto declaring_type = get_declaring_type(); + const auto decltype_name = declaring_type != nullptr ? declaring_type->get_full_name() : "unknownclass"; + + spdlog::error("Exception thrown in REMethodDefinition::invoke for {}.{}", decltype_name, get_name()); context->cleanup_after_exception(scoped_translator.get_prev_reference_count()); memset(&out, 0, sizeof(out)); 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..fb3e640f3 100644 --- a/shared/utility/FunctionHook.cpp +++ b/shared/utility/FunctionHook.cpp @@ -1,76 +1,77 @@ #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; - } - - // 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)); - } } FunctionHook::~FunctionHook() { - remove(); } bool FunctionHook::create() { - if (m_target == 0 || m_destination == 0 || m_original == 0) { + if (m_target == 0 || m_destination == 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)); + try { + m_inline_hook = safetyhook::InlineHook::create(m_target, m_destination); + + if (!m_inline_hook) { + std::string error = ""; + switch (m_inline_hook.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)m_inline_hook.error().type); + break; + }; + + spdlog::error("Failed to hook {:x}: {}", m_target, error); + return false; + } + } catch (const std::exception& e) { + spdlog::error("Failed to hook {:x}: {}", m_target, e.what()); + return false; + } catch (...) { + spdlog::error("Failed to hook {:x}: unknown exception", m_target); 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) { + if (m_inline_hook) { + spdlog::info("Hooked {:x}->{:x}", m_target, m_destination); + } else { + spdlog::error("Failed to hook {:x}", m_target); 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..01b7bf955 100644 --- a/shared/utility/FunctionHook.hpp +++ b/shared/utility/FunctionHook.hpp @@ -5,6 +5,8 @@ #include +#include + class FunctionHook { public: FunctionHook() = delete; @@ -15,28 +17,25 @@ class 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 && m_inline_hook->operator bool(); } FunctionHook& operator=(const FunctionHook& other) = delete; FunctionHook& operator=(FunctionHook&& other) = delete; private: + std::expected 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/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..22d3624e2 100644 --- a/src/HookManager.cpp +++ b/src/HookManager.cpp @@ -57,29 +57,94 @@ 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); + + if (storage->pre_depth == 0) { + // afaik, shared locks are not reentrant, so only lock it + // if we're not already in a pre-hook. + this->access_mux.lock_shared(); + } else if (!storage->pre_warned_recursion) { + const auto tid = std::hash{}(std::this_thread::get_id()); + const auto declaring_type = fn_def->get_declaring_type(); + const auto decltype_name = declaring_type != nullptr ? declaring_type->get_full_name() : "unknownclass"; + spdlog::warn("[HookManager] (Pre) Recursive hook detected for '{}.{}' (thread ID: {:x})", decltype_name, fn_def->get_name(), tid); + storage->pre_warned_recursion = true; + } + + if (storage->overall_depth > 0 && !storage->overall_warned_recursion) { + const auto tid = std::hash{}(std::this_thread::get_id()); + const auto declaring_type = fn_def->get_declaring_type(); + const auto decltype_name = declaring_type != nullptr ? declaring_type->get_full_name() : "unknownclass"; + spdlog::warn("[HookManager] (Overall) '{}.{}' appears to be calling itself in some way (thread ID: {:x})", decltype_name, fn_def->get_name(), tid); + storage->overall_warned_recursion = true; + } + + ++storage->pre_depth; + 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; } } - } + } + + ++storage->overall_depth; + --storage->pre_depth; + + if (storage->pre_depth == 0) { + this->access_mux.unlock_shared(); + } return any_skipped ? PreHookResult::SKIP_ORIGINAL : PreHookResult::CALL_ORIGINAL; } void HookManager::HookedFn::on_post_hook() { + //std::shared_lock _{this->access_mux}; + + auto storage = get_storage(this); + + if (storage->post_depth == 0) { + // afaik, shared locks are not reentrant, so only lock it + // if we're not already in a post-hook. + this->access_mux.lock_shared(); + } else if (!storage->post_warned_recursion) { + const auto tid = std::hash{}(std::this_thread::get_id()); + const auto declaring_type = fn_def->get_declaring_type(); + const auto decltype_name = declaring_type != nullptr ? declaring_type->get_full_name() : "unknownclass"; + spdlog::warn("[HookManager] (Post) Recursive hook detected for '{}.{}' (thread ID: {:x})", decltype_name, fn_def->get_name(), tid); + storage->post_warned_recursion = true; + } + + ++storage->post_depth; + --storage->overall_depth; + + auto& ret_val = storage->ret_val; + //auto& ret_addr = storage->ret_addr_post; + for (const auto& cb : cbs) { if (cb.post_fn) { - cb.post_fn(ret_val, ret_ty, ret_addr); + // Valid return address in recursion scenario is no longer supported with this API. + // We just pass ret_addr_pre for now, even though it's not accurate. + // Hooks will not have much use for the return address anyway. + cb.post_fn(ret_val, ret_ty, storage->ret_addr_pre); } } + + --storage->post_depth; + + if (storage->post_depth == 0) { + this->access_mux.unlock_shared(); + } } 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 +161,90 @@ void HookManager::create_jitted_facilitator(std::unique_ptr