├── .clang-format ├── .github └── workflows │ └── build.yml ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── cmake └── CPM.cmake ├── include └── tulip │ ├── AbstractFunction.hpp │ ├── AbstractType.hpp │ ├── CallingConvention.hpp │ ├── FunctionData.hpp │ ├── HandlerData.hpp │ ├── HookData.hpp │ ├── Platform.hpp │ ├── TulipHook.hpp │ ├── WrapperData.hpp │ └── platform │ ├── DefaultConvention.hpp │ ├── MacosIntelConvention.hpp │ ├── PlatformConvention.hpp │ ├── Windows32Convention.hpp │ └── Windows64Convention.hpp ├── libraries └── dobby │ ├── CMakeLists.txt │ ├── LICENSE │ ├── README.md │ ├── external │ └── TINYSTL │ │ ├── README │ │ ├── algorithm.h │ │ ├── allocator.h │ │ ├── buffer.h │ │ ├── hash.h │ │ ├── hash_base.h │ │ ├── new.h │ │ ├── stddef.h │ │ ├── string.h │ │ ├── string_view.h │ │ ├── traits.h │ │ ├── unordered_map.h │ │ ├── unordered_set.h │ │ └── vector.h │ ├── include │ └── dobby.h │ └── source │ ├── InstructionRelocation │ ├── InstructionRelocation.h │ ├── arm │ │ ├── InstructionRelocationARM.cc │ │ └── InstructionRelocationARM.h │ └── arm64 │ │ ├── InstructionRelocationARM64.cc │ │ ├── InstructionRelocationARM64.h │ │ ├── inst_constants.h │ │ └── inst_decode_encode_kit.h │ ├── MemoryAllocator │ ├── AssemblyCodeBuilder.cc │ ├── AssemblyCodeBuilder.h │ └── CodeBuffer │ │ ├── CodeBufferBase.cc │ │ ├── CodeBufferBase.h │ │ ├── code_buffer_arm.h │ │ └── code_buffer_arm64.h │ ├── PlatformUnifiedInterface │ ├── ExecMemory │ │ ├── ClearCacheTool.h │ │ └── CodePatchTool.h │ └── MemoryAllocator.h │ ├── core │ ├── arch │ │ ├── Cpu.h │ │ ├── CpuFeature.cc │ │ ├── CpuFeature.h │ │ ├── CpuRegister.cc │ │ ├── CpuRegister.h │ │ ├── arm │ │ │ ├── constants-arm.h │ │ │ └── registers-arm.h │ │ └── arm64 │ │ │ ├── constants-arm64.h │ │ │ └── registers-arm64.h │ ├── assembler │ │ ├── AssemblerPseudoLabel.h │ │ ├── assembler-arm.cc │ │ ├── assembler-arm.h │ │ ├── assembler-arm64.cc │ │ ├── assembler-arm64.h │ │ ├── assembler.cc │ │ └── assembler.h │ └── codegen │ │ ├── codegen-arm.cc │ │ ├── codegen-arm.h │ │ ├── codegen-arm64.cc │ │ ├── codegen-arm64.h │ │ └── codegen.h │ └── dobby │ ├── common.h │ ├── dobby_internal.h │ ├── pac_kit.h │ ├── platform_detect_macro.h │ ├── platform_features.h │ ├── types.h │ └── utility_macro.h ├── src ├── CMakeLists.txt ├── Handler.cpp ├── Handler.hpp ├── Hook.hpp ├── Main.cpp ├── Misc.cpp ├── Misc.hpp ├── Pool.cpp ├── Pool.hpp ├── Wrapper.cpp ├── Wrapper.hpp ├── assembler │ ├── ArmV7Assembler.cpp │ ├── ArmV7Assembler.hpp │ ├── ArmV8Assembler.cpp │ ├── ArmV8Assembler.hpp │ ├── BaseAssembler.cpp │ ├── BaseAssembler.hpp │ ├── X64Assembler.cpp │ ├── X64Assembler.hpp │ ├── X86Assembler.cpp │ └── X86Assembler.hpp ├── convention │ ├── CallingConvention.cpp │ ├── DefaultConvention.cpp │ ├── MacosIntelConvention.cpp │ ├── Windows32Convention.cpp │ └── Windows64Convention.cpp ├── generator │ ├── ArmV7Generator.cpp │ ├── ArmV7Generator.hpp │ ├── ArmV8Generator.cpp │ ├── ArmV8Generator.hpp │ ├── Generator.cpp │ ├── Generator.hpp │ ├── X64Generator.cpp │ ├── X64Generator.hpp │ ├── X86Generator.cpp │ └── X86Generator.hpp └── target │ ├── DarwinTarget.cpp │ ├── DarwinTarget.hpp │ ├── MacosIntelTarget.cpp │ ├── MacosIntelTarget.hpp │ ├── MacosM1Target.cpp │ ├── MacosM1Target.hpp │ ├── PlatformTarget.hpp │ ├── PosixArmV7Target.cpp │ ├── PosixArmV7Target.hpp │ ├── PosixArmV8Target.cpp │ ├── PosixArmV8Target.hpp │ ├── PosixTarget.cpp │ ├── PosixTarget.hpp │ ├── Target.cpp │ ├── Target.hpp │ ├── Windows32Target.cpp │ ├── Windows32Target.hpp │ ├── Windows64Target.cpp │ └── Windows64Target.hpp └── test ├── Assembler.hpp ├── Assembler64.cpp ├── Assembler86.cpp ├── CMakeLists.txt ├── CallingConventions.cpp ├── Hook.cpp └── misc ├── Mod.cpp └── WinTest.cpp /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | IndentWidth: 4 3 | 4 | AlignAfterOpenBracket: BlockIndent 5 | AlignEscapedNewlines: Left 6 | AlignConsecutiveAssignments: false 7 | AlignOperands: DontAlign 8 | AlignTrailingComments: false 9 | 10 | AllowAllArgumentsOnNextLine: true 11 | AllowAllParametersOfDeclarationOnNextLine: false 12 | 13 | AllowShortBlocksOnASingleLine: Empty 14 | AllowShortFunctionsOnASingleLine: Empty 15 | AllowShortLambdasOnASingleLine: Empty 16 | 17 | AllowShortCaseLabelsOnASingleLine: true 18 | AllowShortEnumsOnASingleLine: false 19 | 20 | AllowShortIfStatementsOnASingleLine: AllIfsAndElse 21 | AlwaysBreakAfterReturnType: None 22 | 23 | AlwaysBreakBeforeMultilineStrings: true 24 | AlwaysBreakTemplateDeclarations: Yes 25 | 26 | BinPackArguments: false 27 | BinPackParameters: true 28 | 29 | BraceWrapping: 30 | AfterCaseLabel: false 31 | AfterClass: false 32 | AfterControlStatement: Never 33 | AfterEnum: false 34 | AfterFunction: false 35 | AfterNamespace: false 36 | AfterStruct: false 37 | AfterUnion: false 38 | AfterExternBlock: false 39 | 40 | BeforeCatch: true 41 | BeforeElse: true 42 | 43 | BeforeLambdaBody: false 44 | BeforeWhile: false 45 | IndentBraces: false 46 | SplitEmptyFunction: false 47 | SplitEmptyRecord: false 48 | SplitEmptyNamespace: false 49 | 50 | BreakBeforeBinaryOperators: None 51 | BreakBeforeBraces: Custom 52 | BreakBeforeConceptDeclarations: true 53 | BreakBeforeTernaryOperators: false 54 | BreakConstructorInitializers: AfterColon 55 | BreakInheritanceList: AfterColon 56 | BreakStringLiterals: true 57 | ColumnLimit: 120 58 | CompactNamespaces: false 59 | ConstructorInitializerIndentWidth: 4 60 | ContinuationIndentWidth: 4 61 | Cpp11BracedListStyle: true 62 | 63 | DeriveLineEnding: false 64 | UseCRLF: false 65 | UseTab: ForContinuationAndIndentation 66 | TabWidth: 4 67 | 68 | PointerAlignment: Left 69 | ReferenceAlignment: Pointer 70 | 71 | EmptyLineBeforeAccessModifier: Always 72 | FixNamespaceComments: false 73 | 74 | IncludeBlocks: Regroup 75 | IncludeCategories: 76 | - Regex: '^".*"' 77 | Priority: 1 78 | 79 | - Regex: '^<.*>' 80 | Priority: 2 81 | 82 | IndentAccessModifiers: false 83 | AccessModifierOffset: -4 84 | IndentCaseBlocks: false 85 | IndentCaseLabels: true 86 | IndentExternBlock: Indent 87 | IndentGotoLabels: true 88 | IndentPPDirectives: None 89 | IndentRequiresClause: true 90 | IndentWrappedFunctionNames: false 91 | InsertBraces: true 92 | InsertTrailingCommas: None 93 | 94 | KeepEmptyLinesAtTheStartOfBlocks: false 95 | MaxEmptyLinesToKeep: 1 96 | LambdaBodyIndentation: Signature 97 | NamespaceIndentation: All 98 | 99 | PPIndentWidth: -1 100 | 101 | PackConstructorInitializers: Never 102 | 103 | 104 | PenaltyBreakAssignment: 2 105 | PenaltyBreakBeforeFirstCallParameter: 10 106 | PenaltyBreakComment: 300 107 | PenaltyBreakFirstLessLess: 120 108 | PenaltyBreakString: 1000 109 | PenaltyBreakOpenParenthesis: 0 110 | PenaltyBreakTemplateDeclaration: 10 111 | PenaltyExcessCharacter: 10 112 | PenaltyReturnTypeOnItsOwnLine: 100000000 113 | PenaltyIndentedWhitespace: 0 114 | 115 | QualifierAlignment: Right 116 | 117 | RequiresClausePosition: OwnLine 118 | ReflowComments: true 119 | SeparateDefinitionBlocks: Always 120 | 121 | SortIncludes: CaseSensitive 122 | SortUsingDeclarations: true 123 | 124 | SpaceAfterCStyleCast: false 125 | SpaceAfterLogicalNot: false 126 | SpaceAfterTemplateKeyword: true 127 | SpaceAroundPointerQualifiers: Default 128 | SpaceBeforeAssignmentOperators: true 129 | SpaceBeforeCaseColon: false 130 | SpaceBeforeCpp11BracedList: false 131 | SpaceBeforeCtorInitializerColon: true 132 | SpaceBeforeInheritanceColon: true 133 | SpaceBeforeParens: ControlStatements 134 | SpaceBeforeRangeBasedForLoopColon: true 135 | SpaceBeforeSquareBrackets: false 136 | SpaceInEmptyBlock: false 137 | SpaceInEmptyParentheses: false 138 | SpacesBeforeTrailingComments: 1 139 | SpacesInAngles: Never 140 | SpacesInCStyleCastParentheses: false 141 | SpacesInConditionalStatement: false 142 | SpacesInLineCommentPrefix: 143 | Minimum: 1 144 | Maximum: -1 145 | SpacesInParentheses: false 146 | SpacesInSquareBrackets: false 147 | Standard: c++20 148 | 149 | AttributeMacros: ['TULIP_HOOK_DEFAULT_CONV', 'TULIP_HOOK_DLL'] -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build TulipHook 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: 7 | - '**' # every branch 8 | - '!no-build-**' # unless marked as no-build 9 | 10 | jobs: 11 | build: 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | config: 16 | - name: 'Windows (32-bit)' 17 | id: win-32 18 | arch: x86 19 | os: windows-latest 20 | build_tests: true 21 | extra_flags: '-DCMAKE_BUILD_TYPE=Debug' 22 | out_paths: './build/src/TulipHook.lib' 23 | 24 | - name: 'Windows (64-bit)' 25 | id: win-64 26 | arch: x64 27 | os: windows-latest 28 | build_tests: true 29 | extra_flags: '-DCMAKE_BUILD_TYPE=Debug' 30 | out_paths: './build/src/TulipHook.lib' 31 | 32 | - name: 'macOS' 33 | id: mac 34 | os: macos-13 35 | build_tests: true 36 | extra_flags: "-DCMAKE_BUILD_TYPE=Debug" 37 | out_paths: './build/src/libTulipHook.a' 38 | 39 | - name: 'macOS (m1)' 40 | id: mac-arm 41 | os: macos-14 42 | build_tests: true 43 | extra_flags: "-DCMAKE_BUILD_TYPE=Debug" 44 | out_paths: './build/src/libTulipHook.a' 45 | 46 | - name: 'Android Armv7' 47 | id: android-armv7 48 | os: ubuntu-latest 49 | build_tests: false 50 | extra_flags: "-DTULIP_DONT_BUILD_TESTS=ON -DCMAKE_BUILD_TYPE=Debug -DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK_LATEST_HOME/build/cmake/android.toolchain.cmake -DANDROID_ABI=armeabi-v7a -DANDROID_PLATFORM=android-25 -DANDROID_ARM_NEON=ON" 51 | out_paths: './build/src/libTulipHook.a' 52 | 53 | - name: 'Android Arm64' 54 | id: android-arm64 55 | os: ubuntu-latest 56 | build_tests: false 57 | extra_flags: "-DTULIP_DONT_BUILD_TESTS=ON -DCMAKE_BUILD_TYPE=Debug -DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK_LATEST_HOME/build/cmake/android.toolchain.cmake -DANDROID_ABI=arm64-v8a -DANDROID_PLATFORM=android-25 -DANDROID_ARM_NEON=ON" 58 | out_paths: './build/src/libTulipHook.a' 59 | 60 | name: Build and Test ${{ matrix.config.name }} 61 | runs-on: ${{ matrix.config.os }} 62 | 63 | env: 64 | CPM_SOURCE_CACHE: ${{ github.workspace }}/cpm-cache 65 | CMAKE_C_COMPILER_LAUNCHER: sccache 66 | CMAKE_CXX_COMPILER_LAUNCHER: sccache 67 | 68 | steps: 69 | - name: Checkout 70 | uses: actions/checkout@v4 71 | with: 72 | submodules: recursive 73 | 74 | - name: Setup MSVC 75 | uses: ilammy/msvc-dev-cmd@v1.12.1 76 | with: 77 | arch: ${{ matrix.config.arch }} 78 | if: matrix.config.id == 'win-32' || matrix.config.id == 'win-64' 79 | 80 | # https://github.com/hendrikmuhs/ccache-action/pull/182 81 | - name: Setup sccache 82 | uses: chirag-droid/ccache-action@main 83 | with: 84 | variant: sccache 85 | key: ${{ matrix.config.id }}-v1 86 | 87 | - name: Install ninja-build tool 88 | uses: seanmiddleditch/gha-setup-ninja@v3 89 | if: matrix.config.id != 'win-32' && matrix.config.id != 'win-64' 90 | 91 | - name: Configure 92 | shell: bash 93 | run: cmake -G Ninja -B ./build ${{ matrix.config.extra_flags }} 94 | 95 | - name: Build 96 | shell: bash 97 | run: | 98 | cmake --build ./build --parallel 99 | mkdir ./out 100 | cp ${{ matrix.config.out_paths }} ./out 101 | 102 | - name: Upload Artifacts 103 | uses: actions/upload-artifact@v4 104 | with: 105 | name: output-${{ matrix.config.id }} 106 | path: ${{ github.workspace }}/out 107 | 108 | - name: Test 109 | run: ctest --test-dir ./build/test --output-on-failure 110 | if: matrix.config.build_tests == true 111 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | CMakeLists.txt.user 2 | CMakeCache.txt 3 | CMakeFiles 4 | CMakeScripts 5 | Testing 6 | Makefile 7 | cmake_install.cmake 8 | install_manifest.txt 9 | compile_commands.json 10 | CTestTestfile.cmake 11 | _deps 12 | 13 | # Macos be like 14 | **/.DS_Store 15 | 16 | # Cache files for Sublime Text 17 | **/*.tmlanguage.cache 18 | **/*.tmPreferences.cache 19 | **/*.stTheme.cache 20 | 21 | # Workspace files are user-specific 22 | **/*.sublime-workspace 23 | **/*.sublime-project 24 | .vscode 25 | .idea 26 | 27 | # Ignore build folders 28 | **/build 29 | build 30 | 31 | balls.asm 32 | balls2.asm 33 | 34 | # clangd 35 | .cache/ -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.25) 2 | 3 | set(CMAKE_MSVC_DEBUG_INFORMATION_FORMAT Embedded) 4 | cmake_policy(SET CMP0141 NEW) 5 | 6 | project(TulipHookRoot LANGUAGES CXX C) 7 | 8 | include(cmake/CPM.cmake) 9 | 10 | option(TULIP_LINK_SOURCE "Link to TulipHook source files, including external libs" OFF) 11 | 12 | if(TULIP_LINK_SOURCE OR PROJECT_IS_TOP_LEVEL) 13 | if(ANDROID OR UNIX OR APPLE) 14 | add_subdirectory(libraries/dobby) 15 | endif() 16 | add_subdirectory(src) 17 | endif() 18 | 19 | if(PROJECT_IS_TOP_LEVEL AND NOT TULIP_DONT_BUILD_TESTS) 20 | add_subdirectory(test) 21 | endif() 22 | 23 | if (NOT TARGET GeodeResult) 24 | CPMAddPackage("gh:geode-sdk/result@1.3.0") 25 | endif() 26 | 27 | add_library(TulipHookInclude INTERFACE) 28 | target_include_directories(TulipHookInclude INTERFACE 29 | ${TulipHookRoot_SOURCE_DIR}/include 30 | ) 31 | 32 | target_link_libraries(TulipHookInclude INTERFACE GeodeResult) 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Boost Software License - Version 1.0 - August 17th, 2003 2 | 3 | Permission is hereby granted, free of charge, to any person or organization 4 | obtaining a copy of the software and accompanying documentation covered by 5 | this license (the "Software") to use, reproduce, display, distribute, 6 | execute, and transmit the Software, and to prepare derivative works of the 7 | Software, and to permit third-parties to whom the Software is furnished to 8 | do so, all subject to the following: 9 | 10 | The copyright notices in the Software and this entire statement, including 11 | the above license grant, this restriction and the following disclaimer, 12 | must be included in all copies of the Software, in whole or in part, and 13 | all derivative works of the Software, unless such copies or derivative 14 | works are solely in the form of machine-executable object code generated by 15 | a source language processor. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 20 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 21 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 22 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tulip Hook 2 | Low level hooking lib specialized for Geometry Dash 3 | 4 | Moved from altalk23/TulipHook 5 | 6 | Depends on a stripped down version of Dobby for ARMv7 instruction relocation. -------------------------------------------------------------------------------- /cmake/CPM.cmake: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: MIT 2 | # 3 | # SPDX-FileCopyrightText: Copyright (c) 2019-2023 Lars Melchior and contributors 4 | 5 | set(CPM_DOWNLOAD_VERSION 0.38.7) 6 | set(CPM_HASH_SUM "83e5eb71b2bbb8b1f2ad38f1950287a057624e385c238f6087f94cdfc44af9c5") 7 | 8 | if(CPM_SOURCE_CACHE) 9 | set(CPM_DOWNLOAD_LOCATION "${CPM_SOURCE_CACHE}/cpm/CPM_${CPM_DOWNLOAD_VERSION}.cmake") 10 | elseif(DEFINED ENV{CPM_SOURCE_CACHE}) 11 | set(CPM_DOWNLOAD_LOCATION "$ENV{CPM_SOURCE_CACHE}/cpm/CPM_${CPM_DOWNLOAD_VERSION}.cmake") 12 | else() 13 | set(CPM_DOWNLOAD_LOCATION "${CMAKE_BINARY_DIR}/cmake/CPM_${CPM_DOWNLOAD_VERSION}.cmake") 14 | endif() 15 | 16 | # Expand relative path. This is important if the provided path contains a tilde (~) 17 | get_filename_component(CPM_DOWNLOAD_LOCATION ${CPM_DOWNLOAD_LOCATION} ABSOLUTE) 18 | 19 | file(DOWNLOAD 20 | https://github.com/cpm-cmake/CPM.cmake/releases/download/v${CPM_DOWNLOAD_VERSION}/CPM.cmake 21 | ${CPM_DOWNLOAD_LOCATION} EXPECTED_HASH SHA256=${CPM_HASH_SUM} 22 | ) 23 | 24 | include(${CPM_DOWNLOAD_LOCATION}) 25 | -------------------------------------------------------------------------------- /include/tulip/AbstractFunction.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "AbstractType.hpp" 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | namespace tulip::hook { 10 | class AbstractFunction { 11 | template 12 | struct Generator { 13 | static AbstractFunction generate() { 14 | return AbstractFunction(); 15 | } 16 | }; 17 | 18 | template 19 | struct Generator { 20 | static AbstractFunction generate() { 21 | AbstractFunction func; 22 | func.m_return = AbstractType::from(); 23 | (func.m_parameters.push_back(AbstractType::from()), ...); 24 | 25 | return func; 26 | } 27 | }; 28 | 29 | public: 30 | AbstractType m_return; 31 | std::vector m_parameters; 32 | 33 | template 34 | static AbstractFunction from() { 35 | return Generator::generate(); 36 | } 37 | 38 | template 39 | static AbstractFunction from(Return (*)(Parameters...)) { 40 | AbstractFunction func; 41 | func.m_return = AbstractType::from(); 42 | (func.m_parameters.push_back(AbstractType::from()), ...); 43 | 44 | return func; 45 | } 46 | }; 47 | } 48 | -------------------------------------------------------------------------------- /include/tulip/AbstractType.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace tulip::hook { 8 | enum class AbstractTypeKind : uint8_t { 9 | Primitive, 10 | FloatingPoint, 11 | Other, 12 | }; 13 | 14 | class AbstractType { 15 | public: 16 | size_t m_size; 17 | AbstractTypeKind m_kind; 18 | 19 | template 20 | static AbstractType from() { 21 | AbstractType type; 22 | if constexpr (std::is_same_v) { 23 | type.m_kind = AbstractTypeKind::Primitive; 24 | type.m_size = 1; 25 | } 26 | else if constexpr (std::is_floating_point_v) { 27 | type.m_kind = AbstractTypeKind::FloatingPoint; 28 | type.m_size = sizeof(Type); 29 | } 30 | else if constexpr (!std::is_class_v) { 31 | type.m_kind = AbstractTypeKind::Primitive; 32 | type.m_size = std::is_reference_v ? sizeof(void*) : sizeof(Type); 33 | } 34 | else { 35 | type.m_kind = AbstractTypeKind::Other; 36 | type.m_size = sizeof(Type); 37 | } 38 | return type; 39 | } 40 | }; 41 | } 42 | -------------------------------------------------------------------------------- /include/tulip/CallingConvention.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace tulip::hook { 6 | class BaseAssembler; 7 | class AbstractFunction; 8 | 9 | class CallingConvention { 10 | public: 11 | virtual ~CallingConvention() = 0; 12 | 13 | /** 14 | * Generate the code for converting the function args from the 15 | * custom convention to cdecl 16 | */ 17 | virtual void generateIntoDefault(BaseAssembler& a, AbstractFunction const& function) = 0; 18 | /** 19 | * Generate the code for cleaning up the stack after our cdecl 20 | * detour is done (since cdecl is caller-cleanup) 21 | */ 22 | virtual void generateDefaultCleanup(BaseAssembler& a, AbstractFunction const& function) = 0; 23 | 24 | /** 25 | * Generate the code for converting the function args from cdecl 26 | * to the custom convention 27 | */ 28 | virtual void generateIntoOriginal(BaseAssembler& a, AbstractFunction const& function) = 0; 29 | /** 30 | * Generate the code for cleaning up the stack after the custom 31 | * convention is done (since cdecl is caller-cleanup) 32 | */ 33 | virtual void generateOriginalCleanup(BaseAssembler& a, AbstractFunction const& function) = 0; 34 | /** 35 | * Check if the function needs a wrapper generated 36 | */ 37 | virtual bool needsWrapper(AbstractFunction const& function) const = 0; 38 | }; 39 | } 40 | -------------------------------------------------------------------------------- /include/tulip/FunctionData.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace tulip::hook { 6 | struct FunctionData { 7 | void* m_address; 8 | size_t m_size; 9 | }; 10 | } 11 | -------------------------------------------------------------------------------- /include/tulip/HandlerData.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "AbstractFunction.hpp" 4 | 5 | #include 6 | 7 | namespace tulip::hook { 8 | class CallingConvention; 9 | 10 | using HandlerHandle = size_t; 11 | 12 | class HandlerMetadata { 13 | public: 14 | std::shared_ptr m_convention; 15 | 16 | AbstractFunction m_abstract; 17 | }; 18 | } -------------------------------------------------------------------------------- /include/tulip/HookData.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace tulip::hook { 6 | using HookHandle = size_t; 7 | 8 | class HookMetadata { 9 | public: 10 | int32_t m_priority = 0; 11 | }; 12 | } 13 | -------------------------------------------------------------------------------- /include/tulip/Platform.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // clang-format off 4 | 5 | #if defined(WIN32) || defined(_WIN32) || defined(__WIN32) && !defined(__CYGWIN__) || defined(WIN64) || defined(_WIN64) || defined(__WIN64) && !defined(__CYGWIN__) 6 | 7 | #define TULIP_HOOK_WINDOWS 1 8 | #define TULIP_HOOK_SUPPORTED_PLATFORM 1 9 | 10 | #define TULIP_HOOK_DEFAULT_CONV __cdecl 11 | 12 | #if defined(WIN64) || defined(_WIN64) || defined(__WIN64) && !defined(__CYGWIN__) 13 | #define TULIP_HOOK_X64 1 14 | #else 15 | #define TULIP_HOOK_X86 1 16 | #endif 17 | 18 | #ifdef TULIP_HOOK_DYNAMIC 19 | #ifdef TULIP_HOOK_EXPORTING 20 | #define TULIP_HOOK_DLL __declspec(dllexport) 21 | #else 22 | #define TULIP_HOOK_DLL __declspec(dllimport) 23 | #endif 24 | #else 25 | #define TULIP_HOOK_DLL 26 | #endif 27 | 28 | #endif 29 | 30 | #if defined(__APPLE__) 31 | 32 | #define TULIP_HOOK_DARWIN 1 33 | 34 | #include 35 | 36 | #if TARGET_OS_MAC 37 | 38 | #define TULIP_HOOK_MACOS 1 39 | 40 | #define TULIP_HOOK_DEFAULT_CONV 41 | 42 | #if TARGET_CPU_ARM64 43 | #define TULIP_HOOK_ARMV8 1 44 | #define TULIP_HOOK_SUPPORTED_PLATFORM 1 45 | #elif TARGET_CPU_X86_64 46 | #define TULIP_HOOK_X64 1 47 | #define TULIP_HOOK_SUPPORTED_PLATFORM 1 48 | #endif 49 | 50 | #ifdef TULIP_HOOK_EXPORTING 51 | #define TULIP_HOOK_DLL __attribute__((visibility("default"))) 52 | #else 53 | #define TULIP_HOOK_DLL 54 | #endif 55 | 56 | #endif 57 | 58 | #endif 59 | 60 | #if defined(__ANDROID__) 61 | 62 | #define TULIP_HOOK_POSIX 1 63 | 64 | #define TULIP_HOOK_ANDROID 1 65 | 66 | #define TULIP_HOOK_DEFAULT_CONV 67 | 68 | #if defined(__arm__) 69 | #define TULIP_HOOK_ARMV7 1 70 | #define TULIP_HOOK_SUPPORTED_PLATFORM 1 71 | #elif defined(__aarch64__) 72 | #define TULIP_HOOK_ARMV8 1 73 | #define TULIP_HOOK_SUPPORTED_PLATFORM 1 74 | #endif 75 | 76 | #ifdef TULIP_HOOK_EXPORTING 77 | #define TULIP_HOOK_DLL __attribute__((visibility("default"))) 78 | #else 79 | #define TULIP_HOOK_DLL 80 | #endif 81 | 82 | #elif defined(__unix__) 83 | 84 | #define TULIP_HOOK_POSIX 1 85 | 86 | #define TULIP_HOOK_DEFAULT_CONV 87 | 88 | #if defined(__arm__) 89 | #define TULIP_HOOK_ARMV7 1 90 | #define TULIP_HOOK_SUPPORTED_PLATFORM 1 91 | #elif defined(__aarch64__) 92 | #define TULIP_HOOK_ARMV8 1 93 | #define TULIP_HOOK_SUPPORTED_PLATFORM 1 94 | #endif 95 | 96 | #ifdef TULIP_HOOK_EXPORTING 97 | #define TULIP_HOOK_DLL __attribute__((visibility("default"))) 98 | #else 99 | #define TULIP_HOOK_DLL 100 | #endif 101 | 102 | #endif 103 | 104 | #if !defined(TULIP_HOOK_SUPPORTED_PLATFORM) 105 | 106 | #error "Unsupported Platform!" 107 | 108 | #endif 109 | 110 | namespace tulip::hook {} 111 | 112 | // clang-format on -------------------------------------------------------------------------------- /include/tulip/TulipHook.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "AbstractFunction.hpp" 4 | #include "AbstractType.hpp" 5 | #include "CallingConvention.hpp" 6 | #include "FunctionData.hpp" 7 | #include "HandlerData.hpp" 8 | #include "HookData.hpp" 9 | #include "Platform.hpp" 10 | #include 11 | #include "WrapperData.hpp" 12 | #include "platform/PlatformConvention.hpp" 13 | 14 | #include 15 | 16 | namespace tulip::hook { 17 | TULIP_HOOK_DLL geode::Result createHandler(void* address, HandlerMetadata const& metadata) noexcept; 18 | 19 | TULIP_HOOK_DLL geode::Result removeHandler(HandlerHandle const& handler) noexcept; 20 | 21 | TULIP_HOOK_DLL HookHandle createHook(HandlerHandle const& handler, void* address, HookMetadata const& metadata) noexcept; 22 | 23 | TULIP_HOOK_DLL void removeHook(HandlerHandle const& handler, HookHandle const& hook) noexcept; 24 | 25 | TULIP_HOOK_DLL void updateHookMetadata( 26 | HandlerHandle const& handler, HookHandle const& hook, HookMetadata const& metadata 27 | ) noexcept; 28 | 29 | TULIP_HOOK_DLL geode::Result writeMemory(void* destination, void const* source, size_t size) noexcept; 30 | 31 | TULIP_HOOK_DLL geode::Result followJumps(void* address) noexcept; 32 | 33 | // wraps a cdecl function into given convention 34 | TULIP_HOOK_DLL geode::Result createWrapper(void* address, WrapperMetadata const& metadata) noexcept; 35 | 36 | // wraps a function in given convention into cdecl 37 | TULIP_HOOK_DLL geode::Result createReverseWrapper(void* address, WrapperMetadata const& metadata) noexcept; 38 | 39 | enum class TulipConvention { 40 | Default, 41 | Cdecl, 42 | Thiscall, 43 | Fastcall, 44 | Optcall, 45 | Membercall, 46 | Stdcall, 47 | }; 48 | 49 | TULIP_HOOK_DLL std::shared_ptr createConvention(TulipConvention convention) noexcept; 50 | } 51 | -------------------------------------------------------------------------------- /include/tulip/WrapperData.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "AbstractFunction.hpp" 4 | 5 | #include 6 | 7 | namespace tulip::hook { 8 | class CallingConvention; 9 | 10 | class WrapperMetadata { 11 | public: 12 | std::shared_ptr m_convention; 13 | 14 | AbstractFunction m_abstract; 15 | }; 16 | } -------------------------------------------------------------------------------- /include/tulip/platform/DefaultConvention.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../CallingConvention.hpp" 4 | 5 | #include 6 | #include 7 | 8 | namespace tulip::hook { 9 | class AbstractFunction; 10 | 11 | class TULIP_HOOK_DLL DefaultConvention : public CallingConvention { 12 | public: 13 | ~DefaultConvention() override; 14 | 15 | void generateDefaultCleanup(BaseAssembler& a, AbstractFunction const& function) override; 16 | void generateIntoDefault(BaseAssembler& a, AbstractFunction const& function) override; 17 | void generateIntoOriginal(BaseAssembler& a, AbstractFunction const& function) override; 18 | void generateOriginalCleanup(BaseAssembler& a, AbstractFunction const& function) override; 19 | bool needsWrapper(AbstractFunction const& function) const override; 20 | 21 | static std::shared_ptr create(); 22 | }; 23 | } 24 | -------------------------------------------------------------------------------- /include/tulip/platform/MacosIntelConvention.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../Platform.hpp" 4 | 5 | #if defined(TULIP_HOOK_MACOS) && defined(TULIP_HOOK_X64) 6 | 7 | #include "../CallingConvention.hpp" 8 | 9 | #include 10 | #include 11 | 12 | namespace tulip::hook { 13 | class AbstractFunction; 14 | 15 | class TULIP_HOOK_DLL SystemVConvention : public CallingConvention { 16 | public: 17 | ~SystemVConvention() override; 18 | 19 | void generateDefaultCleanup(BaseAssembler& a, AbstractFunction const& function) override; 20 | void generateIntoDefault(BaseAssembler& a, AbstractFunction const& function) override; 21 | void generateIntoOriginal(BaseAssembler& a, AbstractFunction const& function) override; 22 | void generateOriginalCleanup(BaseAssembler& a, AbstractFunction const& function) override; 23 | bool needsWrapper(AbstractFunction const& function) const override; 24 | 25 | static std::shared_ptr create(); 26 | }; 27 | } 28 | 29 | #endif 30 | -------------------------------------------------------------------------------- /include/tulip/platform/PlatformConvention.hpp: -------------------------------------------------------------------------------- 1 | #include "../Platform.hpp" 2 | #include "DefaultConvention.hpp" 3 | #include "Windows32Convention.hpp" 4 | #include "Windows64Convention.hpp" 5 | #include "MacosIntelConvention.hpp" 6 | 7 | namespace tulip::hook { 8 | #if defined(TULIP_HOOK_WINDOWS) && defined(TULIP_HOOK_X86) 9 | using PlatformConvention = CdeclConvention; 10 | #elif defined(TULIP_HOOK_MACOS) && defined(TULIP_HOOK_X64) 11 | using PlatformConvention = SystemVConvention; 12 | #elif defined(TULIP_HOOK_WINDOWS) && defined(TULIP_HOOK_X64) 13 | using PlatformConvention = Windows64Convention; 14 | #else 15 | using PlatformConvention = DefaultConvention; 16 | #endif 17 | } -------------------------------------------------------------------------------- /include/tulip/platform/Windows32Convention.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../Platform.hpp" 4 | 5 | #if defined(TULIP_HOOK_WINDOWS) && defined(TULIP_HOOK_X86) 6 | 7 | #include "../CallingConvention.hpp" 8 | 9 | #include 10 | #include 11 | 12 | namespace tulip::hook { 13 | class AbstractFunction; 14 | 15 | class TULIP_HOOK_DLL CdeclConvention : public CallingConvention { 16 | public: 17 | ~CdeclConvention() override; 18 | 19 | void generateDefaultCleanup(BaseAssembler& a, AbstractFunction const& function) override; 20 | void generateIntoDefault(BaseAssembler& a, AbstractFunction const& function) override; 21 | void generateIntoOriginal(BaseAssembler& a, AbstractFunction const& function) override; 22 | void generateOriginalCleanup(BaseAssembler& a, AbstractFunction const& function) override; 23 | bool needsWrapper(AbstractFunction const& function) const override; 24 | 25 | static std::shared_ptr create(); 26 | }; 27 | 28 | class TULIP_HOOK_DLL ThiscallConvention : public CallingConvention { 29 | public: 30 | ~ThiscallConvention() override; 31 | 32 | void generateDefaultCleanup(BaseAssembler& a, AbstractFunction const& function) override; 33 | void generateIntoDefault(BaseAssembler& a, AbstractFunction const& function) override; 34 | void generateIntoOriginal(BaseAssembler& a, AbstractFunction const& function) override; 35 | void generateOriginalCleanup(BaseAssembler& a, AbstractFunction const& function) override; 36 | bool needsWrapper(AbstractFunction const& function) const override; 37 | 38 | static std::shared_ptr create(); 39 | }; 40 | 41 | class TULIP_HOOK_DLL FastcallConvention : public CallingConvention { 42 | public: 43 | ~FastcallConvention() override; 44 | 45 | void generateDefaultCleanup(BaseAssembler& a, AbstractFunction const& function) override; 46 | void generateIntoDefault(BaseAssembler& a, AbstractFunction const& function) override; 47 | void generateIntoOriginal(BaseAssembler& a, AbstractFunction const& function) override; 48 | void generateOriginalCleanup(BaseAssembler& a, AbstractFunction const& function) override; 49 | bool needsWrapper(AbstractFunction const& function) const override; 50 | 51 | static std::shared_ptr create(); 52 | }; 53 | 54 | class TULIP_HOOK_DLL OptcallConvention : public CallingConvention { 55 | public: 56 | ~OptcallConvention() override; 57 | 58 | void generateDefaultCleanup(BaseAssembler& a, AbstractFunction const& function) override; 59 | void generateIntoDefault(BaseAssembler& a, AbstractFunction const& function) override; 60 | void generateIntoOriginal(BaseAssembler& a, AbstractFunction const& function) override; 61 | void generateOriginalCleanup(BaseAssembler& a, AbstractFunction const& function) override; 62 | bool needsWrapper(AbstractFunction const& function) const override; 63 | 64 | static std::shared_ptr create(); 65 | }; 66 | 67 | class TULIP_HOOK_DLL MembercallConvention : public CallingConvention { 68 | public: 69 | ~MembercallConvention() override; 70 | 71 | void generateDefaultCleanup(BaseAssembler& a, AbstractFunction const& function) override; 72 | void generateIntoDefault(BaseAssembler& a, AbstractFunction const& function) override; 73 | void generateIntoOriginal(BaseAssembler& a, AbstractFunction const& function) override; 74 | void generateOriginalCleanup(BaseAssembler& a, AbstractFunction const& function) override; 75 | bool needsWrapper(AbstractFunction const& function) const override; 76 | 77 | static std::shared_ptr create(); 78 | }; 79 | 80 | class TULIP_HOOK_DLL StdcallConvention : public CallingConvention { 81 | public: 82 | ~StdcallConvention() override; 83 | 84 | void generateDefaultCleanup(BaseAssembler& a, AbstractFunction const& function) override; 85 | void generateIntoDefault(BaseAssembler& a, AbstractFunction const& function) override; 86 | void generateIntoOriginal(BaseAssembler& a, AbstractFunction const& function) override; 87 | void generateOriginalCleanup(BaseAssembler& a, AbstractFunction const& function) override; 88 | bool needsWrapper(AbstractFunction const& function) const override; 89 | 90 | static std::shared_ptr create(); 91 | }; 92 | } 93 | 94 | #endif 95 | -------------------------------------------------------------------------------- /include/tulip/platform/Windows64Convention.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../Platform.hpp" 4 | 5 | #if defined(TULIP_HOOK_WINDOWS) && defined(TULIP_HOOK_X64) 6 | 7 | #include "DefaultConvention.hpp" 8 | 9 | #include 10 | #include 11 | 12 | namespace tulip::hook { 13 | class AbstractFunction; 14 | 15 | class TULIP_HOOK_DLL Windows64Convention : public DefaultConvention { 16 | public: 17 | ~Windows64Convention() override; 18 | 19 | void generateDefaultCleanup(BaseAssembler& a, AbstractFunction const& function) override; 20 | void generateIntoDefault(BaseAssembler& a, AbstractFunction const& function) override; 21 | 22 | static std::shared_ptr create(); 23 | }; 24 | 25 | class TULIP_HOOK_DLL ThiscallConvention : public Windows64Convention { 26 | public: 27 | ~ThiscallConvention() override; 28 | 29 | void generateIntoDefault(BaseAssembler& a, AbstractFunction const& function) override; 30 | void generateIntoOriginal(BaseAssembler& a, AbstractFunction const& function) override; 31 | void generateOriginalCleanup(BaseAssembler& a, AbstractFunction const& function) override; 32 | bool needsWrapper(AbstractFunction const& function) const override; 33 | 34 | static std::shared_ptr create(); 35 | }; 36 | } 37 | 38 | #endif 39 | -------------------------------------------------------------------------------- /libraries/dobby/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5) 2 | project(Dobby) 3 | enable_language(ASM) 4 | 5 | file(GLOB DOBBY_SOURCES 6 | source/core/arch/*.cc 7 | source/core/assembler/*.cc 8 | source/core/codegen/*.cc 9 | source/InstructionRelocation/arm/*.cc 10 | source/InstructionRelocation/arm64/*.cc 11 | source/MemoryAllocator/*.cc 12 | source/MemoryAllocator/CodeBuffer/*.cc 13 | ) 14 | 15 | add_library(${PROJECT_NAME} STATIC 16 | ${DOBBY_SOURCES} 17 | ) 18 | 19 | target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_20) 20 | 21 | target_include_directories(${PROJECT_NAME} PUBLIC 22 | ${CMAKE_CURRENT_SOURCE_DIR}/external 23 | ${CMAKE_CURRENT_SOURCE_DIR}/include 24 | ${CMAKE_CURRENT_SOURCE_DIR}/source 25 | ${CMAKE_CURRENT_SOURCE_DIR}/source/dobby 26 | ) 27 | -------------------------------------------------------------------------------- /libraries/dobby/README.md: -------------------------------------------------------------------------------- 1 | ## Dobby 2 | 3 | [![Contact me Telegram](https://img.shields.io/badge/Contact%20me-Telegram-blue.svg)](https://t.me/IOFramebuffer) [![Join group Telegram](https://img.shields.io/badge/Join%20group-Telegram-brightgreen.svg)](https://t.me/dobby_group) 4 | 5 | Dobby a lightweight, multi-platform, multi-architecture exploit hook framework. 6 | 7 | - Minimal and modular library 8 | - Multi-platform support(Windows/macOS/iOS/Android/Linux) 9 | - Multiple architecture support(X86, X86-64, ARM, ARM64) 10 | 11 | ## Compile 12 | 13 | [docs/compile.md](docs/compile.md) 14 | 15 | ## Download 16 | 17 | [download latest library](https://github.com/jmpews/Dobby/releases/tag/latest) 18 | 19 | ## Credits 20 | 21 | 1. [frida-gum](https://github.com/frida/frida-gum) 22 | 2. [minhook](https://github.com/TsudaKageyu/minhook) 23 | 3. [substrate](https://github.com/jevinskie/substrate). 24 | 4. [v8](https://github.com/v8/v8) 25 | 5. [dart](https://github.com/dart-lang/sdk) 26 | 6. [vixl](https://git.linaro.org/arm/vixl.git) 27 | -------------------------------------------------------------------------------- /libraries/dobby/external/TINYSTL/README: -------------------------------------------------------------------------------- 1 | ref: https://github.com/mendsley/tinystl -------------------------------------------------------------------------------- /libraries/dobby/external/TINYSTL/algorithm.h: -------------------------------------------------------------------------------- 1 | #pragma once -------------------------------------------------------------------------------- /libraries/dobby/external/TINYSTL/allocator.h: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright 2012-2018 Matthew Endsley 3 | * All rights reserved 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted providing that the following conditions 7 | * are met: 8 | * 1. Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 15 | * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 18 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 22 | * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING 23 | * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | * POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | 27 | #ifndef TINYSTL_ALLOCATOR_H 28 | #define TINYSTL_ALLOCATOR_H 29 | 30 | #include 31 | 32 | namespace tinystl { 33 | 34 | struct allocator { 35 | static void* static_allocate(size_t bytes) { 36 | return operator new(bytes); 37 | } 38 | 39 | static void static_deallocate(void* ptr, size_t /*bytes*/) { 40 | operator delete(ptr); 41 | } 42 | }; 43 | } 44 | 45 | #ifndef TINYSTL_ALLOCATOR 46 | # define TINYSTL_ALLOCATOR ::tinystl::allocator 47 | #endif 48 | 49 | #endif 50 | -------------------------------------------------------------------------------- /libraries/dobby/external/TINYSTL/hash.h: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright 2012-2018 Matthew Endsley 3 | * All rights reserved 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted providing that the following conditions 7 | * are met: 8 | * 1. Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 15 | * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 18 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 22 | * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING 23 | * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | * POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | 27 | #ifndef TINYSTL_STRINGHASH_H 28 | #define TINYSTL_STRINGHASH_H 29 | 30 | #include 31 | 32 | namespace tinystl { 33 | 34 | static inline size_t hash_string(const char* str, size_t len) { 35 | // Implementation of sdbm a public domain string hash from Ozan Yigit 36 | // see: http://www.eecs.harvard.edu/margo/papers/usenix91/paper.ps 37 | 38 | size_t hash = 0; 39 | typedef const char* pointer; 40 | for (pointer it = str, end = str + len; it != end; ++it) 41 | hash = *it + (hash << 6) + (hash << 16) - hash; 42 | 43 | return hash; 44 | } 45 | 46 | template 47 | inline size_t hash(const T& value) { 48 | const size_t asint = (size_t)value; 49 | return hash_string((const char*)&asint, sizeof(asint)); 50 | } 51 | } 52 | 53 | #endif 54 | -------------------------------------------------------------------------------- /libraries/dobby/external/TINYSTL/new.h: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright 2012-2018 Matthew Endsley 3 | * All rights reserved 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted providing that the following conditions 7 | * are met: 8 | * 1. Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 15 | * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 18 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 22 | * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING 23 | * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | * POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | 27 | #ifndef TINYSTL_NEW_H 28 | #define TINYSTL_NEW_H 29 | 30 | #include 31 | 32 | namespace tinystl { 33 | 34 | struct placeholder {}; 35 | } 36 | 37 | inline void* operator new(size_t, tinystl::placeholder, void* ptr) { 38 | return ptr; 39 | } 40 | 41 | inline void operator delete(void*, tinystl::placeholder, void*) throw() {} 42 | 43 | #endif 44 | -------------------------------------------------------------------------------- /libraries/dobby/external/TINYSTL/stddef.h: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright 2012-2018 Matthew Endsley 3 | * All rights reserved 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted providing that the following conditions 7 | * are met: 8 | * 1. Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 15 | * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 18 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 22 | * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING 23 | * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | * POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | 27 | #ifndef TINYSTL_STDDEF_H 28 | #define TINYSTL_STDDEF_H 29 | 30 | #if defined(_WIN64) 31 | typedef long long unsigned int size_t; 32 | typedef long long int ptrdiff_t; 33 | #elif defined(_WIN32) 34 | typedef unsigned int size_t; 35 | typedef int ptrdiff_t; 36 | #elif defined (__linux__) && defined(__SIZE_TYPE__) && defined(__PTRDIFF_TYPE__) 37 | typedef __SIZE_TYPE__ size_t; 38 | typedef __PTRDIFF_TYPE__ ptrdiff_t; 39 | #else 40 | # include 41 | #endif 42 | 43 | #endif 44 | -------------------------------------------------------------------------------- /libraries/dobby/external/TINYSTL/string_view.h: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright 2012-1017 Matthew Endsley 3 | * All rights reserved 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted providing that the following conditions 7 | * are met: 8 | * 1. Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 15 | * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 18 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 22 | * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING 23 | * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | * POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | 27 | #ifndef TINYSTL_STRING_VIEW_H 28 | #define TINYSTL_STRING_VIEW_H 29 | 30 | #include 31 | 32 | namespace tinystl { 33 | 34 | class string_view 35 | { 36 | public: 37 | typedef char value_type; 38 | typedef char* pointer; 39 | typedef const char* const_pointer; 40 | typedef char& reference; 41 | typedef const char& const_reference; 42 | typedef const_pointer iterator; 43 | typedef const_pointer const_iterator; 44 | typedef size_t size_type; 45 | typedef ptrdiff_t difference_type; 46 | 47 | static constexpr size_type npos = size_type(-1); 48 | 49 | constexpr string_view(); 50 | constexpr string_view(const char* s, size_type count); 51 | constexpr string_view(const char* s); 52 | constexpr string_view(const string_view&) = default; 53 | string_view& operator=(const string_view&) = default; 54 | 55 | constexpr const char* data() const; 56 | constexpr char operator[](size_type pos) const; 57 | constexpr size_type size() const; 58 | constexpr bool empty() const; 59 | constexpr iterator begin() const; 60 | constexpr const_iterator cbegin() const; 61 | constexpr iterator end() const; 62 | constexpr const_iterator cend() const; 63 | constexpr string_view substr(size_type pos = 0, size_type count = npos) const; 64 | constexpr void swap(string_view& v); 65 | 66 | private: 67 | string_view(decltype(nullptr)) = delete; 68 | 69 | static constexpr size_type strlen(const char*); 70 | 71 | const char* m_str; 72 | size_type m_size; 73 | }; 74 | 75 | constexpr string_view::string_view() 76 | : m_str(nullptr) 77 | , m_size(0) 78 | { 79 | } 80 | 81 | constexpr string_view::string_view(const char* s, size_type count) 82 | : m_str(s) 83 | , m_size(count) 84 | { 85 | } 86 | 87 | constexpr string_view::string_view(const char* s) 88 | : m_str(s) 89 | , m_size(strlen(s)) 90 | { 91 | } 92 | 93 | constexpr const char* string_view::data() const { 94 | return m_str; 95 | } 96 | 97 | constexpr char string_view::operator[](size_type pos) const { 98 | return m_str[pos]; 99 | } 100 | 101 | constexpr string_view::size_type string_view::size() const { 102 | return m_size; 103 | } 104 | 105 | constexpr bool string_view::empty() const { 106 | return 0 == m_size; 107 | } 108 | 109 | constexpr string_view::iterator string_view::begin() const { 110 | return m_str; 111 | } 112 | 113 | constexpr string_view::const_iterator string_view::cbegin() const { 114 | return m_str; 115 | } 116 | 117 | constexpr string_view::iterator string_view::end() const { 118 | return m_str + m_size; 119 | } 120 | 121 | constexpr string_view::const_iterator string_view::cend() const { 122 | return m_str + m_size; 123 | } 124 | 125 | constexpr string_view string_view::substr(size_type pos, size_type count) const { 126 | return string_view(m_str + pos, npos == count ? m_size - pos : count); 127 | } 128 | 129 | constexpr void string_view::swap(string_view& v) { 130 | const char* strtmp = m_str; 131 | size_type sizetmp = m_size; 132 | m_str = v.m_str; 133 | m_size = v.m_size; 134 | v.m_str = strtmp; 135 | v.m_size = sizetmp; 136 | } 137 | 138 | constexpr string_view::size_type string_view::strlen(const char* s) { 139 | for (size_t len = 0; ; ++len) { 140 | if (0 == s[len]) { 141 | return len; 142 | } 143 | } 144 | } 145 | } 146 | 147 | #endif // TINYSTL_STRING_VIEW_H 148 | -------------------------------------------------------------------------------- /libraries/dobby/external/TINYSTL/traits.h: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright 2012-2018 Matthew Endsley 3 | * All rights reserved 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted providing that the following conditions 7 | * are met: 8 | * 1. Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 15 | * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 18 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 22 | * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING 23 | * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | * POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | 27 | #ifndef TINYSTL_TRAITS_H 28 | #define TINYSTL_TRAITS_H 29 | 30 | #include 31 | 32 | #if defined(__GNUC__) 33 | # define TINYSTL_TRY_POD_OPTIMIZATION(t) __is_pod(t) 34 | #elif defined(_MSC_VER) 35 | # define TINYSTL_TRY_POD_OPTIMIZATION(t) (!__is_class(t) || __is_pod(t)) 36 | #else 37 | # define TINYSTL_TRY_POD_OPTIMIZATION(t) false 38 | #endif 39 | 40 | namespace tinystl { 41 | template struct pod_traits {}; 42 | 43 | template struct swap_holder; 44 | 45 | template 46 | static inline void move_impl(T& a, T& b, ...) { 47 | a = b; 48 | } 49 | 50 | template 51 | static inline void move_impl(T& a, T& b, T*, swap_holder* = 0) { 52 | a.swap(b); 53 | } 54 | 55 | template 56 | static inline void move(T& a, T&b) { 57 | move_impl(a, b, (T*)0); 58 | } 59 | 60 | template 61 | static inline void move_construct_impl(T* a, T& b, ...) { 62 | new(placeholder(), a) T(b); 63 | } 64 | 65 | template 66 | static inline void move_construct_impl(T* a, T& b, void*, swap_holder* = 0) { 67 | // If your type T does not have a default constructor, simply insert: 68 | // struct tinystl_nomove_construct; 69 | // in the class definition 70 | new(placeholder(), a) T; 71 | a->swap(b); 72 | } 73 | 74 | template 75 | static inline void move_construct_impl(T* a, T& b, T*, typename T::tinystl_nomove_construct* = 0) { 76 | new(placeholder(), a) T(b); 77 | } 78 | 79 | template 80 | static inline void move_construct(T* a, T& b) { 81 | move_construct_impl(a, b, (T*)0); 82 | } 83 | 84 | template 85 | struct remove_reference { 86 | typedef T type; 87 | }; 88 | 89 | template 90 | struct remove_reference { 91 | typedef T type; 92 | }; 93 | 94 | template 95 | struct remove_reference { 96 | typedef T type; 97 | }; 98 | } 99 | 100 | #endif 101 | -------------------------------------------------------------------------------- /libraries/dobby/include/dobby.h: -------------------------------------------------------------------------------- 1 | #ifndef dobby_h 2 | #define dobby_h 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | #include 9 | #include 10 | 11 | typedef uintptr_t addr_t; 12 | typedef uint32_t addr32_t; 13 | typedef uint64_t addr64_t; 14 | 15 | typedef void *dobby_dummy_func_t; 16 | typedef void *asm_func_t; 17 | 18 | #if defined(__arm__) 19 | typedef struct { 20 | uint32_t dummy_0; 21 | uint32_t dummy_1; 22 | 23 | uint32_t dummy_2; 24 | uint32_t sp; 25 | 26 | union { 27 | uint32_t r[13]; 28 | struct { 29 | uint32_t r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12; 30 | } regs; 31 | } general; 32 | 33 | uint32_t lr; 34 | } DobbyRegisterContext; 35 | #elif defined(__arm64__) || defined(__aarch64__) 36 | #define ARM64_TMP_REG_NDX_0 17 37 | 38 | typedef union _FPReg { 39 | __int128_t q; 40 | struct { 41 | double d1; 42 | double d2; 43 | } d; 44 | struct { 45 | float f1; 46 | float f2; 47 | float f3; 48 | float f4; 49 | } f; 50 | } FPReg; 51 | 52 | // register context 53 | typedef struct { 54 | uint64_t dmmpy_0; // dummy placeholder 55 | uint64_t sp; 56 | 57 | uint64_t dmmpy_1; // dummy placeholder 58 | union { 59 | uint64_t x[29]; 60 | struct { 61 | uint64_t x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18, x19, x20, x21, x22, 62 | x23, x24, x25, x26, x27, x28; 63 | } regs; 64 | } general; 65 | 66 | uint64_t fp; 67 | uint64_t lr; 68 | 69 | union { 70 | FPReg q[32]; 71 | struct { 72 | FPReg q0, q1, q2, q3, q4, q5, q6, q7; 73 | // [!!! READ ME !!!] 74 | // for Arm64, can't access q8 - q31, unless you enable full floating-point register pack 75 | FPReg q8, q9, q10, q11, q12, q13, q14, q15, q16, q17, q18, q19, q20, q21, q22, q23, q24, q25, q26, q27, q28, q29, 76 | q30, q31; 77 | } regs; 78 | } floating; 79 | } DobbyRegisterContext; 80 | #elif defined(_M_IX86) || defined(__i386__) 81 | typedef struct _RegisterContext { 82 | uint32_t dummy_0; 83 | uint32_t esp; 84 | 85 | uint32_t dummy_1; 86 | uint32_t flags; 87 | 88 | union { 89 | struct { 90 | uint32_t eax, ebx, ecx, edx, ebp, esp, edi, esi; 91 | } regs; 92 | } general; 93 | 94 | } DobbyRegisterContext; 95 | #elif defined(_M_X64) || defined(__x86_64__) 96 | typedef struct { 97 | uint64_t dummy_0; 98 | uint64_t rsp; 99 | 100 | union { 101 | struct { 102 | uint64_t rax, rbx, rcx, rdx, rbp, rsp, rdi, rsi, r8, r9, r10, r11, r12, r13, r14, r15; 103 | } regs; 104 | } general; 105 | 106 | uint64_t dummy_1; 107 | uint64_t flags; 108 | } DobbyRegisterContext; 109 | #endif 110 | 111 | #define install_hook_name(name, fn_ret_t, ...) \ 112 | static fn_ret_t fake_##name(__VA_ARGS__); \ 113 | static fn_ret_t (*orig_##name)(__VA_ARGS__); \ 114 | /* __attribute__((constructor)) */ static void install_hook_##name(void *sym_addr) { \ 115 | DobbyHook(sym_addr, (dobby_dummy_func_t)fake_##name, (dobby_dummy_func_t *)&orig_##name); \ 116 | return; \ 117 | } \ 118 | fn_ret_t fake_##name(__VA_ARGS__) 119 | 120 | // memory code patch 121 | int DobbyCodePatch(void *address, uint8_t *buffer, uint32_t buffer_size); 122 | 123 | // function inline hook 124 | int DobbyHook(void *address, dobby_dummy_func_t replace_func, dobby_dummy_func_t *origin_func); 125 | 126 | // dynamic binary instruction instrument 127 | // for Arm64, can't access q8 - q31, unless enable full floating-point register pack 128 | typedef void (*dobby_instrument_callback_t)(void *address, DobbyRegisterContext *ctx); 129 | int DobbyInstrument(void *address, dobby_instrument_callback_t pre_handler); 130 | 131 | // destroy and restore code patch 132 | int DobbyDestroy(void *address); 133 | 134 | const char *DobbyGetVersion(); 135 | 136 | // symbol resolver 137 | void *DobbySymbolResolver(const char *image_name, const char *symbol_name); 138 | 139 | // import table replace 140 | int DobbyImportTableReplace(char *image_name, char *symbol_name, dobby_dummy_func_t fake_func, 141 | dobby_dummy_func_t *orig_func); 142 | 143 | // for arm, Arm64, try use b xxx instead of ldr absolute indirect branch 144 | // for x86, x64, always use absolute indirect jump 145 | void dobby_enable_near_branch_trampoline(); 146 | void dobby_disable_near_branch_trampoline(); 147 | 148 | #ifdef __cplusplus 149 | } 150 | #endif 151 | 152 | #endif 153 | -------------------------------------------------------------------------------- /libraries/dobby/source/InstructionRelocation/InstructionRelocation.h: -------------------------------------------------------------------------------- 1 | #include "dobby/dobby_internal.h" 2 | 3 | void GenRelocateCode(void *buffer, void* relocated_mem, CodeMemBlock *origin, CodeMemBlock *relocated, bool branch, void (*writer)(void*, void const*, size_t)); 4 | 5 | void GenRelocateCodeAndBranch(void *buffer, void* relocated_mem, CodeMemBlock *origin, CodeMemBlock *relocated, void (*writer)(void*, void const*, size_t)); 6 | -------------------------------------------------------------------------------- /libraries/dobby/source/InstructionRelocation/arm64/InstructionRelocationARM64.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "dobby/dobby_internal.h" 4 | 5 | #include "core/arch/arm64/constants-arm64.h" 6 | 7 | #if 0 8 | namespace zz { 9 | namespace arm64 { 10 | void GenRelocateCodeAndBranch(void *buffer, CodeMemBlock *origin, CodeMemBlock *relocated, void (*writer)(void*, void const*, size_t)); 11 | } // namespace arm64 12 | } // namespace zz 13 | #endif -------------------------------------------------------------------------------- /libraries/dobby/source/InstructionRelocation/arm64/inst_constants.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #if 0 4 | enum LoadRegLiteralOp { 5 | LoadRegLiteralFixed = 0x18000000, 6 | LoadRegLiteralFixedMask = 0x3B000000, 7 | LoadRegLiteralMask = 0xFF000000, 8 | }; 9 | 10 | // PC relative addressing. 11 | enum PCRelAddressingOp { 12 | PCRelAddressingFixed = 0x10000000, 13 | PCRelAddressingFixedMask = 0x1F000000, 14 | PCRelAddressingMask = 0x9F000000, 15 | ADR = PCRelAddressingFixed | 0x00000000, 16 | ADRP = PCRelAddressingFixed | 0x80000000 17 | }; 18 | 19 | // Unconditional branch. 20 | enum UnconditionalBranchOp { 21 | UnconditionalBranchFixed = 0x14000000, 22 | UnconditionalBranchFixedMask = 0x7C000000, 23 | UnconditionalBranchMask = 0xFC000000, 24 | 25 | B = UnconditionalBranchFixed | 0x00000000, 26 | BL = UnconditionalBranchFixed | 0x80000000 27 | }; 28 | #endif 29 | 30 | // Compare and branch. 31 | enum CompareBranchOp { 32 | CompareBranchFixed = 0x34000000, 33 | CompareBranchFixedMask = 0x7E000000, 34 | CompareBranchMask = 0xFF000000, 35 | }; 36 | 37 | // Conditional branch. 38 | enum ConditionalBranchOp { 39 | ConditionalBranchFixed = 0x54000000, 40 | ConditionalBranchFixedMask = 0xFE000000, 41 | ConditionalBranchMask = 0xFF000010, 42 | }; 43 | 44 | // Test and branch. 45 | enum TestBranchOp { 46 | TestBranchFixed = 0x36000000, 47 | TestBranchFixedMask = 0x7E000000, 48 | TestBranchMask = 0x7F000000, 49 | }; -------------------------------------------------------------------------------- /libraries/dobby/source/InstructionRelocation/arm64/inst_decode_encode_kit.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "dobby/common.h" 4 | 5 | static inline int64_t SignExtend(unsigned long x, int M, int N) { 6 | #if 1 7 | char sign_bit = bit(x, M - 1); 8 | unsigned long sign_mask = 0 - sign_bit; 9 | x |= ((sign_mask >> M) << M); 10 | #else 11 | x = (long)((long)x << (N - M)) >> (N - M); 12 | #endif 13 | return (int64_t)x; 14 | } 15 | 16 | static inline int64_t decode_imm14_offset(uint32_t instr) { 17 | int64_t offset; 18 | { 19 | int64_t imm14 = bits(instr, 5, 18); 20 | offset = (imm14 << 2); 21 | } 22 | offset = SignExtend(offset, 2 + 14, 64); 23 | return offset; 24 | } 25 | static inline uint32_t encode_imm14_offset(uint32_t instr, int64_t offset) { 26 | uint32_t imm14 = bits((offset >> 2), 0, 13); 27 | set_bits(instr, 5, 18, imm14); 28 | return instr; 29 | } 30 | 31 | static inline int64_t decode_imm19_offset(uint32_t instr) { 32 | int64_t offset; 33 | { 34 | int64_t imm19 = bits(instr, 5, 23); 35 | offset = (imm19 << 2); 36 | } 37 | offset = SignExtend(offset, 2 + 19, 64); 38 | return offset; 39 | } 40 | 41 | static inline uint32_t encode_imm19_offset(uint32_t instr, int64_t offset) { 42 | uint32_t imm19 = bits((offset >> 2), 0, 18); 43 | set_bits(instr, 5, 23, imm19); 44 | return instr; 45 | } 46 | 47 | static inline int64_t decode_imm26_offset(uint32_t instr) { 48 | int64_t offset; 49 | { 50 | int64_t imm26 = bits(instr, 0, 25); 51 | offset = (imm26 << 2); 52 | } 53 | offset = SignExtend(offset, 2 + 26, 64); 54 | return offset; 55 | } 56 | static inline uint32_t encode_imm26_offset(uint32_t instr, int64_t offset) { 57 | uint32_t imm26 = bits((offset >> 2), 0, 25); 58 | set_bits(instr, 0, 25, imm26); 59 | return instr; 60 | } 61 | 62 | static inline int64_t decode_immhi_immlo_offset(uint32_t instr) { 63 | typedef uint32_t instr_t; 64 | struct { 65 | instr_t Rd : 5; // Destination register 66 | instr_t immhi : 19; // 19-bit upper immediate 67 | instr_t dummy_0 : 5; // Must be 10000 == 0x10 68 | instr_t immlo : 2; // 2-bit lower immediate 69 | instr_t op : 1; // 0 = ADR, 1 = ADRP 70 | } instr_decode; 71 | 72 | *(instr_t *)&instr_decode = instr; 73 | 74 | int64_t imm = instr_decode.immlo + (instr_decode.immhi << 2); 75 | imm = SignExtend(imm, 2 + 19, 64); 76 | return imm; 77 | } 78 | static inline uint32_t encode_immhi_immlo_offset(uint32_t instr, int64_t offset) { 79 | struct { 80 | uint32_t Rd : 5; // Destination register 81 | uint32_t immhi : 19; // 19-bit upper immediate 82 | uint32_t dummy_0 : 5; // Must be 10000 == 0x10 83 | uint32_t immlo : 2; // 2-bit lower immediate 84 | uint32_t op : 1; // 0 = ADR, 1 = ADRP 85 | } instr_decode; 86 | 87 | *(uint32_t *)&instr_decode = instr; 88 | instr_decode.immlo = bits(offset, 0, 2); 89 | instr_decode.immhi = bits(offset, 2, 2 + 19); 90 | 91 | return *(uint32_t *)&instr_decode; 92 | } 93 | 94 | static inline int64_t decode_immhi_immlo_zero12_offset(uint32_t instr) { 95 | int64_t imm = decode_immhi_immlo_offset(instr); 96 | imm = imm << 12; 97 | return imm; 98 | } 99 | static inline uint32_t encode_immhi_immlo_zero12_offset(uint32_t instr, int64_t offset) { 100 | offset = (offset >> 12); 101 | return encode_immhi_immlo_offset(instr, offset); 102 | } 103 | 104 | static inline int decode_rt(uint32_t instr) { 105 | return bits(instr, 0, 4); 106 | } 107 | 108 | static inline int decode_rd(uint32_t instr) { 109 | return bits(instr, 0, 4); 110 | } -------------------------------------------------------------------------------- /libraries/dobby/source/MemoryAllocator/AssemblyCodeBuilder.cc: -------------------------------------------------------------------------------- 1 | #include "MemoryAllocator/AssemblyCodeBuilder.h" 2 | 3 | #include "dobby/dobby_internal.h" 4 | #include "PlatformUnifiedInterface/ExecMemory/CodePatchTool.h" 5 | 6 | AssemblyCode *AssemblyCodeBuilder::FinalizeFromTurboAssembler(AssemblerBase *assembler, void (*writer)(void*, void const*, size_t)) { 7 | auto buffer = (CodeBufferBase *)assembler->GetCodeBuffer(); 8 | auto realized_addr = (addr_t)assembler->GetRealizedAddress(); 9 | #if defined(TEST_WITH_UNICORN) 10 | // impl: unicorn emulator map memory 11 | realized_addr = 0; 12 | #endif 13 | if (!realized_addr) { 14 | return nullptr; 15 | } 16 | 17 | // Realize the buffer code to the executable memory address, remove the external label, etc 18 | (*writer)((void *)realized_addr, buffer->GetBuffer(), buffer->GetBufferSize()); 19 | 20 | auto block = new AssemblyCode(realized_addr, buffer->GetBufferSize()); 21 | return block; 22 | } -------------------------------------------------------------------------------- /libraries/dobby/source/MemoryAllocator/AssemblyCodeBuilder.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "PlatformUnifiedInterface/MemoryAllocator.h" 4 | 5 | #include "core/assembler/assembler.h" 6 | 7 | using namespace zz; 8 | 9 | using AssemblyCode = CodeMemBlock; 10 | 11 | class AssemblyCodeBuilder { 12 | public: 13 | static AssemblyCode *FinalizeFromTurboAssembler(AssemblerBase *assembler, void (*writer)(void*, void const*, size_t)); 14 | }; 15 | -------------------------------------------------------------------------------- /libraries/dobby/source/MemoryAllocator/CodeBuffer/CodeBufferBase.cc: -------------------------------------------------------------------------------- 1 | #include "MemoryAllocator/CodeBuffer/CodeBufferBase.h" 2 | 3 | CodeBufferBase *CodeBufferBase::Copy() { 4 | CodeBufferBase *result = new CodeBufferBase(); 5 | result->EmitBuffer(GetBuffer(), GetBufferSize()); 6 | return result; 7 | } 8 | 9 | void CodeBufferBase::Emit8(uint8_t data) { 10 | Emit(data); 11 | } 12 | 13 | void CodeBufferBase::Emit16(uint16_t data) { 14 | Emit(data); 15 | } 16 | 17 | void CodeBufferBase::Emit32(uint32_t data) { 18 | Emit(data); 19 | } 20 | 21 | void CodeBufferBase::Emit64(uint64_t data) { 22 | Emit(data); 23 | } 24 | 25 | void CodeBufferBase::EmitBuffer(uint8_t *buffer, int buffer_size) { 26 | buffer_.insert(buffer_.end(), buffer, buffer + buffer_size); 27 | } 28 | 29 | uint8_t *CodeBufferBase::GetBuffer() { 30 | return buffer_.data(); 31 | } 32 | 33 | size_t CodeBufferBase::GetBufferSize() { 34 | return buffer_.size(); 35 | } 36 | 37 | #if 0 // Template Advanced won't enable even in userspace 38 | template T CodeBufferBase::Load(int offset) { 39 | return *reinterpret_cast(buffer + offset); 40 | } 41 | 42 | template void CodeBufferBase::Store(int offset, T value) { 43 | *reinterpret_cast(buffer + offset) = value; 44 | } 45 | 46 | template void CodeBufferBase::Emit(T value) { 47 | // Ensure the free space enough for the template T value 48 | ensureCapacity(sizeof(T) + GetBufferSize()); 49 | 50 | *reinterpret_cast(buffer_cursor) = value; 51 | buffer_cursor += sizeof(T); 52 | } 53 | #endif 54 | -------------------------------------------------------------------------------- /libraries/dobby/source/MemoryAllocator/CodeBuffer/CodeBufferBase.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "dobby/common.h" 4 | 5 | class CodeBufferBase { 6 | public: 7 | CodeBufferBase() { 8 | } 9 | 10 | public: 11 | virtual CodeBufferBase *Copy(); 12 | 13 | void Emit8(uint8_t data); 14 | 15 | void Emit16(uint16_t data); 16 | 17 | void Emit32(uint32_t data); 18 | 19 | void Emit64(uint64_t data); 20 | 21 | template T Load(int offset) { 22 | return *(T *)(buffer_.data() + offset); 23 | } 24 | 25 | template void Store(int offset, T value) { 26 | *(T *)(buffer_.data() + offset) = value; 27 | } 28 | 29 | template void Emit(T value) { 30 | EmitBuffer((uint8_t *)&value, sizeof(value)); 31 | } 32 | 33 | void EmitBuffer(uint8_t *buffer, int len); 34 | 35 | uint8_t *GetBuffer(); 36 | size_t GetBufferSize(); 37 | 38 | private: 39 | tinystl::vector buffer_; 40 | }; 41 | -------------------------------------------------------------------------------- /libraries/dobby/source/MemoryAllocator/CodeBuffer/code_buffer_arm.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "MemoryAllocator/CodeBuffer/CodeBufferBase.h" 4 | 5 | typedef int32_t arm_inst_t; 6 | typedef int16_t thumb1_inst_t; 7 | typedef int32_t thumb2_inst_t; 8 | 9 | class CodeBuffer : public CodeBufferBase { 10 | enum ExecuteState { ARMExecuteState, ThumbExecuteState }; 11 | 12 | public: 13 | CodeBuffer() : CodeBufferBase() { 14 | } 15 | 16 | public: 17 | arm_inst_t LoadARMInst(uint32_t offset) { 18 | return *reinterpret_cast(GetBuffer() + offset); 19 | } 20 | 21 | thumb1_inst_t LoadThumb1Inst(uint32_t offset) { 22 | return *reinterpret_cast(GetBuffer() + offset); 23 | } 24 | 25 | thumb2_inst_t LoadThumb2Inst(uint32_t offset) { 26 | return *reinterpret_cast(GetBuffer() + offset); 27 | } 28 | 29 | void RewriteAddr(uint32_t offset, addr32_t addr) { 30 | memcpy(GetBuffer() + offset, &addr, sizeof(addr)); 31 | } 32 | 33 | void RewriteARMInst(uint32_t offset, arm_inst_t instr) { 34 | *reinterpret_cast(GetBuffer() + offset) = instr; 35 | } 36 | 37 | void RewriteThumb1Inst(uint32_t offset, thumb1_inst_t instr) { 38 | *reinterpret_cast(GetBuffer() + offset) = instr; 39 | } 40 | 41 | void RewriteThumb2Inst(uint32_t offset, thumb2_inst_t instr) { 42 | memcpy(GetBuffer() + offset, &instr, sizeof(instr)); 43 | } 44 | 45 | void EmitARMInst(arm_inst_t instr) { 46 | Emit(instr); 47 | } 48 | 49 | void EmitThumb1Inst(thumb1_inst_t instr) { 50 | Emit(instr); 51 | } 52 | 53 | void EmitThumb2Inst(thumb2_inst_t instr) { 54 | Emit(instr); 55 | } 56 | }; -------------------------------------------------------------------------------- /libraries/dobby/source/MemoryAllocator/CodeBuffer/code_buffer_arm64.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "MemoryAllocator/CodeBuffer/CodeBufferBase.h" 4 | 5 | typedef int32_t arm64_inst_t; 6 | 7 | class CodeBuffer : public CodeBufferBase { 8 | 9 | public: 10 | CodeBuffer() : CodeBufferBase() { 11 | } 12 | 13 | public: 14 | arm64_inst_t LoadInst(uint32_t offset) { 15 | return *reinterpret_cast(GetBuffer() + offset); 16 | } 17 | 18 | void RewriteInst(uint32_t offset, arm64_inst_t instr) { 19 | *reinterpret_cast(GetBuffer() + offset) = instr; 20 | } 21 | }; -------------------------------------------------------------------------------- /libraries/dobby/source/PlatformUnifiedInterface/ExecMemory/ClearCacheTool.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifdef __cplusplus 4 | extern "C" { 5 | #endif 6 | 7 | void ClearCache(void *start, void *end); 8 | 9 | #ifdef __cplusplus 10 | } 11 | #endif -------------------------------------------------------------------------------- /libraries/dobby/source/PlatformUnifiedInterface/ExecMemory/CodePatchTool.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | int DobbyCodePatch(void *address, uint8_t *buffer, uint32_t buffer_size); -------------------------------------------------------------------------------- /libraries/dobby/source/PlatformUnifiedInterface/MemoryAllocator.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "dobby/common.h" 4 | 5 | struct MemRange { 6 | addr_t start; 7 | addr_t end; 8 | size_t size; 9 | 10 | MemRange(addr_t start, size_t size) : start(start), end(0), size(size) { 11 | end = start + size; 12 | } 13 | 14 | void reset(addr_t start, size_t size) { 15 | this->start = start; 16 | this->size = size; 17 | end = start + size; 18 | } 19 | }; 20 | 21 | struct MemBlock : MemRange { 22 | addr_t addr; 23 | 24 | MemBlock() : MemRange(0, 0), addr(0) { 25 | } 26 | 27 | MemBlock(addr_t addr, size_t size) : MemRange(addr, size), addr(addr) { 28 | } 29 | 30 | void reset(addr_t addr, size_t size) { 31 | MemRange::reset(addr, size); 32 | this->addr = addr; 33 | } 34 | }; 35 | 36 | struct MemoryArena : MemRange { 37 | addr_t addr; 38 | addr_t cursor_addr; 39 | 40 | tinystl::vector memory_blocks; 41 | 42 | MemoryArena(addr_t addr, size_t size) : MemRange(addr, size), addr(addr), cursor_addr(addr) { 43 | } 44 | 45 | virtual MemBlock *allocMemBlock(size_t size); 46 | }; 47 | 48 | using CodeMemBlock = MemBlock; 49 | using CodeMemoryArena = MemoryArena; 50 | 51 | #if 0 52 | struct CodeMemoryArena : MemoryArena { 53 | CodeMemoryArena(addr_t addr, size_t size) : MemoryArena(addr, size) { 54 | } 55 | 56 | CodeMemBlock *allocateCodeMemBlock(size_t size) { 57 | return allocMemBlock(size); 58 | } 59 | }; 60 | #endif 61 | 62 | using DataMemBlock = MemBlock; 63 | using DataMemoryArena = MemoryArena; 64 | 65 | #if 0 66 | struct DataMemoryArena : MemoryArena { 67 | public: 68 | DataMemoryArena(addr_t addr, size_t size) : MemoryArena(addr, size) { 69 | } 70 | 71 | DataMemBlock *allocateDataMemBlock(size_t size) { 72 | return allocMemBlock(size); 73 | } 74 | }; 75 | #endif 76 | 77 | class NearMemoryAllocator; 78 | class MemoryAllocator { 79 | friend class NearMemoryAllocator; 80 | 81 | private: 82 | tinystl::vector code_arenas; 83 | tinystl::vector data_arenas; 84 | 85 | private: 86 | static MemoryAllocator *shared_allocator; 87 | 88 | public: 89 | static MemoryAllocator *SharedAllocator(); 90 | 91 | public: 92 | CodeMemoryArena *allocateCodeMemoryArena(uint32_t size); 93 | CodeMemBlock *allocateExecBlock(uint32_t size); 94 | uint8_t *allocateExecMemory(uint32_t size); 95 | uint8_t *allocateExecMemory(uint8_t *buffer, uint32_t buffer_size); 96 | 97 | DataMemoryArena *allocateDataMemoryArena(uint32_t size); 98 | DataMemBlock *allocateDataBlock(uint32_t size); 99 | uint8_t *allocateDataMemory(uint32_t size); 100 | uint8_t *allocateDataMemory(uint8_t *buffer, uint32_t buffer_size); 101 | }; -------------------------------------------------------------------------------- /libraries/dobby/source/core/arch/Cpu.h: -------------------------------------------------------------------------------- 1 | #ifndef CORE_ARCH_CPU_H 2 | #define CORE_ARCH_CPU_H 3 | 4 | #include "CpuRegister.h" 5 | #include "CpuFeature.h" 6 | 7 | #endif 8 | -------------------------------------------------------------------------------- /libraries/dobby/source/core/arch/CpuFeature.cc: -------------------------------------------------------------------------------- 1 | 2 | #include "core/arch/CpuFeature.h" 3 | 4 | void CpuFeatures::ClearCache(void *start, void *end) { 5 | UNIMPLEMENTED(); 6 | } -------------------------------------------------------------------------------- /libraries/dobby/source/core/arch/CpuFeature.h: -------------------------------------------------------------------------------- 1 | #ifndef CORE_ARCH_CPU_FEATURE_H 2 | #define CORE_ARCH_CPU_FEATURE_H 3 | 4 | #include "dobby/common.h" 5 | 6 | class CpuFeatures { 7 | private: 8 | static void FlushICache(void *start, size_t size) { 9 | ClearCache(start, (void *)((addr_t)start + size)); 10 | } 11 | 12 | static void FlushICache(void *start, void *end) { 13 | ClearCache(start, end); 14 | } 15 | 16 | static void ClearCache(void *start, void *end); 17 | }; 18 | 19 | #endif 20 | -------------------------------------------------------------------------------- /libraries/dobby/source/core/arch/CpuRegister.cc: -------------------------------------------------------------------------------- 1 | 2 | #include "CpuRegister.h" 3 | 4 | constexpr RegisterBase RegisterBase::from_code(int code) { 5 | return RegisterBase{code}; 6 | } 7 | 8 | constexpr RegisterBase RegisterBase::no_reg() { 9 | return RegisterBase{0}; 10 | } -------------------------------------------------------------------------------- /libraries/dobby/source/core/arch/CpuRegister.h: -------------------------------------------------------------------------------- 1 | #ifndef CORE_ARCH_CPU_REGISTER_H 2 | #define CORE_ARCH_CPU_REGISTER_H 3 | 4 | class RegisterBase { 5 | public: 6 | static constexpr RegisterBase from_code(int code); 7 | 8 | static constexpr RegisterBase no_reg(); 9 | 10 | virtual bool Is(const RegisterBase ®) const { 11 | return (reg.reg_code_ == this->reg_code_); 12 | } 13 | 14 | int code() const { 15 | return reg_code_; 16 | }; 17 | 18 | protected: 19 | explicit constexpr RegisterBase(int code) : reg_code_(code) { 20 | } 21 | 22 | int reg_code_; 23 | }; 24 | 25 | #endif 26 | -------------------------------------------------------------------------------- /libraries/dobby/source/core/arch/arm/constants-arm.h: -------------------------------------------------------------------------------- 1 | #ifndef CORE_ARCH_CONSTANTS_ARM_H 2 | #define CORE_ARCH_CONSTANTS_ARM_H 3 | 4 | enum AddrMode { Offset = 0, PreIndex = 1, PostIndex = 2 }; 5 | 6 | enum Condition { 7 | EQ = 0, // equal 8 | NE = 1, // not equal 9 | CS = 2, // carry set/unsigned higher or same 10 | CC = 3, // carry clear/unsigned lower 11 | MI = 4, // minus/negative 12 | PL = 5, // plus/positive or zero 13 | VS = 6, // overflow 14 | VC = 7, // no overflow 15 | HI = 8, // unsigned higher 16 | LS = 9, // unsigned lower or same 17 | GE = 10, // signed greater than or equal 18 | LT = 11, // signed less than 19 | GT = 12, // signed greater than 20 | LE = 13, // signed less than or equal 21 | AL = 14, // always (unconditional) 22 | 23 | }; 24 | 25 | enum Shift { 26 | LSL = 0, // Logical shift left 27 | LSR = 1, // Logical shift right 28 | ASR = 2, // Arithmetic shift right 29 | ROR = 3, // Rotate right 30 | }; 31 | 32 | enum { 33 | B0 = 1 << 0, 34 | B4 = 1 << 4, 35 | B5 = 1 << 5, 36 | B6 = 1 << 6, 37 | B7 = 1 << 7, 38 | B8 = 1 << 8, 39 | B9 = 1 << 9, 40 | B10 = 1 << 10, 41 | B12 = 1 << 12, 42 | B14 = 1 << 14, 43 | B15 = 1 << 15, 44 | B16 = 1 << 16, 45 | B17 = 1 << 17, 46 | B18 = 1 << 18, 47 | B19 = 1 << 19, 48 | B20 = 1 << 20, 49 | B21 = 1 << 21, 50 | B22 = 1 << 22, 51 | B23 = 1 << 23, 52 | B24 = 1 << 24, 53 | B25 = 1 << 25, 54 | B26 = 1 << 26, 55 | B27 = 1 << 27, 56 | B28 = 1 << 28, 57 | }; 58 | 59 | enum InstructionFields { 60 | // Registers. 61 | kRdShift = 12, 62 | kRtShift = 12, 63 | kRmShift = 10, 64 | kRnShift = 16, 65 | 66 | // Condition 67 | kConditionShift = 28, 68 | }; 69 | 70 | #endif -------------------------------------------------------------------------------- /libraries/dobby/source/core/arch/arm/registers-arm.h: -------------------------------------------------------------------------------- 1 | #ifndef ARCH_ARM_REGISTERS 2 | #define ARCH_ARM_REGISTERS 3 | 4 | #include "core/arch/arm/constants-arm.h" 5 | #include "core/arch/Cpu.h" 6 | 7 | namespace zz { 8 | namespace arm { 9 | 10 | #define GENERAL_REGISTERS(V) \ 11 | V(r0) V(r1) V(r2) V(r3) V(r4) V(r5) V(r6) V(r7) V(r8) V(r9) V(r10) V(r11) V(r12) V(sp) V(lr) V(pc) 12 | 13 | enum RegisterCode { 14 | #define REGISTER_CODE(R) kRegCode_##R, 15 | GENERAL_REGISTERS(REGISTER_CODE) 16 | #undef REGISTER_CODE 17 | kRegAfterLast 18 | }; 19 | 20 | class Register : public RegisterBase { 21 | public: 22 | explicit constexpr Register(int code) : RegisterBase(code) { 23 | } 24 | 25 | static constexpr Register Create(int code) { 26 | return Register(code); 27 | } 28 | 29 | static constexpr Register R(int code) { 30 | return Register(code); 31 | } 32 | 33 | bool Is(const Register ®) const { 34 | return (reg.reg_code_ == this->reg_code_); 35 | } 36 | 37 | bool IsValid() const { 38 | return (reg_code_ != 0); 39 | } 40 | 41 | int code() const { 42 | return reg_code_; 43 | } 44 | 45 | private: 46 | }; 47 | 48 | typedef Register CPURegister; 49 | 50 | #define DECLARE_REGISTER(R) constexpr Register R = Register::Create(kRegCode_##R); 51 | GENERAL_REGISTERS(DECLARE_REGISTER) 52 | #undef DECLARE_REGISTER 53 | 54 | constexpr Register no_reg = Register::Create(0); 55 | 56 | } // namespace arm 57 | } // namespace zz 58 | #endif -------------------------------------------------------------------------------- /libraries/dobby/source/core/arch/arm64/registers-arm64.h: -------------------------------------------------------------------------------- 1 | #ifndef ARCH_ARM64_REGISTERS 2 | #define ARCH_ARM64_REGISTERS 3 | 4 | #include "core/arch/arm64/constants-arm64.h" 5 | #include "core/arch/Cpu.h" 6 | 7 | namespace zz { 8 | namespace arm64 { 9 | 10 | class CPURegister : RegisterBase { 11 | public: 12 | enum RegisterType { 13 | kRegister_32, 14 | kRegister_W = kRegister_32, 15 | kRegister_64, 16 | kRegister_X = kRegister_64, 17 | kRegister, 18 | 19 | kVRegister, 20 | kSIMD_FP_Register_8, 21 | kSIMD_FP_Register_B = kSIMD_FP_Register_8, 22 | kSIMD_FP_Register_16, 23 | kSIMD_FP_Register_H = kSIMD_FP_Register_16, 24 | kSIMD_FP_Register_32, 25 | kSIMD_FP_Register_S = kSIMD_FP_Register_32, 26 | kSIMD_FP_Register_64, 27 | kSIMD_FP_Register_D = kSIMD_FP_Register_64, 28 | kSIMD_FP_Register_128, 29 | kSIMD_FP_Register_Q = kSIMD_FP_Register_128, 30 | 31 | kInvalid 32 | }; 33 | 34 | constexpr CPURegister(int code, int size, RegisterType type) : RegisterBase(code), reg_size_(size), reg_type_(type) { 35 | } 36 | 37 | static constexpr CPURegister Create(int code, int size, RegisterType type) { 38 | return CPURegister(code, size, type); 39 | } 40 | 41 | // ===== 42 | 43 | static constexpr CPURegister X(int code) { 44 | return CPURegister(code, 64, kRegister_64); 45 | } 46 | 47 | static constexpr CPURegister W(int code) { 48 | return CPURegister(code, 32, kRegister_32); 49 | } 50 | 51 | static constexpr CPURegister Q(int code) { 52 | return CPURegister(code, 128, kSIMD_FP_Register_128); 53 | } 54 | 55 | static constexpr CPURegister InvalidRegister() { 56 | return CPURegister(0, 0, kInvalid); 57 | } 58 | 59 | // ===== 60 | 61 | bool Is(const CPURegister ®) const { 62 | return (reg.reg_code_ == this->reg_code_); 63 | } 64 | 65 | bool Is64Bits() const { 66 | return reg_size_ == 64; 67 | } 68 | 69 | bool IsRegister() const { 70 | return reg_type_ < kRegister; 71 | } 72 | 73 | bool IsVRegister() const { 74 | return reg_type_ > kVRegister; 75 | } 76 | 77 | // ===== 78 | 79 | RegisterType type() const { 80 | return reg_type_; 81 | } 82 | 83 | int32_t code() const { 84 | return reg_code_; 85 | }; 86 | 87 | private: 88 | RegisterType reg_type_; 89 | int reg_size_; 90 | }; 91 | 92 | typedef CPURegister Register; 93 | typedef CPURegister VRegister; 94 | 95 | // clang-format off 96 | #define GENERAL_REGISTER_CODE_LIST(R) \ 97 | R(0) R(1) R(2) R(3) R(4) R(5) R(6) R(7) \ 98 | R(8) R(9) R(10) R(11) R(12) R(13) R(14) R(15) \ 99 | R(16) R(17) R(18) R(19) R(20) R(21) R(22) R(23) \ 100 | R(24) R(25) R(26) R(27) R(28) R(29) R(30) R(31) 101 | 102 | #define DEFINE_REGISTER(register_class, name, ...) constexpr register_class name = register_class::Create(__VA_ARGS__) 103 | 104 | #define DEFINE_REGISTERS(N) \ 105 | DEFINE_REGISTER(Register, w##N, N, 32, CPURegister::kRegister_32); \ 106 | DEFINE_REGISTER(Register, x##N, N, 64, CPURegister::kRegister_64); 107 | GENERAL_REGISTER_CODE_LIST(DEFINE_REGISTERS) 108 | #undef DEFINE_REGISTERS 109 | 110 | #define DEFINE_VREGISTERS(N) \ 111 | DEFINE_REGISTER(VRegister, b##N, N, 8, CPURegister::kSIMD_FP_Register_8); \ 112 | DEFINE_REGISTER(VRegister, h##N, N, 16, CPURegister::kSIMD_FP_Register_16); \ 113 | DEFINE_REGISTER(VRegister, s##N, N, 32, CPURegister::kSIMD_FP_Register_32); \ 114 | DEFINE_REGISTER(VRegister, d##N, N, 64, CPURegister::kSIMD_FP_Register_64); \ 115 | DEFINE_REGISTER(VRegister, q##N, N, 128, CPURegister::kSIMD_FP_Register_128); \ 116 | GENERAL_REGISTER_CODE_LIST(DEFINE_VREGISTERS) 117 | #undef DEFINE_VREGISTERS 118 | 119 | #undef DEFINE_REGISTER 120 | // clang-format on 121 | 122 | // ===== 123 | 124 | constexpr Register wzr = w31; 125 | constexpr Register xzr = x31; 126 | 127 | constexpr Register SP = x31; 128 | constexpr Register wSP = w31; 129 | constexpr Register FP = x29; 130 | constexpr Register wFP = w29; 131 | constexpr Register LR = x30; 132 | constexpr Register wLR = w30; 133 | 134 | } // namespace arm64 135 | } // namespace zz 136 | 137 | #define W(code) CPURegister::W(code) 138 | #define X(code) CPURegister::X(code) 139 | #define Q(code) CPURegister::Q(code) 140 | #define InvalidRegister CPURegister::InvalidRegister() 141 | 142 | #endif 143 | -------------------------------------------------------------------------------- /libraries/dobby/source/core/assembler/AssemblerPseudoLabel.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "MemoryAllocator/CodeBuffer/CodeBufferBase.h" 4 | 5 | class Label { 6 | public: 7 | Label(addr_t addr) : pos_(addr) { 8 | } 9 | 10 | protected: 11 | addr_t pos_; 12 | }; 13 | 14 | class AssemblerPseudoLabel : public Label { 15 | public: 16 | typedef struct { 17 | int link_type; 18 | size_t pc_offset; 19 | addr_t vmaddr_; 20 | } ref_label_insn_t; 21 | 22 | public: 23 | AssemblerPseudoLabel(addr_t addr) : Label(addr) { 24 | ref_label_insns_.reserve(4); 25 | 26 | bind_to(addr); 27 | } 28 | 29 | bool has_confused_instructions() { 30 | return ref_label_insns_.size(); 31 | } 32 | 33 | void link_confused_instructions(); 34 | 35 | void link_confused_instructions(CodeBufferBase *buffer_); 36 | 37 | void link_to(int link_type, uint32_t pc_offset) { 38 | ref_label_insn_t insn; 39 | insn.link_type = link_type; 40 | insn.pc_offset = pc_offset; 41 | ref_label_insns_.push_back(insn); 42 | } 43 | 44 | public: 45 | addr_t pos() { 46 | return pos_; 47 | }; 48 | 49 | void bind_to(addr_t addr) { 50 | pos_ = addr; 51 | } 52 | 53 | protected: 54 | tinystl::vector ref_label_insns_; 55 | }; 56 | 57 | struct RelocLabel : public AssemblerPseudoLabel { 58 | public: 59 | RelocLabel() : AssemblerPseudoLabel(0) { 60 | memset(data_, 0, sizeof(data_)); 61 | data_size_ = 0; 62 | } 63 | 64 | template static RelocLabel *withData(T value) { 65 | auto label = new RelocLabel(); 66 | label->setData(value); 67 | return label; 68 | } 69 | 70 | template T data() { 71 | return *(T *)data_; 72 | } 73 | 74 | template void setData(T value) { 75 | data_size_ = sizeof(T); 76 | memcpy(data_, &value, data_size_); 77 | } 78 | 79 | template void fixupData(T value) { 80 | *(T *)data_ = value; 81 | } 82 | 83 | uint8_t data_[8]; 84 | int data_size_; 85 | }; -------------------------------------------------------------------------------- /libraries/dobby/source/core/assembler/assembler-arm.cc: -------------------------------------------------------------------------------- 1 | #include "platform_detect_macro.h" 2 | #if TARGET_ARCH_ARM 3 | 4 | #include "core/assembler/assembler-arm.h" 5 | 6 | void AssemblerPseudoLabel::link_confused_instructions(CodeBufferBase *buffer) { 7 | CodeBuffer *_buffer = (CodeBuffer *)buffer; 8 | 9 | for (auto &ref_label_insn : ref_label_insns_) { 10 | arm_inst_t inst = _buffer->LoadARMInst(ref_label_insn.pc_offset); 11 | if (ref_label_insn.link_type == kLdrLiteral) { 12 | int64_t pc = ref_label_insn.pc_offset + ARM_PC_OFFSET; 13 | assert(pc % 4 == 0); 14 | int32_t imm12 = pos() - pc; 15 | if (imm12 > 0) { 16 | set_bit(inst, 23, 1); 17 | } else { 18 | set_bit(inst, 23, 0); 19 | imm12 = -imm12; 20 | } 21 | set_bits(inst, 0, 11, imm12); 22 | } 23 | _buffer->RewriteARMInst(ref_label_insn.pc_offset, inst); 24 | } 25 | } 26 | 27 | namespace zz { 28 | namespace arm { 29 | 30 | void Assembler::EmitARMInst(arm_inst_t instr) { 31 | buffer_->EmitARMInst(instr); 32 | } 33 | 34 | void Assembler::EmitAddress(uint32_t value) { 35 | buffer_->Emit32(value); 36 | } 37 | 38 | } // namespace arm 39 | } // namespace zz 40 | 41 | #endif 42 | -------------------------------------------------------------------------------- /libraries/dobby/source/core/assembler/assembler-arm64.cc: -------------------------------------------------------------------------------- 1 | #include "platform_detect_macro.h" 2 | #if TARGET_ARCH_ARM64 3 | 4 | #include "core/assembler/assembler-arm64.h" 5 | 6 | void AssemblerPseudoLabel::link_confused_instructions(CodeBufferBase *buffer_) { 7 | auto buffer = (CodeBuffer *)buffer_; 8 | 9 | for (auto &ref_label_insn : ref_label_insns_) { 10 | int64_t fixup_offset = pos() - ref_label_insn.pc_offset; 11 | 12 | arm64_inst_t inst = buffer->LoadInst(ref_label_insn.pc_offset); 13 | arm64_inst_t new_inst = 0; 14 | 15 | if (ref_label_insn.link_type == kLabelImm19) { 16 | new_inst = encode_imm19_offset(inst, fixup_offset); 17 | } 18 | 19 | buffer->RewriteInst(ref_label_insn.pc_offset, new_inst); 20 | } 21 | } 22 | 23 | using namespace zz::arm64; 24 | 25 | #endif 26 | -------------------------------------------------------------------------------- /libraries/dobby/source/core/assembler/assembler.cc: -------------------------------------------------------------------------------- 1 | #include "core/assembler/assembler.h" 2 | 3 | namespace zz { 4 | 5 | const void *ExternalReference::address() { 6 | return address_; 7 | } 8 | 9 | AssemblerBase::AssemblerBase(void *address) { 10 | realized_addr_ = address; 11 | buffer_ = nullptr; 12 | } 13 | 14 | AssemblerBase::~AssemblerBase() { 15 | buffer_ = nullptr; 16 | } 17 | 18 | size_t AssemblerBase::ip_offset() const { 19 | return reinterpret_cast(buffer_)->GetBufferSize(); 20 | } 21 | 22 | size_t AssemblerBase::pc_offset() const { 23 | return reinterpret_cast(buffer_)->GetBufferSize(); 24 | } 25 | 26 | CodeBuffer *AssemblerBase::GetCodeBuffer() { 27 | return buffer_; 28 | } 29 | 30 | void AssemblerBase::PseudoBind(AssemblerPseudoLabel *label) { 31 | auto pc_offset = reinterpret_cast(buffer_)->GetBufferSize(); 32 | label->bind_to(pc_offset); 33 | if (label->has_confused_instructions()) { 34 | label->link_confused_instructions(reinterpret_cast(buffer_)); 35 | } 36 | } 37 | 38 | void AssemblerBase::RelocBind() { 39 | for (auto *data_label : data_labels_) { 40 | PseudoBind(data_label); 41 | reinterpret_cast(buffer_)->EmitBuffer(data_label->data_, data_label->data_size_); 42 | } 43 | } 44 | 45 | void AssemblerBase::AppendRelocLabel(RelocLabel *label) { 46 | data_labels_.push_back(label); 47 | } 48 | 49 | void AssemblerBase::SetRealizedAddress(void *address) { 50 | realized_addr_ = address; 51 | } 52 | 53 | void *AssemblerBase::GetRealizedAddress() { 54 | return realized_addr_; 55 | } 56 | 57 | void AssemblerBase::FlushICache(addr_t start, int size) { 58 | } 59 | 60 | void AssemblerBase::FlushICache(addr_t start, addr_t end) { 61 | } 62 | 63 | } // namespace zz 64 | -------------------------------------------------------------------------------- /libraries/dobby/source/core/assembler/assembler.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "MemoryAllocator/CodeBuffer/CodeBufferBase.h" 4 | 5 | #include "AssemblerPseudoLabel.h" 6 | 7 | class CodeBuffer; 8 | 9 | namespace zz { 10 | 11 | class ExternalReference { 12 | public: 13 | explicit ExternalReference(void *address) : address_(address) { 14 | #if defined(__APPLE__) && __arm64e__ 15 | address_ = pac_strip((void *)address_); 16 | #endif 17 | } 18 | 19 | const void *address(); 20 | 21 | private: 22 | const void *address_; 23 | }; 24 | 25 | class AssemblerBase { 26 | public: 27 | explicit AssemblerBase(void *address); 28 | 29 | ~AssemblerBase(); 30 | 31 | size_t ip_offset() const; 32 | 33 | size_t pc_offset() const; 34 | 35 | CodeBuffer *GetCodeBuffer(); 36 | 37 | void PseudoBind(AssemblerPseudoLabel *label); 38 | 39 | void RelocBind(); 40 | 41 | void AppendRelocLabel(RelocLabel *label); 42 | 43 | protected: 44 | tinystl::vector data_labels_; 45 | 46 | public: 47 | virtual void *GetRealizedAddress(); 48 | 49 | virtual void SetRealizedAddress(void *address); 50 | 51 | static void FlushICache(addr_t start, int size); 52 | 53 | static void FlushICache(addr_t start, addr_t end); 54 | 55 | protected: 56 | CodeBuffer *buffer_; 57 | 58 | void *realized_addr_; 59 | }; 60 | 61 | } // namespace zz 62 | 63 | #if 0 64 | #include "globals.h" 65 | #if TARGET_ARCH_ARM 66 | #include "core/assembler/assembler-arm.h" 67 | #elif TARGET_ARCH_ARM64 68 | #include "core/assembler/assembler-arm64.h" 69 | #elif TARGET_ARCH_X64 70 | #include "core/assembler/assembler-x64.h" 71 | #else 72 | #error "unsupported architecture" 73 | #endif 74 | #endif -------------------------------------------------------------------------------- /libraries/dobby/source/core/codegen/codegen-arm.cc: -------------------------------------------------------------------------------- 1 | #include "platform_detect_macro.h" 2 | #if defined(TARGET_ARCH_ARM) 3 | 4 | #include "core/codegen/codegen-arm.h" 5 | 6 | namespace zz { 7 | namespace arm { 8 | 9 | void CodeGen::LiteralLdrBranch(uint32_t address) { 10 | TurboAssembler *turbo_assembler_ = reinterpret_cast(this->assembler_); 11 | #define _ turbo_assembler_-> 12 | _ ldr(pc, MemOperand(pc, -4)); 13 | turbo_assembler_->GetCodeBuffer()->Emit32((addr_t)address); 14 | } 15 | 16 | } // namespace arm 17 | } // namespace zz 18 | 19 | #endif -------------------------------------------------------------------------------- /libraries/dobby/source/core/codegen/codegen-arm.h: -------------------------------------------------------------------------------- 1 | #ifndef CORE_CODEGEN_ARM_H 2 | #define CORE_CODEGEN_ARM_H 3 | 4 | #include "core/codegen/codegen.h" 5 | #include "core/assembler/assembler.h" 6 | #include "core/assembler/assembler-arm.h" 7 | 8 | namespace zz { 9 | namespace arm { 10 | 11 | class CodeGen : public CodeGenBase { 12 | public: 13 | CodeGen(TurboAssembler *turbo_assembler) : CodeGenBase(turbo_assembler) { 14 | } 15 | 16 | void LiteralLdrBranch(uint32_t address); 17 | }; 18 | 19 | } // namespace arm 20 | } // namespace zz 21 | 22 | #endif -------------------------------------------------------------------------------- /libraries/dobby/source/core/codegen/codegen-arm64.cc: -------------------------------------------------------------------------------- 1 | #include "platform_detect_macro.h" 2 | #if defined(TARGET_ARCH_ARM64) 3 | 4 | #include "dobby/dobby_internal.h" 5 | #include "core/codegen/codegen-arm64.h" 6 | 7 | namespace zz { 8 | namespace arm64 { 9 | 10 | void CodeGen::LiteralLdrBranch(uint64_t address) { 11 | auto turbo_assembler_ = reinterpret_cast(this->assembler_); 12 | #define _ turbo_assembler_-> 13 | 14 | auto label = RelocLabel::withData(address); 15 | turbo_assembler_->AppendRelocLabel(label); 16 | 17 | _ Ldr(TMP_REG_0, label); 18 | _ br(TMP_REG_0); 19 | 20 | #undef _ 21 | } 22 | 23 | } // namespace arm64 24 | } // namespace zz 25 | 26 | #endif 27 | -------------------------------------------------------------------------------- /libraries/dobby/source/core/codegen/codegen-arm64.h: -------------------------------------------------------------------------------- 1 | #ifndef CORE_CODEGEN_ARM64_H 2 | #define CORE_CODEGEN_ARM64_H 3 | 4 | #include "core/codegen/codegen.h" 5 | #include "core/assembler/assembler.h" 6 | #include "core/assembler/assembler-arm64.h" 7 | 8 | namespace zz { 9 | namespace arm64 { 10 | 11 | class CodeGen : public CodeGenBase { 12 | public: 13 | CodeGen(TurboAssembler *turbo_assembler) : CodeGenBase(turbo_assembler) { 14 | } 15 | void LiteralLdrBranch(uint64_t address); 16 | }; 17 | 18 | } // namespace arm64 19 | } // namespace zz 20 | 21 | #endif -------------------------------------------------------------------------------- /libraries/dobby/source/core/codegen/codegen.h: -------------------------------------------------------------------------------- 1 | #ifndef CORE_CODEGEN_H 2 | #define CORE_CODEGEN_H 3 | 4 | #include "core/assembler/assembler.h" 5 | 6 | using namespace zz; 7 | 8 | class CodeGenBase { 9 | public: 10 | CodeGenBase(AssemblerBase *assembler) : assembler_(assembler) { 11 | } 12 | 13 | protected: 14 | AssemblerBase *assembler_; 15 | }; 16 | 17 | #endif -------------------------------------------------------------------------------- /libraries/dobby/source/dobby/common.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "dobby.h" 4 | #include "dobby/types.h" 5 | #include "dobby/platform_features.h" 6 | #include "dobby/platform_detect_macro.h" 7 | #include "dobby/utility_macro.h" 8 | #include "dobby/pac_kit.h" -------------------------------------------------------------------------------- /libraries/dobby/source/dobby/dobby_internal.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "dobby/common.h" 4 | 5 | //#include "UnifiedInterface/platform.h" 6 | 7 | //#include "PlatformUnifiedInterface/MemoryAllocator.h" 8 | //#include "PlatformUnifiedInterface/ExecMemory/CodePatchTool.h" 9 | //#include "PlatformUnifiedInterface/ExecMemory/ClearCacheTool.h" 10 | 11 | #include "MemoryAllocator/AssemblyCodeBuilder.h" 12 | 13 | //#include "InterceptRouting/InterceptRouting.h" -------------------------------------------------------------------------------- /libraries/dobby/source/dobby/pac_kit.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #if defined(__arm64e__) && __has_feature(ptrauth_calls) 6 | #include 7 | #endif 8 | 9 | static inline void *pac_strip(void *addr) { 10 | if (addr == NULL) { 11 | return NULL; 12 | } 13 | #if __has_feature(ptrauth_calls) 14 | addr = ptrauth_strip(addr, ptrauth_key_asia); 15 | #endif 16 | return addr; 17 | } 18 | 19 | static inline void *pac_sign(void *addr) { 20 | if (addr == NULL) { 21 | return NULL; 22 | } 23 | #if __has_feature(ptrauth_calls) 24 | addr = ptrauth_sign_unauthenticated((void *)addr, ptrauth_key_asia, 0); 25 | #endif 26 | return addr; 27 | } 28 | 29 | static inline void *pac_strip_and_sign(void *addr) { 30 | return pac_sign(pac_strip(addr)); 31 | } -------------------------------------------------------------------------------- /libraries/dobby/source/dobby/platform_detect_macro.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #if !defined(DISABLE_ARCH_DETECT) 4 | #if defined(__arm__) 5 | #define TARGET_ARCH_ARM 1 6 | #elif defined(__arm64__) || defined(__aarch64__) 7 | #define TARGET_ARCH_ARM64 1 8 | #elif defined(_M_IX86) || defined(__i386__) 9 | #define TARGET_ARCH_IA32 1 10 | #elif defined(_M_X64) || defined(__x86_64__) 11 | #define TARGET_ARCH_X64 1 12 | #else 13 | #error Target architecture was not detected as supported by Dobby 14 | #endif 15 | #endif 16 | -------------------------------------------------------------------------------- /libraries/dobby/source/dobby/platform_features.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #if defined(__APPLE__) && __arm64e__ 4 | #if __has_feature(ptrauth_calls) 5 | #include 6 | #endif 7 | #endif 8 | 9 | #if defined(BUILDING_KERNEL) 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #else 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #if defined(__linux__) || defined(__APPLE__) 27 | #include 28 | #include 29 | #endif 30 | #endif 31 | 32 | #if defined(BUILDING_KERNEL) 33 | #include "kernel_mode_header.h" 34 | #endif 35 | 36 | #if defined(BUILDING_KERNEL) 37 | #define abs(a) ((a) < 0 ? -(a) : (a)) 38 | #define llabs(a) (((long long)a) < 0 ? -((long long)a) : ((long long)a)) 39 | #define min(a, b) (((a) < (b)) ? (a) : (b)) 40 | #define max(a, b) (((a) > (b)) ? (a) : (b)) 41 | #ifdef __cplusplus 42 | #define abs(a) ((a) < 0 ? -(a) : (a)) 43 | #endif 44 | #else 45 | #ifdef __cplusplus 46 | // #include "TINYSTL/vector.h" 47 | // #include "TINYSTL/unordered_map.h" 48 | #include "TINYSTL/vector.h" 49 | #include "TINYSTL/unordered_map.h" 50 | #endif 51 | #endif -------------------------------------------------------------------------------- /libraries/dobby/source/dobby/types.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | typedef unsigned char byte_t; 6 | typedef unsigned int uint; 7 | 8 | #ifndef NULL 9 | #define NULL 0 10 | #endif 11 | -------------------------------------------------------------------------------- /libraries/dobby/source/dobby/utility_macro.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // offset of struct member 4 | #define OFFSETOF(TYPE, ELEMENT) ((size_t) & (((TYPE *)0)->ELEMENT)) 5 | 6 | // assert 7 | #define ASSERT(X) 8 | 9 | // left/right shift 10 | #define LeftShift(a, b, c) ((a & ((1 << b) - 1)) << c) 11 | #define RightShift(a, b, c) ((a >> c) & ((1 << b) - 1)) 12 | 13 | // align 14 | #ifndef ALIGN 15 | #define ALIGN ALIGN_FLOOR 16 | #endif 17 | #define ALIGN_FLOOR(address, range) ((uintptr_t)address & ~((uintptr_t)range - 1)) 18 | #define ALIGN_CEIL(address, range) (((uintptr_t)address + (uintptr_t)range - 1) & ~((uintptr_t)range - 1)) 19 | 20 | // borrow from gdb, refer: binutils-gdb/gdb/arch/arm.h 21 | #define submask(x) ((1L << ((x) + 1)) - 1) 22 | #define bits(obj, st, fn) (((obj) >> (st)) & submask((fn) - (st))) 23 | #define bit(obj, st) (((obj) >> (st)) & 1) 24 | #define sbits(obj, st, fn) ((long)(bits(obj, st, fn) | ((long)bit(obj, fn) * ~submask(fn - st)))) 25 | 26 | // make it easy 27 | #define set_bit(obj, st, bit) obj = (((~(1 << st)) & obj) | (bit << st)) 28 | #define set_bits(obj, st, fn, bits) obj = (((~(submask(fn - st) << st)) & obj) | (bits << st)) 29 | 30 | // definition to expand macro then apply to pragma message 31 | // #pragma message(VAR_NAME_VALUE(HOST_OS_IOS)) 32 | #define VALUE_TO_STRING(x) #x 33 | #define VALUE(x) VALUE_TO_STRING(x) 34 | #define VAR_NAME_VALUE(var) #var "=" VALUE(var) 35 | 36 | // format print 37 | #ifdef __LP64__ 38 | #define __PRI_64_prefix "l" 39 | #define __PRI_PTR_prefix "l" 40 | #else 41 | #define __PRI_64_prefix "ll" 42 | #define __PRI_PTR_prefix 43 | #endif 44 | #define PRIxPTR __PRI_PTR_prefix "x" /* uintptr_t */ 45 | 46 | // deprecated declared 47 | #if defined(__GNUC__) || defined(__clang__) 48 | #define DEPRECATED __attribute__((deprecated)) 49 | #elif defined(_MSC_VER) 50 | #define DEPRECATED __declspec(deprecated) 51 | #else 52 | #pragma message("WARNING: You need to implement DEPRECATED for this compiler") 53 | #define DEPRECATED 54 | #endif 55 | 56 | // export method 57 | #if defined(_WIN32) 58 | #define PUBLIC 59 | #else 60 | #define PUBLIC __attribute__((visibility("default"))) 61 | #define INTERNAL __attribute__((visibility("internal"))) 62 | #endif 63 | 64 | #define DCHECK(...) 65 | #define DCHECK_EQ(...) 66 | #define UNREACHABLE(...) do {} while (true) 67 | #define DEBUG_LOG(...) 68 | #define UNIMPLEMENTED(...) -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.21) 2 | 3 | project(TulipHook LANGUAGES CXX C VERSION 2.4.2) 4 | 5 | file(GLOB TULIP_HOOK_BASE_SOURCES 6 | *.cpp 7 | assembler/BaseAssembler.cpp 8 | convention/CallingConvention.cpp 9 | convention/DefaultConvention.cpp 10 | generator/Generator.cpp 11 | target/Target.cpp 12 | ) 13 | 14 | if(WIN32) 15 | set(TULIP_HOOK_LINK_DOBBY Off) 16 | set(TULIP_HOOK_LINK_CAPSTONE On) 17 | file(GLOB TULIP_HOOK_PLATFORM_SOURCES 18 | assembler/X86Assembler.cpp 19 | assembler/X64Assembler.cpp 20 | convention/Windows32Convention.cpp 21 | convention/Windows64Convention.cpp 22 | generator/X86Generator.cpp 23 | generator/X64Generator.cpp 24 | target/Windows32Target.cpp 25 | target/Windows64Target.cpp 26 | ) 27 | elseif(APPLE) 28 | set(TULIP_HOOK_LINK_DOBBY On) 29 | set(TULIP_HOOK_LINK_CAPSTONE On) 30 | file(GLOB TULIP_HOOK_PLATFORM_SOURCES 31 | assembler/X86Assembler.cpp 32 | assembler/X64Assembler.cpp 33 | assembler/ArmV8Assembler.cpp 34 | convention/MacosIntelConvention.cpp 35 | generator/X86Generator.cpp 36 | generator/X64Generator.cpp 37 | generator/ArmV8Generator.cpp 38 | target/DarwinTarget.cpp 39 | target/MacosIntelTarget.cpp 40 | target/MacosM1Target.cpp 41 | ) 42 | elseif(ANDROID OR UNIX) 43 | set(TULIP_HOOK_LINK_DOBBY On) 44 | set(TULIP_HOOK_LINK_CAPSTONE Off) 45 | file(GLOB TULIP_HOOK_PLATFORM_SOURCES 46 | assembler/ArmV7Assembler.cpp 47 | assembler/ArmV8Assembler.cpp 48 | generator/ArmV7Generator.cpp 49 | generator/ArmV8Generator.cpp 50 | target/PosixTarget.cpp 51 | target/PosixArmV7Target.cpp 52 | target/PosixArmV8Target.cpp 53 | ) 54 | else() 55 | message(FATAL_ERROR "Unsupported platform.") 56 | endif() 57 | 58 | add_library(${PROJECT_NAME} STATIC 59 | ${TULIP_HOOK_BASE_SOURCES} 60 | ${TULIP_HOOK_PLATFORM_SOURCES} 61 | ) 62 | 63 | target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_20) 64 | 65 | if (MSVC) 66 | target_compile_options(${PROJECT_NAME} PUBLIC /EHsc) 67 | endif () 68 | 69 | if (TULIP_HOOK_LINK_DOBBY) 70 | target_link_libraries(${PROJECT_NAME} PRIVATE Dobby) 71 | message(STATUS "${Dobby_SOURCE_DIR}/source") 72 | endif() 73 | 74 | if (TULIP_HOOK_LINK_CAPSTONE) 75 | set(CAPSTONE_INSTALL Off) 76 | set(CAPSTONE_ARCHITECTURE_DEFAULT Off) 77 | 78 | set(CAPSTONE_X86_SUPPORT On) 79 | set(CAPSTONE_X86_REDUCE Off) 80 | set(CAPSTONE_X86_ATT_DISABLE On) 81 | 82 | CPMAddPackage("gh:geode-sdk/capstone#d2a14e5") 83 | target_link_libraries(${PROJECT_NAME} PRIVATE capstone) 84 | target_include_directories(${PROJECT_NAME} PRIVATE ${capstone_SOURCE_DIR}/include) 85 | endif() 86 | 87 | target_link_libraries(${PROJECT_NAME} PUBLIC GeodeResult) 88 | target_compile_definitions(${PROJECT_NAME} PRIVATE -DTULIP_HOOK_EXPORTING=1) 89 | 90 | target_include_directories(${PROJECT_NAME} PRIVATE 91 | ${TulipHookRoot_SOURCE_DIR}/include 92 | ${TulipHookRoot_SOURCE_DIR}/include/tulip 93 | ) 94 | 95 | target_include_directories(${PROJECT_NAME} INTERFACE 96 | ${TulipHookRoot_SOURCE_DIR}/include 97 | ) 98 | -------------------------------------------------------------------------------- /src/Handler.cpp: -------------------------------------------------------------------------------- 1 | #include "Handler.hpp" 2 | 3 | #include "Hook.hpp" 4 | #include "Pool.hpp" 5 | #include "Wrapper.hpp" 6 | #include "target/PlatformTarget.hpp" 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | using namespace tulip::hook; 13 | 14 | Handler::Handler(void* address, HandlerMetadata const& metadata) : 15 | m_address(address), 16 | m_metadata(metadata) {} 17 | 18 | geode::Result> Handler::create(void* address, HandlerMetadata const& metadata) { 19 | auto ret = std::make_unique(address, metadata); 20 | 21 | ret->m_content = new (std::nothrow) HandlerContent(); 22 | if (!ret->m_content) { 23 | return geode::Err("Failed to allocate HandlerContent"); 24 | } 25 | 26 | GEODE_UNWRAP_INTO(ret->m_handler, Target::get().allocateArea(0x300)); 27 | GEODE_UNWRAP_INTO(ret->m_trampoline, Target::get().allocateArea(0x100)); 28 | 29 | return geode::Ok(std::move(ret)); 30 | } 31 | 32 | Handler::~Handler() {} 33 | 34 | geode::Result<> Handler::init() { 35 | // printf("func addr: 0x%" PRIx64 "\n", (uint64_t)m_address); 36 | 37 | auto generator = 38 | Target::get().getHandlerGenerator(m_address, m_trampoline, m_handler, m_content, m_metadata); 39 | 40 | GEODE_UNWRAP_INTO(auto handler, generator->generateHandler()); 41 | m_handlerSize = handler.m_size; 42 | 43 | GEODE_UNWRAP_INTO(auto minIntervener, generator->generateIntervener(0)); 44 | 45 | GEODE_UNWRAP_INTO(auto trampoline, generator->generateTrampoline(minIntervener.size())); 46 | m_trampolineSize = trampoline.m_trampoline.m_size; 47 | 48 | GEODE_UNWRAP_INTO(m_modifiedBytes, generator->generateIntervener(trampoline.m_originalOffset)); 49 | 50 | auto target = m_modifiedBytes.size(); 51 | 52 | auto address = reinterpret_cast(Target::get().getRealPtr(m_address)); 53 | m_originalBytes.insert(m_originalBytes.begin(), address, address + target); 54 | 55 | this->addOriginal(); 56 | 57 | return geode::Ok(); 58 | } 59 | 60 | void Handler::addOriginal() { 61 | auto metadata = HookMetadata{ 62 | .m_priority = INT32_MAX, 63 | }; 64 | static_cast(this->createHook(Target::get().getRealPtrAs(m_trampoline, m_address), metadata)); 65 | } 66 | 67 | HookHandle Handler::createHook(void* address, HookMetadata m_metadata) { 68 | static size_t s_nextHookHandle = 0; 69 | auto hook = HookHandle(++s_nextHookHandle); 70 | 71 | m_hooks.emplace(hook, std::make_unique(address, m_metadata)); 72 | m_handles.insert({address, hook}); 73 | 74 | m_content->m_functions.push_back(address); 75 | 76 | this->reorderFunctions(); 77 | 78 | return hook; 79 | } 80 | 81 | void Handler::removeHook(HookHandle const& hook) { 82 | if (m_hooks.count(hook) == 0) { 83 | return; 84 | } 85 | auto address = m_hooks[hook]->m_address; 86 | 87 | m_hooks.erase(hook); 88 | m_handles.erase(address); 89 | 90 | auto& vec = m_content->m_functions; 91 | 92 | vec.erase(std::remove(vec.begin(), vec.end(), address), vec.end()); 93 | } 94 | 95 | void Handler::clearHooks() { 96 | m_hooks.clear(); 97 | m_handles.clear(); 98 | m_content->m_functions.clear(); 99 | 100 | this->addOriginal(); 101 | } 102 | 103 | void Handler::updateHookMetadata(HookHandle const& hook, HookMetadata const& metadata) { 104 | if (m_hooks.count(hook) == 0) { 105 | return; 106 | } 107 | m_hooks.at(hook)->m_metadata = metadata; 108 | this->reorderFunctions(); 109 | } 110 | 111 | void Handler::reorderFunctions() { 112 | auto& vec = m_content->m_functions; 113 | std::sort(vec.begin(), vec.end(), [this](auto const a, auto const b) { 114 | return (m_hooks.at(m_handles[a])->m_metadata.m_priority < m_hooks.at(m_handles[b])->m_metadata.m_priority); 115 | }); 116 | } 117 | 118 | geode::Result<> Handler::interveneFunction() { 119 | return Target::get().writeMemory( 120 | Target::get().getRealPtr(m_address), 121 | static_cast(m_modifiedBytes.data()), 122 | m_modifiedBytes.size() 123 | ); 124 | } 125 | 126 | geode::Result<> Handler::restoreFunction() { 127 | return Target::get().writeMemory( 128 | Target::get().getRealPtr(m_address), 129 | static_cast(m_originalBytes.data()), 130 | m_originalBytes.size() 131 | ); 132 | } 133 | 134 | static thread_local std::stack s_indexStack; 135 | static thread_local std::stack s_addressStack; 136 | static thread_local std::stack s_dataStack; 137 | 138 | void Handler::incrementIndex(HandlerContent* content) { 139 | if (s_addressStack.size() == 0 || s_addressStack.top() != content) { 140 | // new entry 141 | s_addressStack.push(content); 142 | s_indexStack.push(0); 143 | } 144 | else { 145 | ++s_indexStack.top(); 146 | } 147 | } 148 | 149 | void Handler::decrementIndex() { 150 | if (s_indexStack.top() == 0) { 151 | s_addressStack.pop(); 152 | s_indexStack.pop(); 153 | } 154 | else { 155 | --s_indexStack.top(); 156 | } 157 | } 158 | 159 | void* Handler::getNextFunction(HandlerContent* content) { 160 | auto& vec = content->m_functions; 161 | auto ret = vec[s_indexStack.top() % vec.size()]; 162 | return ret; 163 | } 164 | 165 | void* Handler::popData() { 166 | auto ret = s_dataStack.top(); 167 | s_dataStack.pop(); 168 | return ret; 169 | } 170 | 171 | void Handler::pushData(void* data) { 172 | s_dataStack.push(data); 173 | } -------------------------------------------------------------------------------- /src/Handler.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | namespace tulip::hook { 13 | class Hook; 14 | 15 | struct HandlerContent { 16 | std::vector m_functions; 17 | }; 18 | 19 | class Handler final { 20 | public: 21 | Handler(void* address, HandlerMetadata const& metadata); 22 | 23 | void* const m_address; 24 | HandlerMetadata const m_metadata; 25 | 26 | std::unordered_map> m_hooks; 27 | std::unordered_map m_handles; 28 | 29 | HandlerContent* m_content = nullptr; 30 | 31 | void* m_trampoline = nullptr; 32 | size_t m_trampolineSize = 0; 33 | 34 | void* m_handler = nullptr; 35 | size_t m_handlerSize = 0; 36 | 37 | std::vector m_originalBytes; 38 | std::vector m_modifiedBytes; 39 | 40 | static geode::Result> create(void* address, HandlerMetadata const& metadata); 41 | ~Handler(); 42 | 43 | geode::Result<> init(); 44 | 45 | HookHandle createHook(void* address, HookMetadata m_metadata); 46 | void removeHook(HookHandle const& hook); 47 | 48 | void clearHooks(); 49 | 50 | void addOriginal(); 51 | 52 | void reorderFunctions(); 53 | 54 | void updateHookMetadata(HookHandle const& hook, HookMetadata const& metadata); 55 | 56 | static bool TULIP_HOOK_DEFAULT_CONV symbolResolver(char const* symbol, uint64_t* value); 57 | 58 | static void incrementIndex(HandlerContent* content); 59 | static void decrementIndex(); 60 | static void* getNextFunction(HandlerContent* content); 61 | 62 | static void* popData(); 63 | static void pushData(void* data); 64 | 65 | geode::Result<> interveneFunction(); 66 | geode::Result<> restoreFunction(); 67 | }; 68 | } -------------------------------------------------------------------------------- /src/Hook.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace tulip::hook { 7 | 8 | class Hook { 9 | public: 10 | HookMetadata m_metadata; 11 | 12 | void* m_address; 13 | 14 | Hook(void* address, HookMetadata metadata) : m_address(address), m_metadata(metadata) {} 15 | 16 | ~Hook() {} 17 | }; 18 | } -------------------------------------------------------------------------------- /src/Main.cpp: -------------------------------------------------------------------------------- 1 | #include "Handler.hpp" 2 | #include "Misc.hpp" 3 | #include "Pool.hpp" 4 | #include "Wrapper.hpp" 5 | #include "target/PlatformTarget.hpp" 6 | 7 | #include 8 | 9 | using namespace tulip::hook; 10 | 11 | geode::Result tulip::hook::createHandler(void* address, HandlerMetadata const& metadata) noexcept { 12 | return Pool::get().createHandler(address, metadata); 13 | } 14 | 15 | geode::Result<> tulip::hook::removeHandler(HandlerHandle const& handler) noexcept { 16 | return Pool::get().removeHandler(handler); 17 | } 18 | 19 | HookHandle tulip::hook::createHook(HandlerHandle const& handler, void* function, HookMetadata const& metadata) noexcept { 20 | return Pool::get().getHandler(handler).createHook(function, metadata); 21 | } 22 | 23 | void tulip::hook::removeHook(HandlerHandle const& handler, HookHandle const& hook) noexcept { 24 | return Pool::get().getHandler(handler).removeHook(hook); 25 | } 26 | 27 | void tulip::hook::updateHookMetadata( 28 | HandlerHandle const& handler, HookHandle const& hook, HookMetadata const& metadata 29 | ) noexcept { 30 | return Pool::get().getHandler(handler).updateHookMetadata(hook, metadata); 31 | } 32 | 33 | geode::Result<> tulip::hook::writeMemory(void* destination, void const* source, size_t size) noexcept { 34 | return Target::get().writeMemory(destination, source, size); 35 | } 36 | 37 | geode::Result tulip::hook::followJumps(void* address) noexcept { 38 | return Misc::followJumps(address); 39 | } 40 | 41 | geode::Result tulip::hook::createWrapper(void* address, WrapperMetadata const& metadata) noexcept { 42 | return Wrapper::get().createWrapper(address, metadata); 43 | } 44 | 45 | geode::Result tulip::hook::createReverseWrapper(void* address, WrapperMetadata const& metadata) noexcept { 46 | return Wrapper::get().createReverseWrapper(address, metadata); 47 | } 48 | 49 | std::shared_ptr tulip::hook::createConvention(TulipConvention convention) noexcept { 50 | switch (convention) { 51 | #if defined(TULIP_HOOK_WINDOWS) && defined(TULIP_HOOK_X86) 52 | case TulipConvention::Cdecl: return CdeclConvention::create(); 53 | case TulipConvention::Thiscall: return ThiscallConvention::create(); 54 | case TulipConvention::Fastcall: return FastcallConvention::create(); 55 | case TulipConvention::Optcall: return OptcallConvention::create(); 56 | case TulipConvention::Membercall: return MembercallConvention::create(); 57 | case TulipConvention::Stdcall: return StdcallConvention::create(); 58 | #endif 59 | #if defined(TULIP_HOOK_WINDOWS) && defined(TULIP_HOOK_X64) 60 | case TulipConvention::Thiscall: return ThiscallConvention::create(); 61 | #endif 62 | case TulipConvention::Default: 63 | default: 64 | #if defined(TULIP_HOOK_MACOS) && defined(TULIP_HOOK_X64) 65 | return SystemVConvention::create(); 66 | #elif defined(TULIP_HOOK_WINDOWS) && defined(TULIP_HOOK_X64) 67 | return Windows64Convention::create(); 68 | #else 69 | return DefaultConvention::create(); 70 | #endif 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/Misc.cpp: -------------------------------------------------------------------------------- 1 | #include "Misc.hpp" 2 | 3 | using namespace tulip::hook; 4 | 5 | geode::Result Misc::followJumps(void* address) { 6 | return geode::Err("Implement followJumps in platform"); 7 | } -------------------------------------------------------------------------------- /src/Misc.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace tulip::hook { 6 | 7 | class Misc { 8 | public: 9 | static geode::Result followJumps(void* address); 10 | }; 11 | } -------------------------------------------------------------------------------- /src/Pool.cpp: -------------------------------------------------------------------------------- 1 | #include "Pool.hpp" 2 | 3 | #include "Handler.hpp" 4 | 5 | using namespace tulip::hook; 6 | 7 | Pool& Pool::get() { 8 | static Pool ret; 9 | return ret; 10 | } 11 | 12 | geode::Result Pool::createHandler(void* address, HandlerMetadata const& metadata) { 13 | auto handle = reinterpret_cast(address); 14 | 15 | if (m_handlers.find(handle) == m_handlers.end()) { 16 | GEODE_UNWRAP_INTO(auto handler, Handler::create(address, metadata)); 17 | m_handlers.emplace(handle, std::move(handler)); 18 | GEODE_UNWRAP(m_handlers[handle]->init()); 19 | } 20 | 21 | GEODE_UNWRAP(m_handlers[handle]->interveneFunction()); 22 | 23 | return geode::Ok(std::move(handle)); 24 | } 25 | 26 | geode::Result<> Pool::removeHandler(HandlerHandle const& handle) { 27 | if (m_handlers.find(handle) == m_handlers.end()) { 28 | return geode::Err("Handler not found"); 29 | } 30 | m_handlers[handle]->clearHooks(); 31 | GEODE_UNWRAP(m_handlers[handle]->restoreFunction()); 32 | return geode::Ok(); 33 | } 34 | 35 | Handler& Pool::getHandler(HandlerHandle const& handle) { 36 | return *m_handlers.at(handle); 37 | } 38 | -------------------------------------------------------------------------------- /src/Pool.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace tulip::hook { 9 | class Handler; 10 | 11 | class Pool { 12 | public: 13 | std::unordered_map> m_handlers; 14 | 15 | static Pool& get(); 16 | 17 | geode::Result createHandler(void* address, HandlerMetadata const& metadata); 18 | geode::Result<> removeHandler(HandlerHandle const& handler); 19 | 20 | Handler& getHandler(HandlerHandle const& handler); 21 | }; 22 | } 23 | -------------------------------------------------------------------------------- /src/Wrapper.cpp: -------------------------------------------------------------------------------- 1 | #include "Wrapper.hpp" 2 | 3 | #include "target/PlatformTarget.hpp" 4 | 5 | using namespace tulip::hook; 6 | 7 | Wrapper& Wrapper::get() { 8 | static Wrapper ret; 9 | return ret; 10 | } 11 | 12 | geode::Result Wrapper::createWrapper(void* address, WrapperMetadata const& metadata) { 13 | if (m_wrappers.count(address) == 0) { 14 | auto generator = Target::get().getWrapperGenerator(address, metadata); 15 | GEODE_UNWRAP_INTO(auto wrapped, generator->generateWrapper()); 16 | m_wrappers[address] = wrapped; 17 | } 18 | 19 | return geode::Ok(m_wrappers[address].m_address); 20 | } 21 | 22 | geode::Result Wrapper::createReverseWrapper(void* address, WrapperMetadata const& metadata) { 23 | if (m_reverseWrappers.count(address) == 0) { 24 | auto generator = Target::get().getWrapperGenerator(address, metadata); 25 | GEODE_UNWRAP_INTO(auto wrapped, generator->generateReverseWrapper()); 26 | m_reverseWrappers[address] = wrapped; 27 | } 28 | 29 | return geode::Ok(m_reverseWrappers[address].m_address); 30 | } 31 | -------------------------------------------------------------------------------- /src/Wrapper.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace tulip::hook { 10 | class Handler; 11 | 12 | class Wrapper { 13 | public: 14 | std::unordered_map m_wrappers; 15 | std::unordered_map m_reverseWrappers; 16 | 17 | static Wrapper& get(); 18 | 19 | geode::Result createWrapper(void* address, WrapperMetadata const& metadata); 20 | geode::Result createReverseWrapper(void* address, WrapperMetadata const& metadata); 21 | }; 22 | } 23 | -------------------------------------------------------------------------------- /src/assembler/ArmV7Assembler.cpp: -------------------------------------------------------------------------------- 1 | #include "ArmV7Assembler.hpp" 2 | 3 | using namespace tulip::hook; 4 | 5 | ArmV7Assembler::ArmV7Assembler(int64_t baseAddress) : 6 | BaseAssembler(baseAddress) {} 7 | 8 | ArmV7Assembler::~ArmV7Assembler() {} 9 | 10 | uint8_t* ArmV7Assembler::lastInsn() { 11 | return &m_buffer[m_buffer.size() - 2]; 12 | } 13 | 14 | void ArmV7Assembler::rwl(int8_t offset, int8_t size, int32_t value) { 15 | auto address = this->lastInsn(); 16 | 17 | auto pointer = reinterpret_cast(address); 18 | 19 | auto mask = ((1 << size) - 1) << offset; 20 | 21 | *pointer = (*pointer & ~mask) | (value << offset); 22 | } 23 | 24 | int32_t arrayMaskLow(ArmV7RegisterArray const& array) { 25 | int32_t mask = 0; 26 | 27 | for (auto const& reg : array) { 28 | mask |= 1 << (int32_t)reg; 29 | } 30 | 31 | return mask; 32 | } 33 | 34 | int32_t val(ArmV7Register reg) { 35 | return (int32_t)reg & 0xf; 36 | } 37 | 38 | int32_t vall(ArmV7Register reg) { 39 | return (int32_t)reg & 0x7; 40 | } 41 | 42 | int32_t valh(ArmV7Register reg) { 43 | return ((int32_t)reg & 0x8) >> 3; 44 | } 45 | 46 | void ArmV7Assembler::label8(std::string const& name) { 47 | m_labelUpdates.push_back({this->currentAddress(), name, 1}); 48 | this->write8(0); 49 | } 50 | 51 | void ArmV7Assembler::updateLabels() { 52 | for (auto const& update : m_labelUpdates) { 53 | // this will technically fail but im lazy 54 | // we dont have enough wide instructions for me to care about 55 | auto aligned = (update.m_address & (~0x3)) + 0x4; 56 | auto diff = m_labels[update.m_name] - aligned; 57 | this->rewrite8(update.m_address, diff / 4); 58 | } 59 | } 60 | 61 | using enum ArmV7Register; 62 | 63 | void ArmV7Assembler::nop() { 64 | this->write16(0xbf00); 65 | } 66 | 67 | void ArmV7Assembler::push(ArmV7RegisterArray const& array) { 68 | this->write16(0xb400); 69 | this->rwl(0, 8, arrayMaskLow(array)); 70 | } 71 | 72 | void ArmV7Assembler::vpush(ArmV7RegisterArray const& array) { 73 | this->write16(0xed2d); 74 | this->write16(0x0b00); 75 | this->rwl(1, 7, array.size()); 76 | this->rwl(12, 4, val(array[0])); 77 | } 78 | 79 | void ArmV7Assembler::pop(ArmV7RegisterArray const& array) { 80 | this->write16(0xbc00); 81 | this->rwl(0, 8, arrayMaskLow(array)); 82 | } 83 | 84 | void ArmV7Assembler::vpop(ArmV7RegisterArray const& array) { 85 | this->write16(0xecbd); 86 | this->write16(0x0b00); 87 | this->rwl(1, 7, array.size()); 88 | this->rwl(12, 4, val(array[0])); 89 | } 90 | 91 | void ArmV7Assembler::ldr(ArmV7Register dst, std::string const& label) { 92 | this->label8(label); 93 | this->write8(0x48); 94 | this->rwl(8, 3, vall(dst)); 95 | } 96 | 97 | void ArmV7Assembler::ldrpcn() { 98 | this->write16(0xf85f); 99 | this->write16(0xf000); 100 | } 101 | 102 | void ArmV7Assembler::ldrpcn2() { 103 | this->write16(0xf004); 104 | this->write16(0xe51f); 105 | } 106 | 107 | void ArmV7Assembler::mov(ArmV7Register dst, ArmV7Register src) { 108 | this->write16(0x4600); 109 | this->rwl(0, 3, vall(dst)); 110 | this->rwl(3, 4, val(src)); 111 | this->rwl(7, 1, valh(dst)); 112 | } 113 | 114 | void ArmV7Assembler::blx(ArmV7Register dst) { 115 | this->write16(0x4780); 116 | this->rwl(3, 4, val(dst)); 117 | } 118 | 119 | void ArmV7Assembler::bx(ArmV7Register dst) { 120 | this->write16(0x4700); 121 | this->rwl(3, 4, val(dst)); 122 | } 123 | -------------------------------------------------------------------------------- /src/assembler/ArmV7Assembler.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "BaseAssembler.hpp" 4 | 5 | namespace tulip::hook { 6 | 7 | // there are more but idc 8 | enum class ArmV7Register : uint8_t { 9 | R0 = 0x0, 10 | R1, 11 | R2, 12 | R3, 13 | R4, 14 | R5, 15 | R6, 16 | R7, 17 | R8, 18 | R9, 19 | R10, 20 | R11, 21 | R12, 22 | SP, 23 | LR, 24 | PC, 25 | D0 = 0x40, 26 | D1, 27 | D2, 28 | D3, 29 | D4, 30 | D5, 31 | D6, 32 | D7, 33 | }; 34 | 35 | using ArmV7RegisterArray = std::vector; 36 | 37 | class ArmV7Assembler : public BaseAssembler { 38 | public: 39 | ArmV7Assembler(int64_t baseAddress); 40 | ArmV7Assembler(ArmV7Assembler const&) = delete; 41 | ArmV7Assembler(ArmV7Assembler&&) = delete; 42 | ~ArmV7Assembler(); 43 | 44 | virtual uint8_t* lastInsn(); 45 | void rwl(int8_t offset, int8_t size, int32_t value); 46 | 47 | void label8(std::string const& name); 48 | void updateLabels() override; 49 | 50 | void nop(); 51 | 52 | void push(ArmV7RegisterArray const& array); 53 | void vpush(ArmV7RegisterArray const& array); 54 | 55 | void pop(ArmV7RegisterArray const& array); 56 | void vpop(ArmV7RegisterArray const& array); 57 | 58 | void ldr(ArmV7Register dst, std::string const& label); 59 | 60 | // ldr.w pc, [pc, #-0x4] 61 | // i cant bother to do stuff 62 | void ldrpcn(); 63 | 64 | // non thumb version 65 | void ldrpcn2(); 66 | 67 | void mov(ArmV7Register dst, ArmV7Register src); 68 | 69 | void blx(ArmV7Register dst); 70 | void bx(ArmV7Register src); 71 | }; 72 | } -------------------------------------------------------------------------------- /src/assembler/ArmV8Assembler.cpp: -------------------------------------------------------------------------------- 1 | #include "ArmV8Assembler.hpp" 2 | 3 | using namespace tulip::hook; 4 | 5 | ArmV8Assembler::ArmV8Assembler(int64_t baseAddress) : 6 | BaseAssembler(baseAddress) {} 7 | 8 | ArmV8Assembler::~ArmV8Assembler() {} 9 | 10 | void ArmV8Assembler::updateLabels() { 11 | // Handle LDR 12 | for (auto const& update : m_labelUpdates) { 13 | const auto diff = m_labels[update.m_name] - update.m_address; 14 | const auto opc = this->read32(update.m_address); 15 | this->rewrite32(update.m_address, opc | ((diff >> 2) << 5)); 16 | } 17 | } 18 | 19 | using enum ArmV8Register; 20 | 21 | static bool is_simd(ArmV8Register reg) { return static_cast(reg) >= 0x40; } 22 | 23 | static uint32_t val(ArmV8Register reg) { 24 | auto x = static_cast(reg); 25 | if (is_simd(reg)) 26 | x -= 0x40; 27 | return x; 28 | } 29 | 30 | void ArmV8Assembler::mov(ArmV8Register dst, ArmV8Register src) { 31 | const auto srcShifted = val(src) << 16; 32 | this->write32(0xAA0003E0 | srcShifted | val(dst)); 33 | } 34 | 35 | void ArmV8Assembler::ldr(ArmV8Register dst, std::string const& label) { 36 | m_labelUpdates.push_back({this->currentAddress(), label, 4}); 37 | this->write32((0x58ul << 24) | val(dst)); 38 | } 39 | 40 | void ArmV8Assembler::ldp(ArmV8Register reg1, ArmV8Register reg2, ArmV8Register regBase, int16_t imm, ArmV8IndexKind kind) { 41 | using enum ArmV8IndexKind; 42 | 43 | uint32_t opc = 0; 44 | const auto use_simd = is_simd(reg1) && is_simd(reg2); 45 | 46 | switch (kind) { 47 | case PreIndex: 48 | opc = (use_simd ? 0x1B7 : 0x2A7) << 22; 49 | break; 50 | case PostIndex: 51 | opc = (use_simd ? 0x1B3 : 0x2A3) << 22; 52 | break; 53 | case SignedOffset: 54 | opc = (use_simd ? 0x1B5 : 0x2A5) << 22; 55 | break; 56 | } 57 | 58 | const auto reg2Shifted = val(reg2) << 10; 59 | const auto regBaseShifted = val(regBase) << 5; 60 | const auto immShifted = static_cast((imm >> 3) & 0x7F) << 15; 61 | 62 | this->write32(opc | reg2Shifted | regBaseShifted | immShifted | val(reg1)); 63 | } 64 | 65 | void ArmV8Assembler::stp(ArmV8Register reg1, ArmV8Register reg2, ArmV8Register regBase, int16_t imm, ArmV8IndexKind kind) { 66 | using enum ArmV8IndexKind; 67 | 68 | uint32_t opc = 0; 69 | const auto use_simd = is_simd(reg1) && is_simd(reg2); 70 | 71 | switch (kind) { 72 | case PreIndex: 73 | opc = (use_simd ? 0x1B6 : 0x2A6) << 22; 74 | break; 75 | case PostIndex: 76 | opc = (use_simd ? 0x1B2 : 0x2A2) << 22; 77 | break; 78 | case SignedOffset: 79 | opc = (use_simd ? 0x1B4 : 0x2A4) << 22; 80 | break; 81 | } 82 | 83 | const auto reg2Shifted = (val(reg2) << 10); 84 | const auto regBaseShifted = (val(regBase) << 5); 85 | const auto immShifted = static_cast((imm >> 3) & 0x7F) << 15; 86 | 87 | this->write32(opc | reg2Shifted | regBaseShifted | immShifted | val(reg1)); 88 | } 89 | 90 | void ArmV8Assembler::adrp(ArmV8Register dst, int64_t imm) { 91 | const auto immlo = ((imm >> 12) & 3ull) << 29; 92 | const auto immhi = ((imm >> 14) & 0x7ffffull) << 5; 93 | this->write32(0x90000000 | immlo | immhi | val(dst)); 94 | } 95 | 96 | void ArmV8Assembler::add(ArmV8Register dst, ArmV8Register src, uint16_t imm) { 97 | const auto srcShifted = val(src) << 5; 98 | const auto immShifted = static_cast(imm) << 10; 99 | this->write32(0x91000000 | srcShifted | immShifted | val(dst)); 100 | } 101 | 102 | void ArmV8Assembler::b(uint32_t imm) { 103 | this->write32(0x14000000 | ((imm >> 2) & 0x3FFFFFF)); 104 | } 105 | 106 | void ArmV8Assembler::br(ArmV8Register reg) { 107 | const auto shifted = val(reg) << 5; 108 | this->write32(0xD61F0000 | shifted); 109 | } 110 | 111 | void ArmV8Assembler::blr(ArmV8Register reg) { 112 | const auto shifted = val(reg) << 5; 113 | this->write32(0xD63F0000 | shifted); 114 | } 115 | 116 | void ArmV8Assembler::push(ArmV8RegisterArray const& array) { 117 | using enum ArmV8IndexKind; 118 | 119 | const auto alignedSize = array.size() & ~1ull; 120 | for (auto i = 0u; i < alignedSize; i += 2) 121 | this->stp(array[i], array[i + 1], SP, -0x10, PreIndex); 122 | } 123 | 124 | void ArmV8Assembler::pop(ArmV8RegisterArray const& array) { 125 | using enum ArmV8IndexKind; 126 | 127 | const auto alignedSize = array.size() & ~1ull; 128 | for (auto i = 0u; i < alignedSize; i += 2) 129 | this->ldp(array[alignedSize - i - 2], array[alignedSize - i - 1], SP, 0x10, PostIndex); 130 | } 131 | 132 | void ArmV8Assembler::nop() { this->write32(0xD503201F); } -------------------------------------------------------------------------------- /src/assembler/ArmV8Assembler.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "BaseAssembler.hpp" 4 | 5 | namespace tulip::hook { 6 | 7 | enum class ArmV8Register : uint8_t { 8 | // 64-bit 9 | X0 = 0, 10 | X1, 11 | X2, 12 | X3, 13 | X4, 14 | X5, 15 | X6, 16 | X7, 17 | X8, 18 | X9, 19 | X10, 20 | X11, 21 | X12, 22 | X13, 23 | X14, 24 | X15, 25 | X16, 26 | X17, 27 | X18, 28 | X19, 29 | X20, 30 | X21, 31 | X22, 32 | X23, 33 | X24, 34 | X25, 35 | X26, 36 | X27, 37 | X28, 38 | X29, 39 | X30, 40 | SP, 41 | PC, 42 | D0 = 0x40, 43 | D1, 44 | D2, 45 | D3, 46 | D4, 47 | D5, 48 | D6, 49 | D7, 50 | D8, 51 | D9, 52 | D10, 53 | D11, 54 | D12, 55 | D13, 56 | D14, 57 | D15, 58 | D16, 59 | D17, 60 | D18, 61 | D19, 62 | D20, 63 | D21, 64 | D22, 65 | D23, 66 | D24, 67 | D25, 68 | D26, 69 | D27, 70 | D28, 71 | D29, 72 | D30, 73 | D31, 74 | }; 75 | 76 | enum class ArmV8IndexKind : uint8_t { 77 | PreIndex, 78 | PostIndex, 79 | SignedOffset, 80 | }; 81 | 82 | using ArmV8RegisterArray = std::vector; 83 | 84 | class ArmV8Assembler : public BaseAssembler { 85 | public: 86 | ArmV8Assembler(int64_t baseAddress); 87 | ArmV8Assembler(ArmV8Assembler const&) = delete; 88 | ArmV8Assembler(ArmV8Assembler&&) = delete; 89 | ~ArmV8Assembler(); 90 | 91 | void updateLabels() override; 92 | 93 | /* Instructions */ 94 | 95 | void mov(ArmV8Register dst, ArmV8Register src); 96 | void ldr(ArmV8Register dst, std::string const& label); 97 | void ldp(ArmV8Register reg1, ArmV8Register reg2, ArmV8Register regBase, int16_t imm, ArmV8IndexKind kind); 98 | void stp(ArmV8Register reg1, ArmV8Register reg2, ArmV8Register regBase, int16_t imm, ArmV8IndexKind kind); 99 | void adrp(ArmV8Register dst, int64_t imm); 100 | void add(ArmV8Register dst, ArmV8Register src, uint16_t imm); 101 | void b(uint32_t imm); 102 | void br(ArmV8Register reg); 103 | void blr(ArmV8Register reg); 104 | void nop(); 105 | 106 | /* Pseudo instructions */ 107 | 108 | void push(ArmV8RegisterArray const& array); 109 | void pop(ArmV8RegisterArray const& array); 110 | }; 111 | 112 | } -------------------------------------------------------------------------------- /src/assembler/BaseAssembler.cpp: -------------------------------------------------------------------------------- 1 | #include "BaseAssembler.hpp" 2 | 3 | using namespace tulip::hook; 4 | 5 | BaseAssembler::BaseAssembler(int64_t baseAddress) : 6 | m_baseAddress(baseAddress) {} 7 | 8 | BaseAssembler::~BaseAssembler() {} 9 | 10 | int8_t BaseAssembler::read8(int64_t address) const { 11 | return m_buffer[address - m_baseAddress]; 12 | } 13 | 14 | int16_t BaseAssembler::read16(int64_t address) const { 15 | const auto lo = read8(address); 16 | const auto hi = read8(address + 1); 17 | return ((static_cast(hi) << 8) | lo); 18 | } 19 | 20 | int32_t BaseAssembler::read32(int64_t address) const { 21 | const auto lo = read16(address); 22 | const auto hi = read16(address + 2); 23 | return ((static_cast(hi) << 16) | lo); 24 | } 25 | 26 | int64_t BaseAssembler::read64(int64_t address) const { 27 | const auto lo = read32(address); 28 | const auto hi = read32(address + 4); 29 | return ((static_cast(hi) << 32) | lo); 30 | } 31 | 32 | void BaseAssembler::write8(int8_t value) { 33 | m_buffer.push_back(value); 34 | } 35 | 36 | void BaseAssembler::write16(int16_t value) { 37 | write8(value & 0xFF); 38 | write8((value >> 8) & 0xFF); 39 | } 40 | 41 | void BaseAssembler::write32(int32_t value) { 42 | write16(value & 0xFFFF); 43 | write16((value >> 16) & 0xFFFF); 44 | } 45 | 46 | void BaseAssembler::write64(int64_t value) { 47 | write32(value & 0xFFFFFFFF); 48 | write32((value >> 32) & 0xFFFFFFFF); 49 | } 50 | 51 | int64_t BaseAssembler::currentAddress() const { 52 | return m_baseAddress + m_buffer.size(); 53 | } 54 | 55 | std::vector const& BaseAssembler::buffer() const { 56 | return m_buffer; 57 | } 58 | 59 | void BaseAssembler::rewrite8(int64_t address, int8_t value) { 60 | m_buffer[address - m_baseAddress] = value; 61 | } 62 | 63 | void BaseAssembler::rewrite16(int64_t address, int16_t value) { 64 | rewrite8(address, value & 0xFF); 65 | rewrite8(address + 1, (value >> 8) & 0xFF); 66 | } 67 | 68 | void BaseAssembler::rewrite32(int64_t address, int32_t value) { 69 | rewrite16(address, value & 0xFFFF); 70 | rewrite16(address + 2, (value >> 16) & 0xFFFF); 71 | } 72 | 73 | void BaseAssembler::rewrite64(int64_t address, int64_t value) { 74 | rewrite32(address, value & 0xFFFFFFFF); 75 | rewrite32(address + 4, (value >> 32) & 0xFFFFFFFF); 76 | } 77 | 78 | void BaseAssembler::label(std::string const& name) { 79 | m_labels[name] = this->currentAddress(); 80 | } 81 | 82 | void* BaseAssembler::getLabel(std::string const& name) const { 83 | if (m_labels.find(name) == m_labels.end()) { 84 | return nullptr; 85 | } 86 | return reinterpret_cast(m_labels.at(name)); 87 | } 88 | 89 | void BaseAssembler::updateLabels() {} -------------------------------------------------------------------------------- /src/assembler/BaseAssembler.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace tulip::hook { 9 | 10 | struct AssemblerLabelUpdates { 11 | int64_t m_address; 12 | std::string m_name; 13 | uint8_t m_size; 14 | }; 15 | 16 | class BaseAssembler { 17 | public: 18 | int64_t m_baseAddress; 19 | std::vector m_buffer; 20 | std::unordered_map m_labels; 21 | std::vector m_labelUpdates; 22 | std::vector m_absoluteLabelUpdates; 23 | 24 | BaseAssembler(int64_t baseAddress); 25 | BaseAssembler(BaseAssembler const&) = delete; 26 | BaseAssembler(BaseAssembler&&) = delete; 27 | ~BaseAssembler(); 28 | 29 | int64_t currentAddress() const; 30 | // maybe use span? 31 | std::vector const& buffer() const; 32 | 33 | int8_t read8(int64_t address) const; 34 | int16_t read16(int64_t address) const; 35 | int32_t read32(int64_t address) const; 36 | int64_t read64(int64_t address) const; 37 | 38 | void write8(int8_t value); 39 | void write16(int16_t value); 40 | void write32(int32_t value); 41 | void write64(int64_t value); 42 | 43 | void rewrite8(int64_t address, int8_t value); 44 | void rewrite16(int64_t address, int16_t value); 45 | void rewrite32(int64_t address, int32_t value); 46 | void rewrite64(int64_t address, int64_t value); 47 | 48 | void label(std::string const& name); 49 | 50 | void* getLabel(std::string const& name) const; 51 | 52 | virtual void updateLabels(); 53 | }; 54 | } -------------------------------------------------------------------------------- /src/assembler/X64Assembler.cpp: -------------------------------------------------------------------------------- 1 | #include "X64Assembler.hpp" 2 | 3 | using namespace tulip::hook; 4 | 5 | uint8_t regv(X64Register reg) { 6 | return static_cast(reg); 7 | } 8 | 9 | uint8_t regv(X64Pointer ptr) { 10 | return regv(ptr.reg); 11 | } 12 | 13 | uint8_t lowerv(X64Register reg, uint8_t offset) { 14 | return (regv(reg) >> 3) << offset; 15 | } 16 | 17 | uint8_t lowerv(X64Pointer ptr, uint8_t offset) { 18 | return (regv(ptr) >> 3) << offset; 19 | } 20 | 21 | uint8_t regIdx(X64Register reg) { 22 | return static_cast(reg) & 0x7; 23 | } 24 | 25 | void rex(X64Assembler* ass, X64Register reg, X64Register reg2, bool wide) { 26 | auto rexv = 0x40 | lowerv(reg, 0) | lowerv(reg2, 2) | (wide << 3); 27 | if (rexv != 0x40) { 28 | ass->write8(rexv); 29 | } 30 | } 31 | 32 | void rex(X64Assembler* ass, X64Pointer ptr, X64Register reg, bool wide) { 33 | auto rexv = 0x40 | lowerv(ptr, 0) | lowerv(reg, 2) | (wide << 3); 34 | if (rexv != 0x40) { 35 | ass->write8(rexv); 36 | } 37 | } 38 | 39 | X86Register x86reg(X64Register reg) { 40 | return static_cast(regv(reg) & 0xf7); 41 | } 42 | 43 | X86Pointer x86ptr(X64Pointer ptr) { 44 | return {x86reg(ptr.reg), ptr.offset}; 45 | } 46 | 47 | X64Assembler::X64Assembler(int64_t baseAddress) : 48 | X86Assembler(baseAddress) {} 49 | 50 | X64Assembler::~X64Assembler() {} 51 | 52 | void X64Assembler::updateLabels() { 53 | for (auto const& update : m_labelUpdates) { 54 | if (update.m_size == 4) this->rewrite32(update.m_address, m_labels[update.m_name] - update.m_address - 4); 55 | else if (update.m_size == 1) this->rewrite8(update.m_address, m_labels[update.m_name] - update.m_address - 1); 56 | } 57 | // absolute is not absolute in 64 bit 58 | for (auto const& update : m_absoluteLabelUpdates) { 59 | this->rewrite32(update.m_address, m_labels[update.m_name] - update.m_address - 4); 60 | } 61 | } 62 | 63 | using enum X64Register; 64 | 65 | void X64Assembler::nop() { 66 | X86Assembler::nop(); 67 | } 68 | 69 | void X64Assembler::add(X64Register reg, int32_t value) { 70 | rex(this, reg, RAX, true); 71 | X86Assembler::add(x86reg(reg), value); 72 | } 73 | 74 | void X64Assembler::sub(X64Register reg, int32_t value) { 75 | rex(this, reg, RAX, true); 76 | X86Assembler::sub(x86reg(reg), value); 77 | } 78 | 79 | void X64Assembler::push(X64Register reg) { 80 | rex(this, reg, RAX, false); 81 | X86Assembler::push(x86reg(reg)); 82 | } 83 | 84 | void X64Assembler::push(X64Pointer ptr) { 85 | rex(this, ptr, RAX, false); 86 | X86Assembler::push(x86ptr(ptr)); 87 | } 88 | 89 | void X64Assembler::pop(X64Register reg) { 90 | rex(this, reg, RAX, false); 91 | X86Assembler::pop(x86reg(reg)); 92 | } 93 | 94 | void X64Assembler::jmp(X64Register reg) { 95 | rex(this, reg, RAX, false); 96 | X86Assembler::jmp(x86reg(reg)); 97 | } 98 | 99 | void X64Assembler::jmp(int64_t address) { 100 | X86Assembler::jmp(address); 101 | } 102 | 103 | void X64Assembler::jmp(std::string const& label) { 104 | X86Assembler::jmp(label); 105 | } 106 | 107 | void X64Assembler::jmpip(std::string const& label) { 108 | this->write8(0xff); 109 | this->write8(0x25); 110 | this->label32(label); 111 | } 112 | 113 | void X64Assembler::call(X64Register reg) { 114 | rex(this, reg, RAX, false); 115 | X86Assembler::call(x86reg(reg)); 116 | } 117 | 118 | void X64Assembler::call(int64_t address) { 119 | X86Assembler::call(address); 120 | } 121 | 122 | void X64Assembler::call(std::string const& label) { 123 | X86Assembler::call(label); 124 | } 125 | 126 | void X64Assembler::callip(std::string const& label) { 127 | this->write8(0xff); 128 | this->write8(0x15); 129 | this->label32(label); 130 | } 131 | 132 | void X64Assembler::lea(X64Register reg, std::string const& label) { 133 | rex(this, RAX, reg, true); 134 | X86Assembler::lea(x86reg(reg), label); 135 | } 136 | 137 | void X64Assembler::movsd(X64Register reg, X64Pointer ptr) { 138 | rex(this, ptr, RAX, false); 139 | X86Assembler::movsd(x86reg(reg), x86ptr(ptr)); 140 | } 141 | 142 | void X64Assembler::movsd(X64Pointer ptr, X64Register reg) { 143 | rex(this, ptr, RAX, false); 144 | X86Assembler::movsd(x86ptr(ptr), x86reg(reg)); 145 | } 146 | 147 | void X64Assembler::movss(X64Register reg, X64Pointer ptr) { 148 | rex(this, ptr, RAX, false); 149 | X86Assembler::movss(x86reg(reg), x86ptr(ptr)); 150 | } 151 | 152 | void X64Assembler::movss(X64Pointer ptr, X64Register reg) { 153 | rex(this, ptr, RAX, false); 154 | X86Assembler::movss(x86ptr(ptr), x86reg(reg)); 155 | } 156 | 157 | void X64Assembler::movaps(X64Register reg, X64Pointer ptr) { 158 | rex(this, ptr, RAX, false); 159 | X86Assembler::movaps(x86reg(reg), x86ptr(ptr)); 160 | } 161 | 162 | void X64Assembler::movaps(X64Pointer ptr, X64Register reg) { 163 | rex(this, ptr, RAX, false); 164 | X86Assembler::movaps(x86ptr(ptr), x86reg(reg)); 165 | } 166 | 167 | void X64Assembler::mov(X64Register reg, int32_t value) { 168 | rex(this, reg, RAX, true); 169 | this->write8(0xc7); 170 | this->write8(0xc0 | regIdx(reg)); 171 | this->write32(value); 172 | } 173 | 174 | void X64Assembler::mov(X64Register reg, X64Pointer ptr) { 175 | rex(this, ptr, reg, true); 176 | X86Assembler::mov(x86reg(reg), x86ptr(ptr)); 177 | } 178 | 179 | void X64Assembler::mov(X64Pointer ptr, X64Register reg) { 180 | rex(this, ptr, reg, true); 181 | X86Assembler::mov(x86ptr(ptr), x86reg(reg)); 182 | } 183 | 184 | void X64Assembler::mov(X64Register reg, X64Register reg2) { 185 | rex(this, reg, reg2, true); 186 | X86Assembler::mov(x86reg(reg), x86reg(reg2)); 187 | } 188 | 189 | void X64Assembler::mov(X64Register reg, std::string const& label) { 190 | rex(this, RAX, reg, true); 191 | X86Assembler::mov(x86reg(reg), label); 192 | } 193 | 194 | void X64Assembler::shr(X64Register reg, uint8_t value) { 195 | rex(this, reg, RAX, true); 196 | X86Assembler::shr(x86reg(reg), value); 197 | } 198 | 199 | void X64Assembler::shl(X64Register reg, uint8_t value) { 200 | rex(this, reg, RAX, true); 201 | X86Assembler::shl(x86reg(reg), value); 202 | } 203 | 204 | void X64Assembler::xchg(X64Register reg, X64Register reg2) { 205 | rex(this, reg, reg2, true); 206 | X86Assembler::xchg(x86reg(reg), x86reg(reg2)); 207 | } 208 | 209 | void X64Assembler::cmp(X64Register reg, X64Register reg2) { 210 | rex(this, reg, reg2, true); 211 | X86Assembler::cmp(x86reg(reg), x86reg(reg2)); 212 | } 213 | void X64Assembler::cmp(X64Register reg, int32_t imm) { 214 | rex(this, reg, RAX, true); 215 | X86Assembler::cmp(x86reg(reg), imm); 216 | } 217 | 218 | void X64Assembler::align16() { 219 | auto align = 16 - (this->currentAddress() % 16); 220 | for (auto i = 0; i < align; i++) { 221 | this->write8(0x90); 222 | } 223 | } -------------------------------------------------------------------------------- /src/assembler/X64Assembler.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "X86Assembler.hpp" 4 | 5 | namespace tulip::hook { 6 | 7 | enum class X64Register : uint8_t { 8 | RAX = 0x0, 9 | RCX, 10 | RDX, 11 | RBX, 12 | RSP, 13 | RBP, 14 | RSI, 15 | RDI, 16 | R8, 17 | R9, 18 | R10, 19 | R11, 20 | R12, 21 | R13, 22 | R14, 23 | R15, 24 | XMM0 = 0x80, 25 | XMM1, 26 | XMM2, 27 | XMM3, 28 | XMM4, 29 | XMM5, 30 | XMM6, 31 | XMM7 32 | }; 33 | 34 | struct X64Pointer { 35 | X64Register reg; 36 | int32_t offset = 0; 37 | 38 | X64Pointer(X64Register reg, int32_t offset = 0) : 39 | reg(reg), 40 | offset(offset) {} 41 | }; 42 | 43 | inline X64Pointer operator+(X64Register reg, int32_t offset) { 44 | return X64Pointer(reg, offset); 45 | } 46 | inline X64Pointer operator-(X64Register reg, int32_t offset) { 47 | return X64Pointer(reg, -offset); 48 | } 49 | 50 | // Use this to easily express a X64Pointer, like so: 51 | // RegMem64 m; 52 | // m[RSP], m[RSP + 4] 53 | struct RegMem64 { 54 | X64Pointer operator[](X64Pointer ptr) { 55 | return ptr; 56 | } 57 | }; 58 | 59 | class X64Assembler : public X86Assembler { 60 | public: 61 | X64Assembler(int64_t baseAddress); 62 | X64Assembler(X64Assembler const&) = delete; 63 | X64Assembler(X64Assembler&&) = delete; 64 | ~X64Assembler(); 65 | 66 | void updateLabels() override; 67 | 68 | void nop(); 69 | 70 | void add(X64Register reg, int32_t value); 71 | void sub(X64Register reg, int32_t value); 72 | 73 | void push(X64Register reg); 74 | void push(X64Pointer ptr); 75 | void pop(X64Register reg); 76 | 77 | void jmp(X64Register reg); 78 | void jmp(int64_t address); 79 | void jmp(std::string const& label); 80 | void jmpip(std::string const& label); 81 | 82 | void call(X64Register reg); 83 | void call(int64_t address); 84 | void call(std::string const& label); 85 | void callip(std::string const& label); 86 | 87 | void movsd(X64Register reg, X64Pointer ptr); 88 | void movsd(X64Pointer ptr, X64Register reg); 89 | 90 | void movss(X64Register reg, X64Pointer ptr); 91 | void movss(X64Pointer ptr, X64Register reg); 92 | 93 | void movaps(X64Register reg, X64Pointer ptr); 94 | void movaps(X64Pointer ptr, X64Register reg); 95 | 96 | void lea(X64Register reg, std::string const& label); 97 | 98 | void mov(X64Register reg, int32_t value); 99 | void mov(X64Register reg, X64Pointer ptr); 100 | void mov(X64Pointer ptr, X64Register reg); 101 | void mov(X64Register reg, X64Register reg2); 102 | void mov(X64Register reg, std::string const& label); 103 | 104 | void shr(X64Register reg, uint8_t value); 105 | void shl(X64Register reg, uint8_t value); 106 | 107 | void xchg(X64Register reg, X64Register reg2); 108 | 109 | void cmp(X64Register reg, X64Register reg2); 110 | void cmp(X64Register reg, int32_t imm); 111 | 112 | void align16(); 113 | }; 114 | } -------------------------------------------------------------------------------- /src/assembler/X86Assembler.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "BaseAssembler.hpp" 4 | 5 | namespace tulip::hook { 6 | 7 | enum class X86Register : uint8_t { 8 | EAX = 0x0, 9 | ECX, 10 | EDX, 11 | EBX, 12 | ESP, 13 | EBP, 14 | ESI, 15 | EDI, 16 | XMM0 = 0x80, 17 | XMM1, 18 | XMM2, 19 | XMM3, 20 | XMM4, 21 | XMM5, 22 | XMM6, 23 | XMM7 24 | }; 25 | 26 | struct X86Pointer { 27 | X86Register reg; 28 | int32_t offset = 0; 29 | 30 | X86Pointer(X86Register reg, int32_t offset = 0) : 31 | reg(reg), 32 | offset(offset) {} 33 | }; 34 | 35 | inline X86Pointer operator+(X86Register reg, int32_t offset) { 36 | return X86Pointer(reg, offset); 37 | } 38 | inline X86Pointer operator-(X86Register reg, int32_t offset) { 39 | return X86Pointer(reg, -offset); 40 | } 41 | 42 | // Use this to easily express a X86Pointer, like so: 43 | // RegMem32 m; 44 | // m[ESP], m[ESP + 4] 45 | struct RegMem32 { 46 | X86Pointer operator[](X86Pointer ptr) { 47 | return ptr; 48 | } 49 | }; 50 | 51 | struct X86Operand { 52 | enum class Type { 53 | Register, 54 | ModRM, 55 | } m_type; 56 | X86Register m_reg; 57 | int32_t m_value = 0; 58 | 59 | X86Operand(X86Register reg) : 60 | m_reg(reg), 61 | m_type(Type::Register) {} 62 | 63 | X86Operand(X86Pointer ptr) : 64 | m_reg(ptr.reg), 65 | m_value(ptr.offset), 66 | m_type(Type::ModRM) {} 67 | }; 68 | 69 | class X86Assembler : public BaseAssembler { 70 | protected: 71 | void encodeModRM(X86Operand op, uint8_t digit); 72 | 73 | public: 74 | X86Assembler(int64_t baseAddress); 75 | X86Assembler(X86Assembler const&) = delete; 76 | X86Assembler(X86Assembler&&) = delete; 77 | ~X86Assembler(); 78 | 79 | void label8(std::string const& name); 80 | void label32(std::string const& name); 81 | void abslabel32(std::string const& name); 82 | void updateLabels() override; 83 | 84 | void nop(); 85 | void int3(); 86 | 87 | void ret(); 88 | void ret(int16_t offset); 89 | 90 | void add(X86Register reg, int32_t value); 91 | void sub(X86Register reg, int32_t value); 92 | 93 | void push(X86Register reg); 94 | void push(X86Pointer reg); 95 | void pop(X86Register reg); 96 | 97 | void jmp(X86Register reg); 98 | void jmp(int64_t address); 99 | void jmp(std::string const& label); 100 | void jmp8(std::string const& label); 101 | 102 | void call(X86Register reg); 103 | void call(int64_t value); 104 | void call(std::string const& label); 105 | 106 | void movsd(X86Register reg, X86Pointer ptr); 107 | void movsd(X86Pointer ptr, X86Register reg); 108 | 109 | void movss(X86Register reg, X86Pointer ptr); 110 | void movss(X86Pointer ptr, X86Register reg); 111 | 112 | void movaps(X86Register reg, X86Pointer ptr); 113 | void movaps(X86Pointer ptr, X86Register reg); 114 | 115 | void lea(X86Register reg, std::string const& label); 116 | 117 | void mov(X86Register reg, int32_t value); 118 | void mov(X86Register reg, X86Pointer ptr); 119 | void mov(X86Pointer ptr, X86Register reg); 120 | void mov(X86Register reg, X86Register reg2); 121 | void mov(X86Register reg, std::string const& label); 122 | 123 | void fstps(X86Pointer ptr); 124 | void flds(X86Pointer ptr); 125 | void fstpd(X86Pointer ptr); 126 | void fldd(X86Pointer ptr); 127 | 128 | void shr(X86Register reg, uint8_t value); 129 | void shl(X86Register reg, uint8_t value); 130 | 131 | void xchg(X86Register reg, X86Register reg2); 132 | 133 | void cmp(X86Register reg, X86Register reg2); 134 | void cmp(X86Register reg, int32_t imm); 135 | }; 136 | } -------------------------------------------------------------------------------- /src/convention/CallingConvention.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace tulip::hook; 4 | 5 | CallingConvention::~CallingConvention() {} 6 | -------------------------------------------------------------------------------- /src/convention/DefaultConvention.cpp: -------------------------------------------------------------------------------- 1 | #include "../assembler/BaseAssembler.hpp" 2 | 3 | #include 4 | #include 5 | 6 | using namespace tulip::hook; 7 | 8 | DefaultConvention::~DefaultConvention() {} 9 | 10 | void DefaultConvention::generateDefaultCleanup(BaseAssembler& a, AbstractFunction const& function) {} 11 | 12 | void DefaultConvention::generateIntoDefault(BaseAssembler& a, AbstractFunction const& function) {} 13 | 14 | void DefaultConvention::generateIntoOriginal(BaseAssembler& a, AbstractFunction const& function) {} 15 | 16 | void DefaultConvention::generateOriginalCleanup(BaseAssembler& a, AbstractFunction const& function) {} 17 | 18 | bool DefaultConvention::needsWrapper(AbstractFunction const& function) const { 19 | return false; 20 | } 21 | 22 | std::shared_ptr DefaultConvention::create() { 23 | return std::make_shared(); 24 | } 25 | -------------------------------------------------------------------------------- /src/convention/MacosIntelConvention.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #if defined(TULIP_HOOK_MACOS) && defined(TULIP_HOOK_X64) 4 | 5 | #include 6 | #include 7 | #include "../assembler/X64Assembler.hpp" 8 | 9 | using namespace tulip::hook; 10 | 11 | namespace { 12 | size_t getStackParamSize(AbstractFunction const& function) { 13 | size_t stackParamSize = 0; 14 | int xmmCount = 0; 15 | int gprCount = 0; 16 | if (function.m_return.m_kind == AbstractTypeKind::Other && function.m_return.m_size > 16) { 17 | gprCount += 1; 18 | } 19 | for (auto& param : function.m_parameters) { 20 | if (param.m_kind == AbstractTypeKind::FloatingPoint) { 21 | if (xmmCount < 8) { 22 | xmmCount++; 23 | } else { 24 | stackParamSize += 8; 25 | } 26 | } else if (param.m_kind == AbstractTypeKind::Primitive && param.m_size == 16) { 27 | if (gprCount < 6) { 28 | gprCount++; 29 | } else { 30 | stackParamSize += 8; 31 | } 32 | if (gprCount < 6) { 33 | gprCount++; 34 | } else { 35 | stackParamSize += 8; 36 | } 37 | } else { 38 | if (gprCount < 6) { 39 | gprCount++; 40 | } else { 41 | stackParamSize += 8; 42 | } 43 | } 44 | } 45 | return stackParamSize; 46 | } 47 | } 48 | 49 | SystemVConvention::~SystemVConvention() {} 50 | 51 | void SystemVConvention::generateDefaultCleanup(BaseAssembler& a_, AbstractFunction const& function) { 52 | size_t stackParamSize = getStackParamSize(function); 53 | if (stackParamSize > 0) { 54 | auto& a = static_cast(a_); 55 | using enum X64Register; 56 | auto const paddedSize = (stackParamSize % 16) ? stackParamSize + 8 : stackParamSize; 57 | a.add(RSP, paddedSize); 58 | } 59 | } 60 | 61 | // used to move the stack values to the correct places 62 | 63 | void SystemVConvention::generateIntoDefault(BaseAssembler& a_, AbstractFunction const& function) { 64 | size_t stackParamSize = getStackParamSize(function); 65 | if (stackParamSize > 0) { 66 | auto& a = static_cast(a_); 67 | using enum X64Register; 68 | RegMem64 m; 69 | auto const paddedSize = (stackParamSize % 16) ? stackParamSize + 8 : stackParamSize; 70 | a.sub(RSP, paddedSize); 71 | int stackOffset = 0; 72 | 73 | for (auto i = 0; i < stackParamSize; i += 8) { 74 | a.mov(RAX, m[RBP + (16 + i)]); 75 | a.mov(m[RSP + i], RAX); 76 | } 77 | } 78 | } 79 | 80 | void SystemVConvention::generateIntoOriginal(BaseAssembler& a_, AbstractFunction const& function) { 81 | } 82 | 83 | void SystemVConvention::generateOriginalCleanup(BaseAssembler& a_, AbstractFunction const& function) { 84 | } 85 | 86 | bool SystemVConvention::needsWrapper(AbstractFunction const& function) const { 87 | return false; 88 | } 89 | 90 | std::shared_ptr SystemVConvention::create() { 91 | return std::make_shared(); 92 | } 93 | 94 | #endif -------------------------------------------------------------------------------- /src/convention/Windows64Convention.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #if defined(TULIP_HOOK_WINDOWS) && defined(TULIP_HOOK_X64) 4 | 5 | #include 6 | #include 7 | #include "../assembler/X64Assembler.hpp" 8 | 9 | using namespace tulip::hook; 10 | 11 | namespace { 12 | size_t getStackParamSize(AbstractFunction const& function) { 13 | size_t stackParamSize = 0; 14 | int regCount = 0; 15 | if (function.m_return.m_kind == AbstractTypeKind::Other) { 16 | regCount += 1; 17 | } 18 | for (auto& param : function.m_parameters) { 19 | if (regCount < 4) { 20 | regCount++; 21 | } else { 22 | stackParamSize += 8; 23 | } 24 | } 25 | return stackParamSize; 26 | } 27 | size_t getPaddedStackParamSize(AbstractFunction const& function) { 28 | auto stackParamSize = getStackParamSize(function); 29 | return (stackParamSize % 16) ? stackParamSize + 8 : stackParamSize; 30 | } 31 | } 32 | 33 | Windows64Convention::~Windows64Convention() {} 34 | 35 | void Windows64Convention::generateDefaultCleanup(BaseAssembler& a_, AbstractFunction const& function) { 36 | auto& a = static_cast(a_); 37 | using enum X64Register; 38 | 39 | // size_t paddedSize = getPaddedStackParamSize(function); 40 | // a.add(RSP, paddedSize + 0x20); 41 | } 42 | 43 | void Windows64Convention::generateIntoDefault(BaseAssembler& a_, AbstractFunction const& function) { 44 | auto& a = static_cast(a_); 45 | using enum X64Register; 46 | RegMem64 m; 47 | 48 | size_t stackParamSize = getStackParamSize(function); 49 | auto const paddedSize = (stackParamSize % 16) ? stackParamSize + 8 : stackParamSize; 50 | // + 0x20 for the shadow space before the first arg 51 | // a.sub(RSP, paddedSize + 0x20); 52 | // a.label("convention-alloc-small"); 53 | if (stackParamSize > 0) { 54 | // theres stack args, so we need to copy them over 55 | 56 | // RBP points to this (each cell is 8 bytes): 57 | // [orig rbp] [return ptr] [] [] [] [] [1st stack arg] ... 58 | // rbp + 0 rbp + 8 rbp + 0x30 ... 59 | // new stack will look like 60 | // [] [] [] [] [1st stack arg] ... 61 | // rsp + 0x20 ... 62 | 63 | for (auto i = 0; i < stackParamSize; i += 8) { 64 | a.mov(RAX, m[RBP + (0x30 + i)]); 65 | a.mov(m[RSP + (0x20 + i)], RAX); 66 | } 67 | } 68 | } 69 | 70 | std::shared_ptr Windows64Convention::create() { 71 | return std::make_shared(); 72 | } 73 | 74 | // Member functions deal with struct return differently, since in the windows x64 convention 75 | // a struct return is as a hidden first parameter, member functions end up considering the first parameter 76 | // the one after the `this`, whereas static functions do not. 77 | // 78 | // So where a static function would behave like this: 79 | // SomeStruct* func(SomeStruct* ret_ptr, Class* self, int a, int b); 80 | // a member function would behave like this: 81 | // SomeStruct* Class::method(Class* this, SomeStruct* ret_ptr, int a, int b); 82 | // so to undo this we just swap the first two parameters (RCX and RDX). 83 | 84 | ThiscallConvention::~ThiscallConvention() {} 85 | 86 | void ThiscallConvention::generateIntoDefault(BaseAssembler& a_, AbstractFunction const& function) { 87 | auto& a = static_cast(a_); 88 | using enum X64Register; 89 | RegMem64 m; 90 | 91 | if (function.m_return.m_kind == AbstractTypeKind::Other) { 92 | a.xchg(RCX, RDX); 93 | } 94 | 95 | Windows64Convention::generateIntoDefault(a, function); 96 | } 97 | 98 | void ThiscallConvention::generateIntoOriginal(BaseAssembler& a_, AbstractFunction const& function) { 99 | auto& a = static_cast(a_); 100 | using enum X64Register; 101 | RegMem64 m; 102 | 103 | if (function.m_return.m_kind == AbstractTypeKind::Other) { 104 | a.xchg(RCX, RDX); 105 | } 106 | 107 | // the wrapper requires the struct forwarding as well 108 | Windows64Convention::generateIntoDefault(a, function); 109 | } 110 | 111 | void ThiscallConvention::generateOriginalCleanup(BaseAssembler& a_, AbstractFunction const& function) { 112 | // the wrapper requires the struct forwarding as well 113 | Windows64Convention::generateDefaultCleanup(a_, function); 114 | } 115 | 116 | bool ThiscallConvention::needsWrapper(AbstractFunction const& function) const { 117 | return function.m_return.m_kind == AbstractTypeKind::Other; 118 | } 119 | 120 | std::shared_ptr ThiscallConvention::create() { 121 | return std::make_shared(); 122 | } 123 | 124 | #endif -------------------------------------------------------------------------------- /src/generator/ArmV7Generator.cpp: -------------------------------------------------------------------------------- 1 | #include "ArmV7Generator.hpp" 2 | 3 | #include "../Handler.hpp" 4 | #include "../assembler/ArmV7Assembler.hpp" 5 | #include "../target/PlatformTarget.hpp" 6 | 7 | #include 8 | #include 9 | 10 | using namespace tulip::hook; 11 | 12 | namespace { 13 | void* TULIP_HOOK_DEFAULT_CONV preHandler(HandlerContent* content, void* originalReturn) { 14 | Handler::incrementIndex(content); 15 | auto ret = Handler::getNextFunction(content); 16 | Handler::pushData(originalReturn); 17 | 18 | return ret; 19 | } 20 | 21 | void* TULIP_HOOK_DEFAULT_CONV postHandler() { 22 | auto ret = Handler::popData(); 23 | Handler::decrementIndex(); 24 | return ret; 25 | } 26 | } 27 | 28 | std::vector ArmV7HandlerGenerator::handlerBytes(uint64_t address) { 29 | ArmV7Assembler a((uint64_t)Target::get().getRealPtr((void*)address)); 30 | using enum ArmV7Register; 31 | 32 | // preserve registers 33 | a.push({R0, R1, R2, R3}); 34 | a.vpush({D0, D1, D2, D3}); 35 | 36 | // set the parameters 37 | a.ldr(R0, "content"); 38 | a.mov(R1, LR); 39 | 40 | // call the pre handler, incrementing 41 | a.ldr(R2, "handlerPre"); 42 | a.blx(R2); 43 | a.mov(LR, R0); 44 | 45 | // recover registers 46 | a.vpop({D0, D1, D2, D3}); 47 | a.pop({R0, R1, R2, R3}); 48 | 49 | // call the func 50 | a.blx(LR); 51 | 52 | // preserve the return values 53 | a.push({R0, R1}); 54 | 55 | // call the post handler, decrementing 56 | a.ldr(R0, "handlerPost"); 57 | a.blx(R0); 58 | 59 | // recover the original return 60 | a.mov(LR, R0); 61 | 62 | // recover the return values 63 | a.pop({R0, R1}); 64 | 65 | // done! 66 | a.bx(LR); 67 | 68 | a.label("handlerPre"); 69 | a.write32(reinterpret_cast(preHandler)); 70 | 71 | a.label("handlerPost"); 72 | a.write32(reinterpret_cast(postHandler)); 73 | 74 | a.label("content"); 75 | a.write32(reinterpret_cast(m_content)); 76 | 77 | a.updateLabels(); 78 | 79 | return std::move(a.m_buffer); 80 | } 81 | 82 | std::vector ArmV7HandlerGenerator::intervenerBytes(uint64_t address, size_t size) { 83 | ArmV7Assembler a((uint64_t)Target::get().getRealPtr((void*)address)); 84 | using enum ArmV7Register; 85 | 86 | // align 87 | if (address & 0x2) { 88 | a.nop(); 89 | } 90 | 91 | if (address & 0x1) { 92 | // thumb 93 | a.ldrpcn(); 94 | } 95 | else { 96 | // arm 97 | a.ldrpcn2(); 98 | } 99 | 100 | // my thumbs will eat me 101 | a.write32(reinterpret_cast(m_handler) + 1); 102 | 103 | a.updateLabels(); 104 | 105 | return std::move(a.m_buffer); 106 | } 107 | 108 | geode::Result ArmV7HandlerGenerator::generateTrampoline(uint64_t target) { 109 | auto origin = new CodeMemBlock((uint64_t)Target::get().getRealPtr(m_address), target); 110 | auto relocated = new CodeMemBlock(); 111 | // idk about arm thumb stuff help me 112 | auto originBuffer = m_address; 113 | auto relocatedBuffer = m_trampoline; 114 | 115 | static thread_local std::string error; 116 | error = ""; 117 | 118 | GenRelocateCodeAndBranch(originBuffer, relocatedBuffer, origin, relocated, +[](void* dest, void const* src, size_t size) { 119 | auto res = Target::get().writeMemory(dest, src, size); 120 | if (!res) { 121 | error = res.unwrapErr(); 122 | } 123 | }); 124 | 125 | if (!error.empty()) { 126 | return geode::Err(std::move(error)); 127 | } 128 | 129 | if (relocated->size == 0) { 130 | return geode::Err("Failed to relocate original function"); 131 | } 132 | return geode::Ok(TrampolineReturn{FunctionData{m_trampoline, relocated->size}, relocated->size}); 133 | } 134 | -------------------------------------------------------------------------------- /src/generator/ArmV7Generator.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Generator.hpp" 4 | 5 | #include 6 | 7 | namespace tulip::hook { 8 | 9 | class ArmV7HandlerGenerator : public HandlerGenerator { 10 | public: 11 | using HandlerGenerator::HandlerGenerator; 12 | 13 | geode::Result generateTrampoline(uint64_t target) override; 14 | 15 | std::vector handlerBytes(uint64_t address) override; 16 | std::vector intervenerBytes(uint64_t address, size_t size) override; 17 | }; 18 | 19 | class ArmV7WrapperGenerator : public WrapperGenerator { 20 | public: 21 | using WrapperGenerator::WrapperGenerator; 22 | }; 23 | } 24 | -------------------------------------------------------------------------------- /src/generator/ArmV8Generator.cpp: -------------------------------------------------------------------------------- 1 | #include "ArmV8Generator.hpp" 2 | 3 | #include "../Handler.hpp" 4 | #include "../assembler/ArmV8Assembler.hpp" 5 | #include "../target/PlatformTarget.hpp" 6 | 7 | #include 8 | 9 | using namespace tulip::hook; 10 | 11 | namespace { 12 | void* TULIP_HOOK_DEFAULT_CONV preHandler(HandlerContent* content, void* originalReturn) { 13 | Handler::incrementIndex(content); 14 | auto ret = Handler::getNextFunction(content); 15 | Handler::pushData(originalReturn); 16 | 17 | return ret; 18 | } 19 | 20 | void* TULIP_HOOK_DEFAULT_CONV postHandler() { 21 | auto ret = Handler::popData(); 22 | Handler::decrementIndex(); 23 | return ret; 24 | } 25 | } 26 | 27 | std::vector ArmV8HandlerGenerator::handlerBytes(uint64_t address) { 28 | ArmV8Assembler a(address); 29 | using enum ArmV8Register; 30 | 31 | // preserve registers 32 | a.push({X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13, X14, X15}); 33 | a.push({D0, D1, D2, D3, D4, D5, D6, D7}); 34 | // v8-15 are callee saved. 35 | a.push({D16, D17, D18, D19, D20, D21, D22, D23, D24, D25, D26, D27, D28, D29, D30, D31}); 36 | 37 | // set the parameters 38 | a.ldr(X0, "content"); 39 | a.mov(X1, X30); 40 | 41 | // call the pre handler, incrementing 42 | a.ldr(X2, "handlerPre"); 43 | a.blr(X2); 44 | a.mov(X30, X0); 45 | 46 | // recover registers 47 | a.pop({D16, D17, D18, D19, D20, D21, D22, D23, D24, D25, D26, D27, D28, D29, D30, D31}); 48 | a.pop({D0, D1, D2, D3, D4, D5, D6, D7}); 49 | a.pop({X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13, X14, X15}); 50 | 51 | // call the func 52 | a.blr(X30); 53 | 54 | // preserve the return values 55 | a.push({X0, X1, X2, X3, X4, X5, X6, X7}); 56 | a.push({D0, D1, D2, D3, D4, D5, D6, D7}); 57 | 58 | // call the post handler, decrementing 59 | a.ldr(X0, "handlerPost"); 60 | a.blr(X0); 61 | 62 | // recover the original return 63 | a.mov(X30, X0); 64 | 65 | // recover the return values 66 | a.pop({D0, D1, D2, D3, D4, D5, D6, D7}); 67 | a.pop({X0, X1, X2, X3, X4, X5, X6, X7}); 68 | 69 | // done! 70 | a.br(X30); 71 | 72 | // Align to 8 bytes for ldr. 73 | if (a.currentAddress() & 7) 74 | a.nop(); 75 | 76 | a.label("handlerPre"); 77 | a.write64(reinterpret_cast(preHandler)); 78 | 79 | a.label("handlerPost"); 80 | a.write64(reinterpret_cast(postHandler)); 81 | 82 | a.label("content"); 83 | a.write64(reinterpret_cast(m_content)); 84 | 85 | a.updateLabels(); 86 | 87 | return std::move(a.m_buffer); 88 | } 89 | 90 | std::vector ArmV8HandlerGenerator::intervenerBytes(uint64_t address, size_t size) { 91 | ArmV8Assembler a(address); 92 | using enum ArmV8Register; 93 | 94 | const auto callback = reinterpret_cast(m_handler); 95 | const int64_t alignedAddr = address & ~0xFFF; 96 | const int64_t alignedCallback = callback & ~0xFFF; 97 | const int64_t delta = callback - static_cast(address); 98 | 99 | // Delta can be encoded in 28 bits or less -> use branch. 100 | if (delta >= -0x8000000 && delta <= 0x7FFFFFF) { 101 | a.b(delta); 102 | } 103 | // Delta can be encoded in 33 bits or less -> use adrp. 104 | else if (delta >= -static_cast(0x100000000) && delta <= 0xFFFFFFFF) { 105 | a.adrp(X16, alignedCallback - alignedAddr); 106 | a.add(X16, X16, callback & 0xFFF); 107 | a.br(X16); 108 | } 109 | // Delta is too big -> use branch with register. 110 | else { 111 | // // Align to 8 bytes for ldr. 112 | // if (address & 7) { 113 | // a.nop(); 114 | // } 115 | 116 | a.ldr(X16, "handler"); 117 | a.br(X16); 118 | 119 | a.label("handler"); 120 | a.write64(callback); 121 | } 122 | 123 | a.updateLabels(); 124 | 125 | return std::move(a.m_buffer); 126 | } 127 | 128 | geode::Result ArmV8HandlerGenerator::generateTrampoline(uint64_t target) { 129 | auto origin = new CodeMemBlock(reinterpret_cast(m_address), target); 130 | auto relocated = new CodeMemBlock(); 131 | auto originBuffer = m_address; 132 | auto relocatedBuffer = m_trampoline; 133 | 134 | static thread_local std::string error; 135 | error = ""; 136 | 137 | GenRelocateCodeAndBranch(originBuffer, relocatedBuffer, origin, relocated, +[](void* dest, void const* src, size_t size) { 138 | auto res = Target::get().writeMemory(dest, src, size); 139 | if (!res) { 140 | error = res.unwrapErr(); 141 | } 142 | }); 143 | 144 | if (!error.empty()) { 145 | return geode::Err(std::move(error)); 146 | } 147 | 148 | if (relocated->size == 0) { 149 | return geode::Err("Failed to relocate original function"); 150 | } 151 | return geode::Ok(TrampolineReturn{FunctionData{m_trampoline, relocated->size}, relocated->size}); 152 | } 153 | -------------------------------------------------------------------------------- /src/generator/ArmV8Generator.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Generator.hpp" 4 | 5 | #include 6 | 7 | namespace tulip::hook { 8 | 9 | class ArmV8HandlerGenerator : public HandlerGenerator { 10 | public: 11 | using HandlerGenerator::HandlerGenerator; 12 | 13 | geode::Result generateTrampoline(uint64_t target) override; 14 | 15 | std::vector handlerBytes(uint64_t address) override; 16 | std::vector intervenerBytes(uint64_t address, size_t size) override; 17 | }; 18 | 19 | class ArmV8WrapperGenerator : public WrapperGenerator { 20 | public: 21 | using WrapperGenerator::WrapperGenerator; 22 | }; 23 | } 24 | -------------------------------------------------------------------------------- /src/generator/Generator.cpp: -------------------------------------------------------------------------------- 1 | #include "Generator.hpp" 2 | 3 | #include "../target/PlatformTarget.hpp" 4 | 5 | using namespace tulip::hook; 6 | 7 | HandlerGenerator::HandlerGenerator( 8 | void* address, void* trampoline, void* handler, void* content, HandlerMetadata const& metadata 9 | ) : 10 | m_address(address), 11 | m_trampoline(trampoline), 12 | m_handler(handler), 13 | m_content(content), 14 | m_metadata(metadata) {} 15 | 16 | WrapperGenerator::WrapperGenerator(void* address, WrapperMetadata const& metadata) : 17 | m_address(address), 18 | m_metadata(metadata) {} 19 | 20 | geode::Result HandlerGenerator::generateHandler() { 21 | auto address = reinterpret_cast(m_handler); 22 | auto encode = this->handlerBytes(address); 23 | 24 | GEODE_UNWRAP(Target::get().writeMemory(m_handler, encode.data(), encode.size())); 25 | 26 | return geode::Ok(FunctionData{m_handler, encode.size()}); 27 | } 28 | 29 | geode::Result> HandlerGenerator::generateIntervener(int64_t size) { 30 | auto address = reinterpret_cast(m_address); 31 | auto encode = this->intervenerBytes(address, size); 32 | 33 | return geode::Ok(std::move(encode)); 34 | } 35 | 36 | geode::Result HandlerGenerator::generateTrampoline(uint64_t target) { 37 | GEODE_UNWRAP_INTO(auto offsets, this->relocatedBytes(reinterpret_cast(m_trampoline), target)); 38 | 39 | auto address = reinterpret_cast(m_trampoline) + offsets.m_relocatedBytes.size(); 40 | auto encode = this->trampolineBytes(address, offsets.m_originalOffset); 41 | 42 | std::vector merge; 43 | merge.insert(merge.end(), offsets.m_relocatedBytes.begin(), offsets.m_relocatedBytes.end()); 44 | merge.insert(merge.end(), encode.begin(), encode.end()); 45 | 46 | GEODE_UNWRAP(Target::get().writeMemory(m_trampoline, merge.data(), merge.size())); 47 | 48 | return geode::Ok(TrampolineReturn{FunctionData{m_trampoline, merge.size()}, offsets.m_originalOffset}); 49 | } 50 | 51 | std::vector HandlerGenerator::handlerBytes(uint64_t address) { 52 | return std::vector(); 53 | } 54 | std::vector HandlerGenerator::intervenerBytes(uint64_t address, size_t size) { 55 | return std::vector(); 56 | } 57 | std::vector HandlerGenerator::trampolineBytes(uint64_t address, size_t offset) { 58 | return std::vector(); 59 | } 60 | geode::Result HandlerGenerator::relocatedBytes(uint64_t base, uint64_t target) { 61 | return geode::Ok(HandlerGenerator::RelocateReturn()); 62 | } 63 | 64 | geode::Result WrapperGenerator::generateWrapper() { 65 | return geode::Ok(FunctionData{m_address, 0}); // only windows needs the wrapper 66 | } 67 | 68 | geode::Result WrapperGenerator::generateReverseWrapper() { 69 | return geode::Ok(FunctionData{m_address, 0}); // only windows needs the wrapper 70 | } 71 | 72 | std::vector WrapperGenerator::wrapperBytes(uint64_t address) { 73 | return std::vector(); 74 | } 75 | 76 | std::vector WrapperGenerator::reverseWrapperBytes(uint64_t address) { 77 | return std::vector(); 78 | } -------------------------------------------------------------------------------- /src/generator/Generator.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace tulip::hook { 10 | 11 | class HandlerGenerator { 12 | public: 13 | void* const m_address; 14 | void* const m_trampoline; 15 | void* const m_handler; 16 | void* const m_content; 17 | HandlerMetadata const m_metadata; 18 | 19 | HandlerGenerator( 20 | void* address, void* trampoline, void* handler, void* content, HandlerMetadata const& metadata 21 | ); 22 | 23 | virtual ~HandlerGenerator() = default; 24 | 25 | virtual geode::Result generateHandler(); 26 | virtual geode::Result> generateIntervener(int64_t size); 27 | 28 | struct RelocateReturn { 29 | std::vector m_relocatedBytes; 30 | size_t m_originalOffset; 31 | }; 32 | 33 | struct TrampolineReturn { 34 | FunctionData m_trampoline; 35 | size_t m_originalOffset; 36 | }; 37 | 38 | virtual geode::Result generateTrampoline(uint64_t target); 39 | 40 | virtual std::vector handlerBytes(uint64_t address); 41 | virtual std::vector intervenerBytes(uint64_t address, size_t size); 42 | virtual std::vector trampolineBytes(uint64_t address, size_t offset); 43 | virtual geode::Result relocatedBytes(uint64_t base, uint64_t target); 44 | }; 45 | 46 | class WrapperGenerator { 47 | public: 48 | void* const m_address; 49 | WrapperMetadata const m_metadata; 50 | 51 | virtual ~WrapperGenerator() = default; 52 | 53 | WrapperGenerator(void* address, WrapperMetadata const& metadata); 54 | 55 | virtual geode::Result generateWrapper(); 56 | virtual geode::Result generateReverseWrapper(); 57 | 58 | virtual std::vector wrapperBytes(uint64_t address); 59 | virtual std::vector reverseWrapperBytes(uint64_t address); 60 | }; 61 | } 62 | -------------------------------------------------------------------------------- /src/generator/X64Generator.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "X86Generator.hpp" 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | namespace tulip::hook { 10 | class X64Assembler; 11 | 12 | class X64HandlerGenerator : public X86HandlerGenerator { 13 | public: 14 | using X86HandlerGenerator::X86HandlerGenerator; 15 | 16 | // std::vector handlerBytes(uint64_t address) override; 17 | std::vector intervenerBytes(uint64_t address, size_t size) override; 18 | 19 | geode::Result generateHandler() override; 20 | 21 | geode::Result generateTrampoline(uint64_t target) override; 22 | 23 | geode::Result<> relocateRIPInstruction(cs_insn* insn, uint8_t* buffer, uint64_t& trampolineAddress, uint64_t& originalAddress, int64_t disp) override; 24 | geode::Result<> relocateBranchInstruction(cs_insn* insn, uint8_t* buffer, uint64_t& trampolineAddress, uint64_t& originalAddress, int64_t targetAddress) override; 25 | 26 | private: 27 | size_t preserveRegisters(X64Assembler& a); 28 | void restoreRegisters(X64Assembler& a, size_t size); 29 | 30 | size_t preserveReturnRegisters(X64Assembler& a); 31 | void restoreReturnRegisters(X64Assembler& a, size_t size); 32 | }; 33 | 34 | class X64WrapperGenerator : public X86WrapperGenerator { 35 | public: 36 | using X86WrapperGenerator::X86WrapperGenerator; 37 | 38 | std::vector wrapperBytes(uint64_t address) override; 39 | 40 | #ifdef TULIP_HOOK_WINDOWS 41 | std::vector unwindInfoBytes(uint64_t address); 42 | #endif 43 | 44 | geode::Result generateWrapper() override; 45 | // std::vector reverseWrapperBytes(uint64_t address) override; 46 | }; 47 | } 48 | -------------------------------------------------------------------------------- /src/generator/X86Generator.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Generator.hpp" 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | namespace tulip::hook { 10 | 11 | class X86HandlerGenerator : public HandlerGenerator { 12 | protected: 13 | uint64_t m_modifiedBytesSize = 0; 14 | // this is only relevant for 64-bit relocation, pointer is to the buffer so dont keep this around 15 | std::unordered_map m_shortBranchRelocations; 16 | public: 17 | using HandlerGenerator::HandlerGenerator; 18 | 19 | geode::Result relocatedBytes(uint64_t base, uint64_t target) override; 20 | 21 | std::vector handlerBytes(uint64_t address) override; 22 | std::vector intervenerBytes(uint64_t address, size_t size) override; 23 | 24 | geode::Result generateTrampoline(uint64_t target) override; 25 | 26 | virtual geode::Result<> relocateInstruction(cs_insn* insn, uint8_t* buffer, uint64_t& trampolineAddress, uint64_t& originalAddress); 27 | virtual geode::Result<> relocateRIPInstruction(cs_insn* insn, uint8_t* buffer, uint64_t& trampolineAddress, uint64_t& originalAddress, int64_t disp); 28 | virtual geode::Result<> relocateBranchInstruction(cs_insn* insn, uint8_t* buffer, uint64_t& trampolineAddress, uint64_t& originalAddress, int64_t targetAddress); 29 | }; 30 | 31 | class X86WrapperGenerator : public WrapperGenerator { 32 | public: 33 | using WrapperGenerator::WrapperGenerator; 34 | 35 | geode::Result generateWrapper() override; 36 | // geode::Result generateReverseWrapper() override; 37 | 38 | std::vector wrapperBytes(uint64_t address) override; 39 | // std::vector reverseWrapperBytes(uint64_t address) override; 40 | }; 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/target/DarwinTarget.cpp: -------------------------------------------------------------------------------- 1 | #include "DarwinTarget.hpp" 2 | 3 | #include 4 | 5 | using namespace tulip::hook; 6 | 7 | #if defined(TULIP_HOOK_DARWIN) 8 | 9 | #include 10 | #include /* mach_task_self() */ 11 | #include 12 | #include /* vm_allocate() */ 13 | #include 14 | 15 | geode::Result<> DarwinTarget::allocatePage() { 16 | kern_return_t status; 17 | vm_address_t ret; 18 | 19 | status = vm_allocate(mach_task_self(), &ret, static_cast(PAGE_MAX_SIZE), VM_FLAGS_ANYWHERE); 20 | 21 | if (status != KERN_SUCCESS) { 22 | return geode::Err("Couldn't allocate page"); 23 | } 24 | 25 | m_allocatedPage = reinterpret_cast(ret); 26 | m_currentOffset = 0; 27 | m_remainingOffset = PAGE_MAX_SIZE; 28 | 29 | return this->protectMemory(m_allocatedPage, PAGE_MAX_SIZE, VM_PROT_READ | VM_PROT_EXECUTE); 30 | } 31 | 32 | geode::Result DarwinTarget::getProtection(void* address) { 33 | kern_return_t status; 34 | vm_size_t vmsize; 35 | vm_address_t vmaddress = reinterpret_cast(address); 36 | vm_region_basic_info_data_t info; 37 | mach_msg_type_number_t infoCount = VM_REGION_BASIC_INFO_COUNT_64; 38 | mach_port_t object; 39 | 40 | status = vm_region_64( 41 | mach_task_self(), 42 | &vmaddress, 43 | &vmsize, 44 | VM_REGION_BASIC_INFO_64, 45 | reinterpret_cast(&info), 46 | &infoCount, 47 | &object 48 | ); 49 | 50 | if (status != KERN_SUCCESS) { 51 | return geode::Err("Couldn't get protection"); 52 | } 53 | 54 | return geode::Ok(info.protection); 55 | } 56 | 57 | void DarwinTarget::internalProtectMemory(void* address, size_t size, uint32_t protection, int& errorCode) { 58 | errorCode = vm_protect( 59 | mach_task_self(), 60 | reinterpret_cast(address), 61 | size, 62 | false, 63 | protection 64 | ); 65 | } 66 | void DarwinTarget::internalWriteMemory(void* destination, void const* source, size_t size, int& errorCode) { 67 | errorCode = vm_write( 68 | mach_task_self(), 69 | reinterpret_cast(destination), 70 | reinterpret_cast(source), 71 | static_cast(size) 72 | ); 73 | } 74 | 75 | geode::Result<> DarwinTarget::protectMemory(void* address, size_t size, uint32_t protection) { 76 | kern_return_t status; 77 | 78 | this->internalProtectMemory(address, size, protection, status); 79 | if (status != KERN_SUCCESS) { 80 | return geode::Err("Couldn't protect memory: " + std::to_string(status)); 81 | } 82 | return geode::Ok(); 83 | } 84 | 85 | geode::Result<> DarwinTarget::rawWriteMemory(void* destination, void const* source, size_t size) { 86 | kern_return_t status; 87 | 88 | this->internalWriteMemory(destination, source, size, status); 89 | if (status != KERN_SUCCESS) { 90 | return geode::Err("Couldn't write memory: " + std::to_string(status)); 91 | } 92 | return geode::Ok(); 93 | } 94 | 95 | geode::Result<> DarwinTarget::writeMemory(void* destination, void const* source, size_t size) { 96 | GEODE_UNWRAP_INTO(auto oldProtection, this->getProtection(destination)); 97 | 98 | // with no wx pages, we run into the risk of accidentally marking our code as rw 99 | // (this causes a crash in the result destructor, which is not very good) 100 | // should be fixed now i think 101 | 102 | kern_return_t s1, s2, s3; 103 | this->internalProtectMemory(destination, size, this->getWritableProtection(), s1); 104 | if (s1 != KERN_SUCCESS) { 105 | return geode::Err("Couldn't protect memory to rwc: " + std::to_string(s1)); 106 | } 107 | 108 | this->internalWriteMemory(destination, source, size, s2); 109 | if (s2 != KERN_SUCCESS) { 110 | return geode::Err("Couldn't write memory: " + std::to_string(s2)); 111 | } 112 | 113 | this->internalProtectMemory(destination, size, oldProtection, s3); 114 | if (s3 != KERN_SUCCESS) { 115 | return geode::Err("Couldn't protect memory back: " + std::to_string(s3)); 116 | } 117 | 118 | return geode::Ok(); 119 | } 120 | 121 | uint32_t DarwinTarget::getWritableProtection() { 122 | return VM_PROT_COPY | VM_PROT_READ | VM_PROT_WRITE; 123 | } 124 | 125 | #endif -------------------------------------------------------------------------------- /src/target/DarwinTarget.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #if defined(TULIP_HOOK_DARWIN) 6 | 7 | #include "Target.hpp" 8 | 9 | namespace tulip::hook { 10 | class DarwinTarget : public Target { 11 | public: 12 | using Target::Target; 13 | 14 | geode::Result<> allocatePage() override; 15 | geode::Result getProtection(void* address) override; 16 | geode::Result<> protectMemory(void* address, size_t size, uint32_t protection) override; 17 | geode::Result<> rawWriteMemory(void* destination, void const* source, size_t size) override; 18 | uint32_t getWritableProtection() override; 19 | 20 | geode::Result<> writeMemory(void* destination, void const* source, size_t size) override; 21 | 22 | private: 23 | void internalProtectMemory(void* address, size_t size, uint32_t protection, int& errorCode); 24 | void internalWriteMemory(void* destination, void const* source, size_t size, int& errorCode); 25 | }; 26 | } 27 | 28 | #endif 29 | -------------------------------------------------------------------------------- /src/target/MacosIntelTarget.cpp: -------------------------------------------------------------------------------- 1 | #include "MacosIntelTarget.hpp" 2 | 3 | #include 4 | 5 | using namespace tulip::hook; 6 | 7 | #if defined(TULIP_HOOK_MACOS) && defined(TULIP_HOOK_X64) 8 | 9 | Target& Target::get() { 10 | static MacosIntelTarget ret; 11 | return ret; 12 | } 13 | 14 | geode::Result MacosIntelTarget::openCapstone() { 15 | cs_err status; 16 | 17 | status = cs_open(CS_ARCH_X86, CS_MODE_64, &m_capstone); 18 | if (status != CS_ERR_OK) { 19 | return geode::Err("Couldn't open capstone"); 20 | } 21 | 22 | return geode::Ok(m_capstone); 23 | } 24 | 25 | std::unique_ptr MacosIntelTarget::getHandlerGenerator( 26 | void* address, void* trampoline, void* handler, void* content, HandlerMetadata const& metadata 27 | ) { 28 | return std::make_unique(address, trampoline, handler, content, metadata); 29 | } 30 | 31 | std::unique_ptr MacosIntelTarget::getWrapperGenerator(void* address, WrapperMetadata const& metadata) { 32 | return std::make_unique(address, metadata); 33 | } 34 | 35 | #endif -------------------------------------------------------------------------------- /src/target/MacosIntelTarget.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #if defined(TULIP_HOOK_MACOS) && defined(TULIP_HOOK_X64) 6 | 7 | #include "../generator/X64Generator.hpp" 8 | #include "DarwinTarget.hpp" 9 | 10 | namespace tulip::hook { 11 | class MacosIntelTarget : public DarwinTarget { 12 | public: 13 | using DarwinTarget::DarwinTarget; 14 | 15 | geode::Result openCapstone() override; 16 | 17 | std::unique_ptr getHandlerGenerator( 18 | void* address, void* trampoline, void* handler, void* content, HandlerMetadata const& metadata 19 | ) override; 20 | std::unique_ptr getWrapperGenerator(void* address, WrapperMetadata const& metadata) override; 21 | }; 22 | } 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /src/target/MacosM1Target.cpp: -------------------------------------------------------------------------------- 1 | #include "MacosM1Target.hpp" 2 | 3 | #include 4 | 5 | using namespace tulip::hook; 6 | 7 | #if defined(TULIP_HOOK_MACOS) && defined(TULIP_HOOK_ARMV8) 8 | 9 | Target& Target::get() { 10 | static MacosM1Target ret; 11 | return ret; 12 | } 13 | 14 | geode::Result MacosM1Target::openCapstone() { 15 | // cs_err status; 16 | 17 | // status = cs_open(CS_ARCH_X86, CS_MODE_64, &m_capstone); 18 | // if (status != CS_ERR_OK) { 19 | return geode::Err("Couldn't open capstone"); 20 | // } 21 | 22 | // return geode::Ok(m_capstone); 23 | } 24 | 25 | std::unique_ptr MacosM1Target::getHandlerGenerator( 26 | void* address, void* trampoline, void* handler, void* content, HandlerMetadata const& metadata 27 | ) { 28 | return std::make_unique(address, trampoline, handler, content, metadata); 29 | } 30 | 31 | std::unique_ptr MacosM1Target::getWrapperGenerator(void* address, WrapperMetadata const& metadata) { 32 | return std::make_unique(address, metadata); 33 | } 34 | 35 | #endif -------------------------------------------------------------------------------- /src/target/MacosM1Target.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #if defined(TULIP_HOOK_MACOS) && defined(TULIP_HOOK_ARMV8) 6 | 7 | #include "../generator/ArmV8Generator.hpp" 8 | #include "DarwinTarget.hpp" 9 | 10 | namespace tulip::hook { 11 | class MacosM1Target : public DarwinTarget { 12 | public: 13 | using DarwinTarget::DarwinTarget; 14 | 15 | geode::Result openCapstone() override; 16 | 17 | std::unique_ptr getHandlerGenerator( 18 | void* address, void* trampoline, void* handler, void* content, HandlerMetadata const& metadata 19 | ) override; 20 | std::unique_ptr getWrapperGenerator(void* address, WrapperMetadata const& metadata) override; 21 | }; 22 | } 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /src/target/PlatformTarget.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "MacosIntelTarget.hpp" 4 | #include "MacosM1Target.hpp" 5 | #include "PosixArmV7Target.hpp" 6 | #include "PosixArmV8Target.hpp" 7 | #include "Windows32Target.hpp" -------------------------------------------------------------------------------- /src/target/PosixArmV7Target.cpp: -------------------------------------------------------------------------------- 1 | #include "PosixArmV7Target.hpp" 2 | 3 | #include 4 | #include 5 | 6 | using namespace tulip::hook; 7 | 8 | #if defined(TULIP_HOOK_POSIX) && defined(TULIP_HOOK_ARMV7) 9 | 10 | #include 11 | 12 | Target& Target::get() { 13 | static PosixArmV7Target ret; 14 | return ret; 15 | } 16 | 17 | geode::Result PosixArmV7Target::openCapstone() { 18 | //cs_err status; 19 | 20 | //status = cs_open(CS_ARCH_ARM, CS_MODE_32, &m_capstone); 21 | 22 | //if (status != CS_ERR_OK) { 23 | return geode::Err("Couldn't open capstone"); 24 | //} 25 | 26 | //return geode::Ok(m_capstone); 27 | } 28 | 29 | std::unique_ptr PosixArmV7Target::getHandlerGenerator( 30 | void* address, void* trampoline, void* handler, void* content, HandlerMetadata const& metadata 31 | ) { 32 | return std::make_unique(address, trampoline, handler, content, metadata); 33 | } 34 | 35 | std::unique_ptr PosixArmV7Target::getWrapperGenerator(void* address, WrapperMetadata const& metadata) { 36 | return std::make_unique(address, metadata); 37 | } 38 | 39 | // Thumb is very fun to deal with! 40 | void* PosixArmV7Target::getRealPtr(void* ptr) { 41 | return reinterpret_cast(reinterpret_cast(ptr) & (~1)); 42 | } 43 | void* PosixArmV7Target::getRealPtrAs(void* ptr, void* lookup) { 44 | return reinterpret_cast( 45 | reinterpret_cast(this->getRealPtr(ptr)) | 46 | (reinterpret_cast(lookup) & 1) 47 | ); 48 | } 49 | 50 | #endif 51 | -------------------------------------------------------------------------------- /src/target/PosixArmV7Target.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #if defined(TULIP_HOOK_POSIX) && defined(TULIP_HOOK_ARMV7) 6 | 7 | #include "../generator/ArmV7Generator.hpp" 8 | #include "PosixTarget.hpp" 9 | 10 | namespace tulip::hook { 11 | class PosixArmV7Target : public PosixTarget { 12 | public: 13 | using PosixTarget::PosixTarget; 14 | 15 | geode::Result openCapstone() override; 16 | 17 | std::unique_ptr getHandlerGenerator( 18 | void* address, void* trampoline, void* handler, void* content, HandlerMetadata const& metadata 19 | ) override; 20 | std::unique_ptr getWrapperGenerator(void* address, WrapperMetadata const& metadata) override; 21 | 22 | void* getRealPtr(void* ptr) override; 23 | void* getRealPtrAs(void* ptr, void* lookup) override; 24 | }; 25 | } 26 | 27 | #endif 28 | -------------------------------------------------------------------------------- /src/target/PosixArmV8Target.cpp: -------------------------------------------------------------------------------- 1 | #include "PosixArmV8Target.hpp" 2 | 3 | #include 4 | #include 5 | 6 | using namespace tulip::hook; 7 | 8 | #if defined(TULIP_HOOK_POSIX) && defined(TULIP_HOOK_ARMV8) 9 | 10 | Target& Target::get() { 11 | static PosixArmV8Target ret; 12 | return ret; 13 | } 14 | 15 | geode::Result PosixArmV8Target::openCapstone() { 16 | //cs_err status; 17 | 18 | //status = cs_open(CS_ARCH_ARM64, static_cast(0), &m_capstone); 19 | 20 | //if (status != CS_ERR_OK) { 21 | return geode::Err("Couldn't open capstone"); 22 | //} 23 | 24 | //return geode::Ok(m_capstone); 25 | } 26 | 27 | std::unique_ptr PosixArmV8Target::getHandlerGenerator( 28 | void* address, void* trampoline, void* handler, void* content, HandlerMetadata const& metadata 29 | ) { 30 | return std::make_unique(address, trampoline, handler, content, metadata); 31 | } 32 | 33 | std::unique_ptr PosixArmV8Target::getWrapperGenerator(void* address, WrapperMetadata const& metadata) { 34 | return std::make_unique(address, metadata); 35 | } 36 | 37 | #endif 38 | -------------------------------------------------------------------------------- /src/target/PosixArmV8Target.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #if defined(TULIP_HOOK_POSIX) && defined(TULIP_HOOK_ARMV8) 6 | 7 | #include "../generator/ArmV8Generator.hpp" 8 | #include "PosixTarget.hpp" 9 | 10 | namespace tulip::hook { 11 | class PosixArmV8Target : public PosixTarget { 12 | public: 13 | using PosixTarget::PosixTarget; 14 | 15 | geode::Result openCapstone() override; 16 | 17 | std::unique_ptr getHandlerGenerator( 18 | void* address, void* trampoline, void* handler, void* content, HandlerMetadata const& metadata 19 | ) override; 20 | std::unique_ptr getWrapperGenerator(void* address, WrapperMetadata const& metadata) override; 21 | }; 22 | } 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /src/target/PosixTarget.cpp: -------------------------------------------------------------------------------- 1 | #include "PosixTarget.hpp" 2 | 3 | #include 4 | #include 5 | 6 | using namespace tulip::hook; 7 | 8 | #if defined(TULIP_HOOK_POSIX) 9 | 10 | #include 11 | #include 12 | 13 | geode::Result<> PosixTarget::allocatePage() { 14 | auto const protection = PROT_READ | PROT_WRITE | PROT_EXEC; 15 | auto const flags = MAP_PRIVATE | MAP_ANONYMOUS; 16 | 17 | auto ret = mmap(nullptr, 0x4000, protection, flags, -1, 0); 18 | if (ret == MAP_FAILED) { 19 | return geode::Err("Couldn't allocate page"); 20 | } 21 | 22 | m_allocatedPage = reinterpret_cast(ret); 23 | m_currentOffset = 0; 24 | m_remainingOffset = 0x4000; 25 | 26 | return geode::Ok(); 27 | } 28 | 29 | geode::Result PosixTarget::getProtection(void* address) { 30 | // why 31 | // just why does posix not have get protection 32 | return geode::Ok(this->getWritableProtection()); 33 | } 34 | 35 | geode::Result<> PosixTarget::protectMemory(void* address, size_t size, uint32_t protection) { 36 | auto const pageSize = getpagesize(); 37 | auto const pageMask = pageSize - 1; 38 | 39 | auto const ptr = reinterpret_cast(address); 40 | auto const alignedPtr = ptr & (~pageMask); 41 | auto const beginSize = ptr - alignedPtr; 42 | auto const pageCount = (beginSize + size + pageMask) / pageSize; 43 | auto const alignedSize = pageCount * pageSize; 44 | 45 | auto status = mprotect(reinterpret_cast(alignedPtr), alignedSize, protection); 46 | 47 | if (status != 0) { 48 | return geode::Err("Couldn't protect memory"); 49 | } 50 | return geode::Ok(); 51 | } 52 | 53 | geode::Result<> PosixTarget::rawWriteMemory(void* destination, void const* source, size_t size) { 54 | auto res = this->protectMemory(destination, size, this->getWritableProtection()); 55 | 56 | if (!res) { 57 | return geode::Err("Couldn't protect memory"); 58 | } 59 | 60 | memcpy(destination, source, size); 61 | 62 | return geode::Ok(); 63 | } 64 | 65 | uint32_t PosixTarget::getWritableProtection() { 66 | return PROT_READ | PROT_WRITE | PROT_EXEC; 67 | } 68 | 69 | #endif 70 | -------------------------------------------------------------------------------- /src/target/PosixTarget.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #if defined(TULIP_HOOK_POSIX) 6 | 7 | #include "Target.hpp" 8 | 9 | namespace tulip::hook { 10 | 11 | class PosixTarget : public Target { 12 | public: 13 | using Target::Target; 14 | 15 | geode::Result<> allocatePage() override; 16 | geode::Result getProtection(void* address) override; 17 | geode::Result<> protectMemory(void* address, size_t size, uint32_t protection) override; 18 | geode::Result<> rawWriteMemory(void* destination, void const* source, size_t size) override; 19 | uint32_t getWritableProtection() override; 20 | }; 21 | 22 | } 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /src/target/Target.cpp: -------------------------------------------------------------------------------- 1 | #include "Target.hpp" 2 | 3 | using namespace tulip::hook; 4 | 5 | geode::Result Target::allocateArea(size_t size) { 6 | if (m_remainingOffset < size) { 7 | GEODE_UNWRAP(this->allocatePage()); 8 | } 9 | 10 | auto ret = reinterpret_cast(m_allocatedPage) + m_currentOffset; 11 | 12 | m_remainingOffset -= size; 13 | m_currentOffset += size; 14 | 15 | return geode::Ok(reinterpret_cast(ret)); 16 | } 17 | 18 | geode::Result<> Target::writeMemory(void* destination, void const* source, size_t size) { 19 | GEODE_UNWRAP(this->rawWriteMemory(destination, source, size)); 20 | 21 | return geode::Ok(); 22 | } 23 | 24 | void Target::closeCapstone() { 25 | #if defined(TULIP_HOOK_X86) || defined(TULIP_HOOK_X64) 26 | cs_close(&m_capstone); 27 | #endif 28 | m_capstone = 0; 29 | } 30 | 31 | csh Target::getCapstone() { 32 | return m_capstone; 33 | } 34 | 35 | void* Target::getRealPtr(void* ptr) { 36 | return ptr; 37 | } 38 | 39 | void* Target::getRealPtrAs(void* ptr, void* lookup) { 40 | return ptr; 41 | } 42 | -------------------------------------------------------------------------------- /src/target/Target.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #if defined(TULIP_HOOK_X86) || defined(TULIP_HOOK_X64) 10 | #include 11 | #else 12 | typedef size_t csh; 13 | #endif 14 | 15 | namespace tulip::hook { 16 | class HandlerGenerator; 17 | class WrapperGenerator; 18 | 19 | class Target { 20 | protected: 21 | csh m_capstone = 0; 22 | 23 | void* m_allocatedPage = nullptr; 24 | size_t m_currentOffset = 0; 25 | size_t m_remainingOffset = 0; 26 | 27 | public: 28 | static Target& get(); 29 | 30 | virtual ~Target() = default; 31 | 32 | geode::Result allocateArea(size_t size); 33 | 34 | virtual geode::Result<> writeMemory(void* destination, void const* source, size_t size); 35 | 36 | virtual geode::Result openCapstone() = 0; 37 | void closeCapstone(); 38 | csh getCapstone(); 39 | 40 | virtual geode::Result<> allocatePage() = 0; 41 | virtual geode::Result getProtection(void* address) = 0; 42 | virtual geode::Result<> protectMemory(void* address, size_t size, uint32_t protection) = 0; 43 | virtual geode::Result<> rawWriteMemory(void* destination, void const* source, size_t size) = 0; 44 | virtual uint32_t getWritableProtection() = 0; 45 | 46 | virtual std::unique_ptr getHandlerGenerator( 47 | void* address, void* trampoline, void* handler, void* content, HandlerMetadata const& metadata 48 | ) = 0; 49 | virtual std::unique_ptr getWrapperGenerator(void* address, WrapperMetadata const& metadata) = 0; 50 | // sorry :( virtual BaseAssembler* getAssembler(int64_t baseAddress); 51 | 52 | // These just exist because of arm7! fun! 53 | virtual void* getRealPtr(void* ptr); 54 | virtual void* getRealPtrAs(void* ptr, void* lookup); 55 | }; 56 | }; 57 | -------------------------------------------------------------------------------- /src/target/Windows32Target.cpp: -------------------------------------------------------------------------------- 1 | #include "Windows32Target.hpp" 2 | 3 | #include 4 | 5 | using namespace tulip::hook; 6 | 7 | #if defined(TULIP_HOOK_WINDOWS) 8 | 9 | #if defined(TULIP_HOOK_X86) 10 | 11 | Target& Target::get() { 12 | static Windows32Target ret; 13 | return ret; 14 | } 15 | 16 | #endif 17 | 18 | #define WIN32_LEAN_AND_MEAN 19 | #include 20 | 21 | #include "../Pool.hpp" 22 | #include "../Handler.hpp" 23 | #include "../Wrapper.hpp" 24 | 25 | PVOID __declspec(dllexport) GeodeFunctionTableAccess64(HANDLE hProcess, DWORD64 AddrBase) { 26 | for (auto& [handle, handler] : Pool::get().m_handlers) { 27 | auto handlerBegin = reinterpret_cast(handler->m_handler); 28 | auto handlerEnd = handlerBegin + handler->m_handlerSize; 29 | 30 | auto tramplineBegin = reinterpret_cast(handler->m_trampoline); 31 | auto tramplineEnd = tramplineBegin + handler->m_trampolineSize; 32 | 33 | if (AddrBase >= handlerBegin && AddrBase < handlerEnd) { 34 | // std::stringstream ss; 35 | // ss << "Control PC: " << std::hex << AddrBase << " Handler Begin: " << handlerBegin << " Handler End: " << handlerEnd; 36 | // MessageBoxA(nullptr, ss.str().c_str(), "Error Loading Geode", MB_ICONERROR); 37 | return reinterpret_cast(handlerEnd); 38 | } 39 | 40 | if (AddrBase >= tramplineBegin && AddrBase < tramplineEnd) { 41 | // std::stringstream ss; 42 | // ss << "Control PC: " << std::hex << AddrBase << " Trampline Begin: " << tramplineBegin << " Trampline End: " << tramplineEnd; 43 | // MessageBoxA(nullptr, ss.str().c_str(), "Error Loading Geode", MB_ICONERROR); 44 | return reinterpret_cast(tramplineEnd); 45 | } 46 | } 47 | 48 | for (auto& [handle, wrapper] : Wrapper::get().m_wrappers) { 49 | auto wrapperBegin = reinterpret_cast(wrapper.m_address); 50 | auto wrapperEnd = wrapperBegin + wrapper.m_size; 51 | 52 | if (AddrBase >= wrapperBegin && AddrBase < wrapperEnd) { 53 | // std::stringstream ss; 54 | // ss << "Control PC: " << std::hex << AddrBase << " Wrapper Begin: " << wrapperBegin << " Wrapper End: " << wrapperEnd; 55 | // MessageBoxA(nullptr, ss.str().c_str(), "Error Loading Geode", MB_ICONERROR); 56 | return reinterpret_cast(wrapperEnd); 57 | } 58 | } 59 | 60 | return nullptr; 61 | } 62 | 63 | geode::Result<> Windows32Target::allocatePage() { 64 | m_allocatedPage = VirtualAlloc(nullptr, 0x4000, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READ); 65 | 66 | if (!m_allocatedPage) { 67 | return geode::Err("Unable to allocate memory: " + std::to_string(GetLastError())); 68 | } 69 | 70 | m_currentOffset = 0; 71 | m_remainingOffset = 0x4000; 72 | 73 | return geode::Ok(); 74 | } 75 | 76 | geode::Result Windows32Target::getProtection(void* address) { 77 | MEMORY_BASIC_INFORMATION information; 78 | 79 | if (!VirtualQuery(address, &information, sizeof(MEMORY_BASIC_INFORMATION))) { 80 | return geode::Err("Unable to query memory protection information"); 81 | } 82 | 83 | return geode::Ok(information.Protect); 84 | } 85 | 86 | geode::Result<> Windows32Target::protectMemory(void* address, size_t size, uint32_t protection) { 87 | DWORD oldProtection; 88 | 89 | if (!VirtualProtect(address, size, protection, &oldProtection)) { 90 | return geode::Err("Unable to apply memory protection"); 91 | } 92 | 93 | return geode::Ok(); 94 | } 95 | 96 | geode::Result<> Windows32Target::rawWriteMemory(void* destination, void const* source, size_t size) { 97 | DWORD oldProtection; 98 | 99 | // protect memory to be writable 100 | if (!VirtualProtect(destination, size, this->getWritableProtection(), &oldProtection)) { 101 | return geode::Err("Unable to protect memory"); 102 | } 103 | 104 | std::memcpy(destination, source, size); 105 | 106 | // restore old protection 107 | VirtualProtect(destination, size, oldProtection, &oldProtection); 108 | 109 | return geode::Ok(); 110 | } 111 | 112 | uint32_t Windows32Target::getWritableProtection() { 113 | return PAGE_READWRITE; 114 | } 115 | 116 | geode::Result Windows32Target::openCapstone() { 117 | cs_err status; 118 | 119 | status = cs_open(CS_ARCH_X86, CS_MODE_32, &m_capstone); 120 | if (status != CS_ERR_OK) { 121 | return geode::Err("Couldn't open capstone"); 122 | } 123 | 124 | return geode::Ok(m_capstone); 125 | } 126 | 127 | std::unique_ptr Windows32Target::getHandlerGenerator( 128 | void* address, void* trampoline, void* handler, void* content, HandlerMetadata const& metadata 129 | ) { 130 | return std::make_unique(address, trampoline, handler, content, metadata); 131 | } 132 | 133 | std::unique_ptr Windows32Target::getWrapperGenerator(void* address, WrapperMetadata const& metadata) { 134 | return std::make_unique(address, metadata); 135 | } 136 | 137 | #endif -------------------------------------------------------------------------------- /src/target/Windows32Target.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #if defined(TULIP_HOOK_WINDOWS) 6 | 7 | #include "../generator/X86Generator.hpp" 8 | #include "Target.hpp" 9 | 10 | namespace tulip::hook { 11 | class Windows32Target : public Target { 12 | public: 13 | using Target::Target; 14 | 15 | geode::Result openCapstone() override; 16 | 17 | geode::Result<> allocatePage() override; 18 | geode::Result getProtection(void* address) override; 19 | geode::Result<> protectMemory(void* address, size_t size, uint32_t protection) override; 20 | geode::Result<> rawWriteMemory(void* destination, void const* source, size_t size) override; 21 | uint32_t getWritableProtection() override; 22 | 23 | std::unique_ptr getHandlerGenerator( 24 | void* address, void* trampoline, void* handler, void* content, HandlerMetadata const& metadata 25 | ) override; 26 | std::unique_ptr getWrapperGenerator(void* address, WrapperMetadata const& metadata) override; 27 | }; 28 | } 29 | 30 | #endif 31 | -------------------------------------------------------------------------------- /src/target/Windows64Target.cpp: -------------------------------------------------------------------------------- 1 | #include "Windows64Target.hpp" 2 | 3 | #include 4 | #include 5 | 6 | using namespace tulip::hook; 7 | 8 | #if defined(TULIP_HOOK_WINDOWS) && defined(TULIP_HOOK_X64) 9 | 10 | #include "../Pool.hpp" 11 | #include "../Handler.hpp" 12 | #include "../Wrapper.hpp" 13 | #include 14 | #include 15 | 16 | Target& Target::get() { 17 | static Windows64Target ret; 18 | return ret; 19 | } 20 | 21 | geode::Result<> Windows64Target::allocatePage() { 22 | m_allocatedPage = VirtualAlloc(nullptr, 0x10000, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READ); 23 | 24 | if (!m_allocatedPage) { 25 | return geode::Err("Unable to allocate memory: " + std::to_string(GetLastError())); 26 | } 27 | 28 | m_currentOffset = 0; 29 | m_remainingOffset = 0x10000; 30 | 31 | RtlInstallFunctionTableCallback( 32 | reinterpret_cast(m_allocatedPage) | 0x3, 33 | reinterpret_cast(m_allocatedPage), 34 | 0x10000, 35 | +[](DWORD64 controlPc, PVOID context) -> PRUNTIME_FUNCTION { 36 | for (auto& [handle, handler] : Pool::get().m_handlers) { 37 | auto handlerBegin = reinterpret_cast(handler->m_handler); 38 | auto handlerEnd = handlerBegin + handler->m_handlerSize; 39 | 40 | auto tramplineBegin = reinterpret_cast(handler->m_trampoline); 41 | auto tramplineEnd = tramplineBegin + handler->m_trampolineSize; 42 | 43 | if (controlPc >= handlerBegin && controlPc < handlerEnd) { 44 | // std::stringstream ss; 45 | // ss << "Control PC: " << std::hex << controlPc << " Handler Begin: " << handlerBegin << " Handler End: " << handlerEnd; 46 | // MessageBoxA(nullptr, ss.str().c_str(), "Error Loading Geode", MB_ICONERROR); 47 | return reinterpret_cast(handlerEnd); 48 | } 49 | 50 | if (controlPc >= tramplineBegin && controlPc < tramplineEnd) { 51 | // std::stringstream ss; 52 | // ss << "Control PC: " << std::hex << controlPc << " Trampline Begin: " << tramplineBegin << " Trampline End: " << tramplineEnd; 53 | // MessageBoxA(nullptr, ss.str().c_str(), "Error Loading Geode", MB_ICONERROR); 54 | return reinterpret_cast(tramplineEnd); 55 | } 56 | } 57 | 58 | for (auto& [handle, wrapper] : Wrapper::get().m_wrappers) { 59 | auto wrapperBegin = reinterpret_cast(wrapper.m_address); 60 | auto wrapperEnd = wrapperBegin + wrapper.m_size; 61 | 62 | if (controlPc >= wrapperBegin && controlPc < wrapperEnd) { 63 | // std::stringstream ss; 64 | // ss << "Control PC: " << std::hex << controlPc << " Wrapper Begin: " << wrapperBegin << " Wrapper End: " << wrapperEnd; 65 | // MessageBoxA(nullptr, ss.str().c_str(), "Error Loading Geode", MB_ICONERROR); 66 | return reinterpret_cast(wrapperEnd); 67 | } 68 | } 69 | 70 | return nullptr; 71 | }, 72 | nullptr, 73 | nullptr 74 | ); 75 | 76 | return geode::Ok(); 77 | } 78 | 79 | geode::Result Windows64Target::openCapstone() { 80 | cs_err status; 81 | 82 | status = cs_open(CS_ARCH_X86, CS_MODE_64, &m_capstone); 83 | if (status != CS_ERR_OK) { 84 | return geode::Err("Couldn't open capstone"); 85 | } 86 | 87 | return geode::Ok(m_capstone); 88 | } 89 | 90 | std::unique_ptr Windows64Target::getHandlerGenerator( 91 | void* address, void* trampoline, void* handler, void* content, HandlerMetadata const& metadata 92 | ) { 93 | return std::make_unique(address, trampoline, handler, content, metadata); 94 | } 95 | 96 | std::unique_ptr Windows64Target::getWrapperGenerator(void* address, WrapperMetadata const& metadata) { 97 | return std::make_unique(address, metadata); 98 | } 99 | 100 | #endif -------------------------------------------------------------------------------- /src/target/Windows64Target.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #if defined(TULIP_HOOK_WINDOWS) && defined(TULIP_HOOK_X64) 6 | 7 | #include "../generator/X64Generator.hpp" 8 | #include "Windows32Target.hpp" 9 | 10 | namespace tulip::hook { 11 | class Windows64Target : public Windows32Target { 12 | public: 13 | using Windows32Target::Windows32Target; 14 | 15 | geode::Result openCapstone() override; 16 | 17 | geode::Result<> allocatePage() override; 18 | std::unique_ptr getHandlerGenerator( 19 | void* address, void* trampoline, void* handler, void* content, HandlerMetadata const& metadata 20 | ) override; 21 | std::unique_ptr getWrapperGenerator(void* address, WrapperMetadata const& metadata) override; 22 | }; 23 | } 24 | 25 | #endif 26 | -------------------------------------------------------------------------------- /test/Assembler.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | inline std::vector operator""_bytes(char const* data, size_t size) { 6 | return {reinterpret_cast(data), reinterpret_cast(data + size)}; 7 | } -------------------------------------------------------------------------------- /test/Assembler64.cpp: -------------------------------------------------------------------------------- 1 | #include "../src/assembler/X64Assembler.hpp" 2 | #include "Assembler.hpp" 3 | 4 | #include 5 | 6 | using namespace tulip::hook; 7 | 8 | using enum X64Register; 9 | static RegMem64 m; 10 | 11 | TEST(X64AssemblerTest, Nop) { 12 | X64Assembler a(0x123); 13 | a.nop(); 14 | EXPECT_EQ(a.buffer(), "\x90"_bytes); 15 | } 16 | 17 | TEST(X64AssemblerTest, Jmp) { 18 | X64Assembler a(0x123); 19 | a.jmp(0xb00b5); 20 | a.jmp(RCX); 21 | a.jmp(R8); 22 | a.jmp(0x123); 23 | EXPECT_EQ(a.buffer(), "\xE9\x8D\xFF\x0A\x00\xFF\xE1\x41\xFF\xE0\xEB\xF4"_bytes); 24 | } 25 | 26 | TEST(X64AssemblerTest, Jmp8) { 27 | X64Assembler a(0x123); 28 | a.jmp8("label"); 29 | a.write32(0x80085); 30 | a.label("label"); 31 | a.updateLabels(); 32 | EXPECT_EQ(a.buffer(), "\xEB\x04\x85\x00\x08\x00"_bytes); 33 | } 34 | 35 | TEST(X64AssemblerTest, Call) { 36 | X64Assembler a(0x123); 37 | a.call(R13); 38 | a.call(RSP); 39 | EXPECT_EQ(a.buffer(), "\x41\xFF\xD5\xFF\xD4"_bytes); 40 | } 41 | 42 | TEST(X64AssemblerTest, Push) { 43 | X64Assembler a(0x123); 44 | a.push(RAX); 45 | a.push(R12); 46 | a.push(m[RSP + 0x10]); 47 | EXPECT_EQ(a.buffer(), "\x50\x41\x54\xFF\x74\x24\x10"_bytes); 48 | } 49 | 50 | TEST(X64AssemblerTest, Mov) { 51 | X64Assembler a(0x123); 52 | a.mov(R8, 10); 53 | a.mov(RAX, RAX); 54 | a.mov(R9, R8); 55 | a.mov(RCX, m[R10 + 4]); 56 | a.mov(R9, m[RBP + 4]); 57 | a.mov(m[RBP + 4], RSP); 58 | a.mov(m[R13], R8); 59 | EXPECT_EQ( 60 | a.buffer(), 61 | "\x49\xc7\xc0\x0a\x00\x00\x00\x48\x89\xC0\x4D\x89\xC1\x49\x8B\x4A\x04\x4C\x8B\x4D\x04\x48\x89\x65\x04\x4D\x89\x45\x00"_bytes 62 | ); 63 | } 64 | 65 | TEST(X64AssemblerTest, Movsd) { 66 | X64Assembler a(0x123); 67 | a.movsd(m[RSP], XMM0); 68 | a.movsd(XMM1, m[RSP + 4]); 69 | EXPECT_EQ(a.buffer(), "\xF2\x0F\x11\x04\x24\xF2\x0F\x10\x4C\x24\x04"_bytes); 70 | } 71 | 72 | TEST(X64AssemblerTest, Movss) { 73 | X64Assembler a(0x123); 74 | a.movss(m[RSP], XMM0); 75 | a.movss(XMM1, m[RSP + 4]); 76 | EXPECT_EQ(a.buffer(), "\xF3\x0F\x11\x04\x24\xF3\x0F\x10\x4C\x24\x04"_bytes); 77 | } 78 | 79 | TEST(X64AssemblerTest, Movaps) { 80 | X64Assembler a(0x123); 81 | a.movaps(m[RSP], XMM0); 82 | a.movaps(XMM1, m[RSP + 4]); 83 | EXPECT_EQ(a.buffer(), "\x0F\x29\x04\x24\x0F\x28\x4C\x24\x04"_bytes); 84 | } 85 | 86 | TEST(X64AssemblerTest, Label) { 87 | X64Assembler a(0x123); 88 | a.mov(RAX, "label"); 89 | a.lea(RCX, "label"); 90 | a.label("label"); 91 | a.write64(0x80085); 92 | a.updateLabels(); 93 | EXPECT_EQ( 94 | a.buffer(), "\x48\x8B\x05\x07\x00\x00\x00\x48\x8D\x0D\x00\x00\x00\x00\x85\x00\x08\x00\x00\x00\x00\x00"_bytes 95 | ); 96 | } 97 | 98 | TEST(X64AssemblerTest, Xchg) { 99 | X64Assembler a(0x123); 100 | a.xchg(RCX, RDX); 101 | a.xchg(RBX, R8); 102 | EXPECT_EQ(a.buffer(), "\x48\x87\xD1\x4C\x87\xC3"_bytes); 103 | } -------------------------------------------------------------------------------- /test/Assembler86.cpp: -------------------------------------------------------------------------------- 1 | #include "../src/assembler/X86Assembler.hpp" 2 | #include "Assembler.hpp" 3 | 4 | #include 5 | 6 | using namespace tulip::hook; 7 | 8 | using enum X86Register; 9 | static RegMem32 m; 10 | 11 | TEST(X86AssemblerTest, Nop) { 12 | X86Assembler a(0x123); 13 | a.nop(); 14 | EXPECT_EQ(a.buffer(), "\x90"_bytes); 15 | } 16 | 17 | TEST(X86AssemblerTest, Jmp) { 18 | X86Assembler a(0x123); 19 | a.jmp(0xb00b5); 20 | a.jmp(ECX); 21 | EXPECT_EQ(a.buffer(), "\xE9\x8D\xFF\x0A\x00\xFF\xE1"_bytes); 22 | } 23 | 24 | TEST(X86AssemblerTest, Call) { 25 | X86Assembler a(0); 26 | a.call(0x456); 27 | a.call(EAX); 28 | a.call(EBP); 29 | a.call(ESP); 30 | EXPECT_EQ(a.buffer(), "\xE8\x51\x04\x00\x00\xFF\xD0\xFF\xD5\xFF\xD4"_bytes); 31 | } 32 | 33 | TEST(X86AssemblerTest, Push) { 34 | X86Assembler a(0x123); 35 | a.push(EAX); 36 | a.push(ESP); 37 | a.push(m[ESP + 0x10]); 38 | EXPECT_EQ(a.buffer(), "\x50\x54\xFF\x74\x24\x10"_bytes); 39 | } 40 | 41 | TEST(X86AssemblerTest, Mov) { 42 | X86Assembler a(0x123); 43 | a.mov(EAX, 10); 44 | a.mov(EAX, EAX); 45 | a.mov(ECX, EAX); 46 | a.mov(ECX, m[EDX + 4]); 47 | a.mov(ECX, m[EBP + 4]); 48 | a.mov(m[EBP + 4], ESP); 49 | a.mov(m[EBP], EAX); 50 | EXPECT_EQ(a.buffer(), "\xb8\x0a\x00\x00\x00\x89\xC0\x89\xC1\x8B\x4A\x04\x8B\x4D\x04\x89\x65\x04\x89\x45\x00"_bytes); 51 | } 52 | 53 | TEST(X86AssemblerTest, Movsd) { 54 | X86Assembler a(0x123); 55 | a.movsd(m[ESP], XMM0); 56 | a.movsd(XMM1, m[ESP + 4]); 57 | EXPECT_EQ(a.buffer(), "\xF2\x0F\x11\x04\x24\xF2\x0F\x10\x4C\x24\x04"_bytes); 58 | } 59 | 60 | TEST(X86AssemblerTest, Movss) { 61 | X86Assembler a(0x123); 62 | a.movss(m[ESP], XMM0); 63 | a.movss(XMM1, m[ESP + 4]); 64 | EXPECT_EQ(a.buffer(), "\xF3\x0F\x11\x04\x24\xF3\x0F\x10\x4C\x24\x04"_bytes); 65 | } 66 | 67 | TEST(X86AssemblerTest, Movaps) { 68 | X86Assembler a(0x123); 69 | a.movaps(m[ESP], XMM0); 70 | a.movaps(XMM1, m[ESP + 4]); 71 | EXPECT_EQ(a.buffer(), "\x0F\x29\x04\x24\x0F\x28\x4C\x24\x04"_bytes); 72 | } 73 | 74 | TEST(X86AssemblerTest, Label) { 75 | X86Assembler a(0x123); 76 | a.mov(EAX, "label"); 77 | a.lea(ECX, "label"); 78 | a.label("label"); 79 | a.write32(0x80085); 80 | a.updateLabels(); 81 | EXPECT_EQ(a.buffer(), "\x8B\x05\x2f\x01\x00\x00\x8D\x0D\x2f\x01\x00\x00\x85\x00\x08\x00"_bytes); 82 | } 83 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.21) 2 | 3 | project(TulipHookTest LANGUAGES CXX C) 4 | 5 | # For Windows: Prevent overriding the parent project's compiler/linker settings 6 | set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) 7 | CPMAddPackage("gh:google/googletest#f8d7d77") 8 | 9 | enable_testing() 10 | 11 | add_executable(${PROJECT_NAME} Assembler86.cpp Assembler64.cpp Hook.cpp) 12 | target_link_libraries(${PROJECT_NAME} PUBLIC GTest::gtest_main TulipHook) 13 | 14 | if(WIN32) 15 | if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND 16 | CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "GNU") 17 | # idk 18 | else() 19 | set_target_properties(${PROJECT_NAME} PROPERTIES LINK_FLAGS "/INCREMENTAL:NO") 20 | endif() 21 | 22 | if (CMAKE_SIZEOF_VOID_P EQUAL 8) 23 | add_executable(WinTest misc/WinTest.cpp) 24 | target_link_libraries(WinTest PUBLIC TulipHook) 25 | # set_target_properties(WinTest PROPERTIES LINK_FLAGS "/INCREMENTAL:NO") 26 | endif() 27 | 28 | if (CMAKE_SIZEOF_VOID_P EQUAL 4) 29 | add_library(TestMod SHARED misc/Mod.cpp) 30 | target_link_libraries(TestMod PUBLIC TulipHook) 31 | endif() 32 | endif() 33 | 34 | include(GoogleTest) 35 | gtest_discover_tests(${PROJECT_NAME}) 36 | -------------------------------------------------------------------------------- /test/Hook.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #define FUNCTION_PARAM_TYPES int, int, int, int, int, int, int, int, int, float, float, float, float, float, float, float, float, float, float 6 | 7 | template 8 | int32_t function(FUNCTION_PARAM_TYPES) { 9 | return 1; 10 | } 11 | 12 | template 13 | int32_t hook(FUNCTION_PARAM_TYPES) { 14 | return 3; 15 | } 16 | 17 | template 18 | int32_t priorityHook(Params... params) { 19 | auto ret = function(params...); 20 | return ret + 3; 21 | } 22 | 23 | using namespace tulip::hook; 24 | 25 | using FunctionPtrType = int32_t (*)( 26 | FUNCTION_PARAM_TYPES 27 | ); 28 | 29 | template 30 | void makeWrapper(FunctionPtrType& out) { 31 | WrapperMetadata wrapperMetadata; 32 | wrapperMetadata.m_convention = std::make_unique(); 33 | wrapperMetadata.m_abstract = AbstractFunction::from>(); 34 | 35 | auto wrapped = 36 | createWrapper(reinterpret_cast(static_cast(&function)), wrapperMetadata); 37 | 38 | ASSERT_FALSE(wrapped.isErr()) << "Failed to create wrapper: " << wrapped.unwrapErr(); 39 | 40 | out = reinterpret_cast(wrapped.unwrap()); 41 | } 42 | 43 | template 44 | void makeHandler(HandlerHandle& out) { 45 | HandlerMetadata handlerMetadata; 46 | handlerMetadata.m_convention = std::make_unique(); 47 | handlerMetadata.m_abstract = AbstractFunction::from(); 50 | 51 | auto handle = 52 | createHandler(reinterpret_cast(static_cast(&function)), handlerMetadata); 53 | 54 | ASSERT_FALSE(handle.isErr()) << "Failed to create handler: " << handle.unwrapErr(); 55 | 56 | out = handle.unwrap(); 57 | } 58 | 59 | void destroyHandler(HandlerHandle const& handle) { 60 | auto rem = removeHandler(handle); 61 | if (rem.isErr()) 62 | exit(1); 63 | } 64 | 65 | template 66 | HookHandle makeHook(HandlerHandle const& handle) { 67 | HookMetadata metadata; 68 | return createHook(handle, reinterpret_cast(static_cast(&hook)), metadata); 69 | } 70 | 71 | template 72 | HookHandle makePriorityHook(HandlerHandle const& handle) { 73 | HookMetadata metadata; 74 | metadata.m_priority = -100; 75 | return createHook(handle, reinterpret_cast(static_cast(&priorityHook)), metadata); 76 | } 77 | 78 | #pragma clang diagnostic push 79 | #pragma ide diagnostic ignored "ConstantFunctionResult" 80 | template 81 | int callFunction() { 82 | return function(1, 2, 3, 4, 5, 6, 7, 8, 9, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f, 10.0f); 83 | } 84 | #pragma clang diagnostic pop 85 | 86 | TEST(HookTest, NoHandler) { 87 | EXPECT_EQ(callFunction<0>(), 1); 88 | } 89 | 90 | TEST(HookTest, NoHooks) { 91 | HandlerHandle handlerHandle; 92 | makeHandler<1>(handlerHandle); 93 | EXPECT_EQ(callFunction<1>(), 1); 94 | } 95 | 96 | TEST(HookTest, MakeWrapper) { 97 | FunctionPtrType unwrapped; 98 | makeWrapper<2>(unwrapped); 99 | EXPECT_EQ(unwrapped(1, 2, 3, 4, 5, 6, 7, 8, 9, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f, 10.0f), 1); 100 | } 101 | 102 | // hook -> function 103 | TEST(HookTest, SingleHook) { 104 | HandlerHandle handlerHandle; 105 | makeHandler<3>(handlerHandle); 106 | 107 | makeHook<3>(handlerHandle); 108 | 109 | EXPECT_EQ(callFunction<3>(), 3); 110 | } 111 | 112 | // priorityHook -> hook -> function 113 | TEST(HookTest, PriorityHook) { 114 | HandlerHandle handlerHandle; 115 | makeHandler<4>(handlerHandle); 116 | 117 | makeHook<4>(handlerHandle); 118 | makePriorityHook<4>(handlerHandle); 119 | 120 | EXPECT_EQ(callFunction<4>(), 6); 121 | } 122 | 123 | // priorityHook -> function 124 | TEST(HookTest, RemoveHook) { 125 | HandlerHandle handlerHandle; 126 | makeHandler<5>(handlerHandle); 127 | 128 | HookHandle hookHandle = makeHook<5>(handlerHandle); 129 | makePriorityHook<5>(handlerHandle); 130 | removeHook(handlerHandle, hookHandle); 131 | 132 | EXPECT_EQ(callFunction<5>(), 4); 133 | } 134 | 135 | // priorityHook -> hook -> function 136 | TEST(HookTest, ReAddHook) { 137 | HandlerHandle handlerHandle; 138 | makeHandler<6>(handlerHandle); 139 | 140 | HookHandle hookHandle = makeHook<6>(handlerHandle); 141 | makePriorityHook<6>(handlerHandle); 142 | removeHook(handlerHandle, hookHandle); 143 | makeHook<6>(handlerHandle); 144 | 145 | EXPECT_EQ(callFunction<6>(), 6); 146 | } 147 | 148 | // priorityHook -> priorityHook -> priorityHook -> function 149 | TEST(HookTest, MultiInstance) { 150 | HandlerHandle handlerHandle; 151 | makeHandler<7>(handlerHandle); 152 | 153 | makePriorityHook<7>(handlerHandle); 154 | makePriorityHook<7>(handlerHandle); 155 | makePriorityHook<7>(handlerHandle); 156 | 157 | EXPECT_EQ(callFunction<7>(), 10); 158 | } 159 | 160 | // priorityHook -> priorityHook -> priorityHook -> priorityHook -> hook -> function 161 | TEST(HookTest, MoreMultiInstance) { 162 | HandlerHandle handlerHandle; 163 | makeHandler<8>(handlerHandle); 164 | 165 | makeHook<8>(handlerHandle); 166 | makePriorityHook<8>(handlerHandle); 167 | makePriorityHook<8>(handlerHandle); 168 | makePriorityHook<8>(handlerHandle); 169 | makePriorityHook<8>(handlerHandle); 170 | 171 | EXPECT_EQ(callFunction<8>(), 15); 172 | } 173 | 174 | TEST(HookTest, RemoveHandler) { 175 | HandlerHandle handlerHandle; 176 | makeHandler<9>(handlerHandle); 177 | 178 | makeHook<9>(handlerHandle); 179 | 180 | destroyHandler(handlerHandle); 181 | 182 | EXPECT_EQ(callFunction<9>(), 1); 183 | } 184 | 185 | TEST(HookTest, RecreateHandler) { 186 | HandlerHandle handlerHandle; 187 | makeHandler<10>(handlerHandle); 188 | 189 | makeHook<10>(handlerHandle); 190 | 191 | destroyHandler(handlerHandle); 192 | 193 | makeHandler<10>(handlerHandle); 194 | 195 | EXPECT_EQ(callFunction<10>(), 1); 196 | } 197 | 198 | int checkParams(int a, int b, int c, int d, int e, int f, int g, float h, int i) { 199 | EXPECT_EQ(a, 1); 200 | EXPECT_EQ(b, 2); 201 | EXPECT_EQ(c, 3); 202 | EXPECT_EQ(d, 4); 203 | EXPECT_EQ(e, 5); 204 | EXPECT_EQ(f, 6); 205 | EXPECT_EQ(g, 7); 206 | EXPECT_EQ(h, 8.0f); 207 | EXPECT_EQ(i, 9); 208 | return 11; 209 | } 210 | 211 | int checkParamsHook(int a, int b, int c, int d, int e, int f, int g, float h, int i) { 212 | EXPECT_EQ(a, 1); 213 | EXPECT_EQ(b, 2); 214 | EXPECT_EQ(c, 3); 215 | EXPECT_EQ(d, 4); 216 | EXPECT_EQ(e, 5); 217 | EXPECT_EQ(f, 6); 218 | EXPECT_EQ(g, 7); 219 | EXPECT_EQ(h, 8.0f); 220 | EXPECT_EQ(i, 9); 221 | return checkParams(a, b, c, d, e, f, g, h, i); 222 | } 223 | 224 | TEST(HookTest, SingleHookCheckParams) { 225 | HandlerMetadata handlerMetadata; 226 | handlerMetadata.m_convention = std::make_unique(); 227 | handlerMetadata.m_abstract = AbstractFunction::from(&checkParams); 228 | 229 | auto handleRes = createHandler(reinterpret_cast(&checkParams), handlerMetadata); 230 | 231 | ASSERT_FALSE(handleRes.isErr()) << "Failed to create handler: " << handleRes.unwrapErr(); 232 | 233 | auto handle = handleRes.unwrap(); 234 | 235 | HookMetadata metadata; 236 | createHook(handle, reinterpret_cast(&checkParamsHook), metadata); 237 | 238 | checkParams(1, 2, 3, 4, 5, 6, 7, 8.0f, 9); 239 | } -------------------------------------------------------------------------------- /test/misc/WinTest.cpp: -------------------------------------------------------------------------------- 1 | #include "tulip/TulipHook.hpp" 2 | 3 | #include 4 | 5 | #define NO_INLINE __declspec(noinline) 6 | 7 | struct Dummy { 8 | int x, y, z, d; 9 | ~Dummy() {} 10 | }; 11 | 12 | struct FooBar { 13 | int number; 14 | 15 | NO_INLINE Dummy targetFunc(int stack, int stack2) { 16 | std::cout << "I am FooBar::targetFunc\n"; 17 | std::cout << " this: " << this << "\n"; 18 | std::cout << " this->number: " << this->number << "\n"; 19 | std::cout << " My stack is " << stack << " and " << stack2 << "\n"; 20 | return {.x = 3}; 21 | } 22 | }; 23 | 24 | NO_INLINE Dummy targetFuncHook(FooBar* self, int stack, int stack2) { 25 | std::cout << "I am targetFuncHook" << std::endl; 26 | std::cout << " this: " << self << std::endl; 27 | std::cout << " this->number: " << self->number << "\n"; 28 | std::cout << " My stack " << stack << " and " << stack2 << std::endl; 29 | return self->targetFunc(stack, stack2); 30 | } 31 | 32 | int main() { 33 | auto poo = &FooBar::targetFunc; 34 | void* address = reinterpret_cast(poo); 35 | 36 | std::cout << "address of FooBar::targetFunc is " << address << std::endl; 37 | 38 | auto metadata = tulip::hook::HandlerMetadata { 39 | .m_convention = std::make_shared(), 40 | .m_abstract = tulip::hook::AbstractFunction::from(&targetFuncHook) 41 | }; 42 | 43 | auto handleResult = tulip::hook::createHandler(address, metadata); 44 | if (!handleResult) { 45 | std::cout << "creating the handler failed" << std::endl; 46 | return 1; 47 | } 48 | auto handle = handleResult.unwrap(); 49 | 50 | auto h_metadata = tulip::hook::HookMetadata { 51 | .m_priority = 2 52 | }; 53 | 54 | tulip::hook::createHook(handle, (void*)&targetFuncHook, h_metadata); 55 | 56 | std::cout << "hook created!" << std::endl; 57 | 58 | auto bar = new FooBar; 59 | std::cout << "bar is " << bar << std::endl; 60 | bar->number = 23; 61 | auto value = bar->targetFunc(5, 7); 62 | 63 | std::cout << "targetFunc returned " << value.x << std::endl; 64 | 65 | return 0; 66 | } 67 | --------------------------------------------------------------------------------