├── DelimStringReader.h ├── HookEach.hpp ├── HookInit.hpp ├── LICENSE ├── LateStaticInit.h ├── MemoryMgr.GTA.h ├── MemoryMgr.h ├── ModuleList.hpp ├── Patterns.cpp ├── Patterns.h ├── README.md ├── ScopedUnprotect.hpp └── Trampoline.h /DelimStringReader.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | template 4 | class BasicDelimStringReader 5 | { 6 | public: 7 | BasicDelimStringReader( size_t size ) 8 | : m_buffer( new T[size] ), m_size( size ) 9 | { 10 | Reset(); 11 | } 12 | 13 | ~BasicDelimStringReader() 14 | { 15 | delete[] m_buffer; 16 | } 17 | 18 | inline T* GetBuffer() const 19 | { 20 | return m_buffer; 21 | } 22 | 23 | inline size_t GetSize() const 24 | { 25 | return m_size; 26 | } 27 | 28 | const T* GetString( size_t* size = nullptr ) 29 | { 30 | if ( *m_cursor == '\0' ) 31 | { 32 | if ( size != nullptr ) *size = 0; 33 | return nullptr; 34 | } 35 | const T* curString = m_cursor; 36 | size_t len = 0; 37 | 38 | while ( *m_cursor++ != '\0' ) len++; 39 | 40 | if ( size != nullptr ) *size = len; 41 | return curString; 42 | } 43 | 44 | inline void Reset() 45 | { 46 | m_cursor = m_buffer; 47 | } 48 | 49 | private: 50 | T* const m_buffer; 51 | const T* m_cursor; 52 | const size_t m_size; 53 | }; 54 | 55 | typedef BasicDelimStringReader DelimStringReader; 56 | typedef BasicDelimStringReader WideDelimStringReader; -------------------------------------------------------------------------------- /HookEach.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // Helper macro to instantiate a set of templated variables/functions and a function hooking multiple places in code en masse. 4 | // This helper is meant to help improve compatibility with different modifications and/or application versions by easily lifting 5 | // assumptions about the similarity of patched call sites and/or variable instances. 6 | 7 | // HookEach works by automatically generating duplicate functions and/or variables, with instances templated by an index. 8 | // The required amount of indices is determined automatically from the size of the std::array passed to a genetated HookEach_* function 9 | // when applying the code patches. 10 | 11 | // Cases where the code would typically benefit from HookEach: 12 | // 1. Intercepting multiple call sites to the same function. With HookEach, each call site automagically gets its own "original function pointer" variable. 13 | // 2. Intercepting multiple uses of a global variable or a constant. With HookEach, the code doesn't need to worry if some/all of those have been modified by another modification. 14 | 15 | // Setting up HookEach for intercepting multiple call sites (the most common case): 16 | // 1. Define a templated original function pointer variable. Template must be of type 'template'. 17 | // 2. Define a templated intercepted function. Template must be of type 'template'. 18 | // 3. Fill the body of an intercepted function, somewhere in that function invoke orgFuncPtr, where 'orgFuncPtr' is the variable defined in 1. 19 | // This will invoke a different orgFuncPtr for every instance of an intercepted function. 20 | // If possible, keep the index-agnostic part of the hook in a separate function, so the templated function stays as small as possible. 21 | // 4. Initialize HookEach by adding HOOK_EACH_INIT(name, orgFuncPtr, patchedFunc), where 'name' is a custom name given to this pair of templated entities. 22 | // This name will act as a suffix of a generated HookEach_ function. 23 | // 5. When applying code patches, create a std::array<> of call sites to patch, where each entry is a memory address to intercept. 24 | // 6. Call HookEach_Name(array, InterceptCall) to apply code patches. 25 | 26 | // For intercepting variables, similar steps can be taken, defining a replacement variable instead of a function in 2. 27 | // A callable other than InterceptCall must also be passed to HookEach_*. This callable will be called for every tuple of (data, original, replaced) 28 | // parameters, where 'data' are the std::array entries, and 'original' and 'replaced' are the variables defined ealier in HOOK_EACH_INIT. 29 | // Additionally, if 'data' is a tuple or a pair, it will be expanded into individual variables and then passed to the callable. 30 | 31 | // For more complex cases where identical patches must be applied on multiple arrays of addresses, two approaches can be used: 32 | // 1. Use HOOK_EACH_INIT(name, counter, original, replaced) to define an unique alias with a non-zero counter. 33 | // 2. Specify a non-zero count as a template parameter of HookEach_*. 34 | // In both cases, each unique call then gets its own set of 'original' and 'replaced' instances. 35 | 36 | #include 37 | #include 38 | #include 39 | #include 40 | 41 | namespace hook_each::details 42 | { 43 | template 44 | struct is_tuple_like : std::false_type {}; 45 | 46 | template 47 | struct is_tuple_like> : std::true_type {}; 48 | 49 | template 50 | struct is_tuple_like> : std::true_type {}; 51 | }; 52 | 53 | #define HOOK_EACH_INIT_CTR(name, ctr, original, replaced) \ 54 | template \ 55 | static void hook_each_impl_##name(const std::array& elems, Func&& f, std::index_sequence) \ 56 | { \ 57 | if constexpr (hook_each::details::is_tuple_like::value) \ 58 | { \ 59 | (std::apply([&f, &originalInner = original<(Ctr << 16) | I>, &replacedInner = replaced<(Ctr << 16) | I>](auto&&... params) { \ 60 | f(std::forward(params)..., originalInner, replacedInner); \ 61 | }, elems[I]), ...); \ 62 | } \ 63 | else \ 64 | { \ 65 | (f(elems[I], original<(Ctr << 16) | I>, replaced<(Ctr << 16) | I>), ...); \ 66 | } \ 67 | } \ 68 | \ 69 | template \ 70 | static void HookEach_##name(const std::array& elems, Func&& f) \ 71 | { \ 72 | hook_each_impl_##name(elems, std::forward(f), std::make_index_sequence{}); \ 73 | } 74 | 75 | #define HOOK_EACH_INIT(name, original, replaced) HOOK_EACH_INIT_CTR(name, 0, original, replaced) 76 | -------------------------------------------------------------------------------- /HookInit.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // This header file is intended to streamline the process of hooking inside executables by hooking into WinAPI functions 4 | // It is intended to be included in a separate compilation unit after defining the following: 5 | 6 | // #define HOOKED_FUNCTION WinAPIFunction - must be a define or else the plugin will patch into itself 7 | // #define HOOKED_LIBRARY "LIBRARY.DLL" 8 | // windows.h also must be included already (you probably had to include it to define the above) 9 | 10 | // When integrated properly, the following function shall be exposed: 11 | // void OnInitializeHook() - called once from the hooked WinAPI function 12 | 13 | // The following exports are added to the binary: 14 | // void InitializeASI() 15 | // uint32_t GetBuildNumber() - returns revision/build number as defined in VersionInfo.lua (if defined) 16 | 17 | // Hooks will be initialized by first attempting to patch IAT of the main module 18 | // If this fails, selected WinAPI export will be hooked directly 19 | 20 | #include "MemoryMgr.h" 21 | #include "Trampoline.h" 22 | 23 | #include 24 | 25 | #define STRINGIZE(s) STRINGIZE2(s) 26 | #define STRINGIZE2(s) #s 27 | 28 | extern void OnInitializeHook(); 29 | 30 | namespace HookInit 31 | { 32 | static std::once_flag hookFlag; 33 | static void ProcHook() 34 | { 35 | std::call_once(hookFlag, OnInitializeHook); 36 | } 37 | 38 | // Helper to extract parameters from the function 39 | template 40 | struct wrap_winapi_function_helper; 41 | 42 | template 43 | struct wrap_winapi_function_helper 44 | { 45 | static inline Result (WINAPI *origFunction)(Args...); 46 | static Result WINAPI Hook(Args... args) 47 | { 48 | ProcHook(); 49 | return origFunction(std::forward(args)...); 50 | } 51 | 52 | static inline uint8_t origCode[5]; 53 | static Result WINAPI OverwritingHook(Args... args) 54 | { 55 | Memory::VP::Patch(origFunction, { origCode[0], origCode[1], origCode[2], origCode[3], origCode[4] }); 56 | return Hook(std::forward(args)...); 57 | } 58 | }; 59 | 60 | using wrapped_function = wrap_winapi_function_helper; 61 | 62 | static void ReplaceFunction(void** funcPtr) 63 | { 64 | DWORD dwProtect; 65 | 66 | VirtualProtect(funcPtr, sizeof(*funcPtr), PAGE_READWRITE, &dwProtect); 67 | wrapped_function::origFunction = **reinterpret_cast(funcPtr); 68 | 69 | *funcPtr = &wrapped_function::Hook; 70 | VirtualProtect(funcPtr, sizeof(*funcPtr), dwProtect, &dwProtect); 71 | } 72 | 73 | static bool PatchIAT() 74 | { 75 | DWORD_PTR instance; 76 | #ifdef HOOKED_MODULE 77 | instance = reinterpret_cast(GetModuleHandle(TEXT(HOOKED_MODULE))); 78 | if (instance == 0) 79 | #endif 80 | { 81 | instance = reinterpret_cast(GetModuleHandle(nullptr)); 82 | } 83 | const PIMAGE_NT_HEADERS ntHeader = reinterpret_cast(instance + reinterpret_cast(instance)->e_lfanew); 84 | 85 | // Find IAT 86 | PIMAGE_IMPORT_DESCRIPTOR pImports = reinterpret_cast(instance + ntHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress); 87 | 88 | for ( ; pImports->Name != 0; pImports++ ) 89 | { 90 | if ( _stricmp(reinterpret_cast(instance + pImports->Name), HOOKED_LIBRARY) == 0 ) 91 | { 92 | if ( pImports->OriginalFirstThunk != 0 ) 93 | { 94 | const PIMAGE_THUNK_DATA pThunk = reinterpret_cast(instance + pImports->OriginalFirstThunk); 95 | 96 | for ( ptrdiff_t j = 0; pThunk[j].u1.AddressOfData != 0; j++ ) 97 | { 98 | if ( strcmp(reinterpret_cast(instance + pThunk[j].u1.AddressOfData)->Name, STRINGIZE(HOOKED_FUNCTION)) == 0 ) 99 | { 100 | void** pAddress = reinterpret_cast(instance + pImports->FirstThunk) + j; 101 | ReplaceFunction(pAddress); 102 | return true; 103 | } 104 | } 105 | } 106 | else 107 | { 108 | // This will only work if nobody else beats us to it - which is fine, because a fallback exists 109 | void** pFunctions = reinterpret_cast(instance + pImports->FirstThunk); 110 | 111 | for ( ptrdiff_t j = 0; pFunctions[j] != nullptr; j++ ) 112 | { 113 | if ( pFunctions[j] == HOOKED_FUNCTION ) 114 | { 115 | ReplaceFunction(&pFunctions[j]); 116 | return true; 117 | } 118 | } 119 | } 120 | } 121 | } 122 | return false; 123 | } 124 | 125 | static bool PatchIAT_ByPointers() 126 | { 127 | using namespace Memory::VP; 128 | 129 | wrapped_function::origFunction = HOOKED_FUNCTION; 130 | memcpy(wrapped_function::origCode, reinterpret_cast(wrapped_function::origFunction), sizeof(wrapped_function::origCode)); 131 | 132 | #ifdef _WIN64 133 | Trampoline* trampoline = Trampoline::MakeTrampoline(wrapped_function::origFunction); 134 | InjectHook(wrapped_function::origFunction, trampoline->Jump(&wrapped_function::OverwritingHook), HookType::Jump); 135 | #else 136 | InjectHook(wrapped_function::origFunction, wrapped_function::OverwritingHook, HookType::Jump); 137 | #endif 138 | return true; 139 | } 140 | 141 | static void InstallHooks() 142 | { 143 | bool getStartupInfoHooked = PatchIAT(); 144 | if ( !getStartupInfoHooked ) 145 | { 146 | PatchIAT_ByPointers(); 147 | } 148 | } 149 | 150 | 151 | // Optional initialization method, only valid if SKIP_INITIALIZEASI is defined! 152 | #if defined(SKIP_INITIALIZEASI) 153 | void DLLMain(HINSTANCE, DWORD reason, void*) 154 | { 155 | if (reason == DLL_PROCESS_ATTACH) 156 | { 157 | InstallHooks(); 158 | } 159 | } 160 | #endif 161 | 162 | } 163 | 164 | extern "C" 165 | { 166 | #if !defined(SKIP_INITIALIZEASI) 167 | static LONG InitCount = 0; 168 | __declspec(dllexport) void __cdecl InitializeASI() 169 | { 170 | if ( _InterlockedCompareExchange(&InitCount, 1, 0) != 0 ) return; 171 | HookInit::InstallHooks(); 172 | } 173 | #endif 174 | 175 | #if !defined(SKIP_BUILDNUMBER) && defined(rsc_RevisionID) && defined(rsc_BuildID) 176 | __declspec(dllexport) uint32_t __cdecl GetBuildNumber() 177 | { 178 | return (rsc_RevisionID << 8) | rsc_BuildID; 179 | } 180 | #endif 181 | } 182 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019-2023 Adrian Zdanowicz (Silent) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LateStaticInit.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define WIN32_LEAN_AND_MEAN 4 | #include 5 | 6 | #include 7 | 8 | 9 | class LateStaticInit 10 | { 11 | public: 12 | explicit LateStaticInit( std::function func ) 13 | : m_initFunc( std::move(func) ) 14 | { 15 | m_next = ms_head; 16 | ms_head = this; 17 | } 18 | 19 | static bool TryApplyWithPredicate(std::function pred) 20 | { 21 | if (pred()) 22 | { 23 | // Predicate succeeded - immediately apply everything and return 24 | Apply(); 25 | return true; 26 | } 27 | 28 | ms_predicate = std::move(pred); 29 | 30 | // Predicate failed - create a thread and keep trying the predicate until it succeeds 31 | HANDLE initThread = CreateThread( nullptr, 0, ThreadProc, nullptr, 0, nullptr ); 32 | SetThreadPriority( initThread, THREAD_PRIORITY_ABOVE_NORMAL ); 33 | CloseHandle( initThread ); 34 | 35 | return false; 36 | } 37 | 38 | private: 39 | static void Apply() 40 | { 41 | LateStaticInit* head = nullptr; 42 | std::swap( head, ms_head ); 43 | 44 | while ( head != nullptr ) 45 | { 46 | head->m_initFunc(); 47 | head = head->m_next; 48 | } 49 | } 50 | 51 | static DWORD WINAPI ThreadProc( LPVOID lpParameter ) 52 | { 53 | while ( true ) 54 | { 55 | const bool predicateResult = ms_predicate(); 56 | Sleep(1); // Deliberarely sleeping AFTER checking the predicate! 57 | if ( predicateResult ) 58 | { 59 | Apply(); 60 | break; 61 | } 62 | } 63 | 64 | return 0; 65 | } 66 | 67 | std::function m_initFunc; 68 | LateStaticInit* m_next; 69 | 70 | static inline LateStaticInit* ms_head = nullptr; 71 | static inline std::function ms_predicate; 72 | }; 73 | 74 | #define LATE_STATIC_INIT_INTERNAL( func, suffix ) static LateStaticInit __LATESTATICINIT__ ## suffix( [&]() { func } ) 75 | 76 | #define LATE_STATIC_INIT( prefix, func ) LATE_STATIC_INIT_INTERNAL( func, prefix ) 77 | -------------------------------------------------------------------------------- /MemoryMgr.GTA.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "MemoryMgr.h" 4 | 5 | #include 6 | #include 7 | #include "Patterns.h" 8 | 9 | namespace Memory 10 | { 11 | struct PatternAndOffset 12 | { 13 | constexpr PatternAndOffset( std::string_view pattern, ptrdiff_t offset = 0 ) noexcept 14 | : pattern(std::move(pattern)), offset(offset) 15 | { 16 | } 17 | 18 | constexpr bool Valid() const noexcept { return !pattern.empty(); } 19 | 20 | std::string_view pattern; 21 | ptrdiff_t offset; 22 | }; 23 | 24 | using AddrVariant = std::variant; 25 | 26 | namespace internal 27 | { 28 | inline int8_t* GetVer() 29 | { 30 | static int8_t bVer = -1; 31 | return &bVer; 32 | } 33 | 34 | inline bool* GetEuropean() 35 | { 36 | static bool bEuropean; 37 | return &bEuropean; 38 | } 39 | 40 | inline uintptr_t GetDummy() 41 | { 42 | static uintptr_t dwDummy; 43 | return reinterpret_cast(&dwDummy); 44 | } 45 | } 46 | } 47 | 48 | namespace Memory 49 | { 50 | namespace internal 51 | { 52 | inline uintptr_t HandlePattern( const PatternAndOffset& pattern ) 53 | { 54 | void* addr = hook::get_pattern( pattern.pattern, pattern.offset ); 55 | return reinterpret_cast(addr); 56 | } 57 | 58 | #if defined _GTA_III 59 | inline void InitializeVersions() 60 | { 61 | signed char* bVer = GetVer(); 62 | 63 | if ( *bVer == -1 ) 64 | { 65 | if (*(uint32_t*)DynBaseAddress(0x5C1E75) == 0xB85548EC) *bVer = 0; 66 | else if (*(uint32_t*)DynBaseAddress(0x5C2135) == 0xB85548EC) *bVer = 1; 67 | else if (*(uint32_t*)DynBaseAddress(0x5C6FD5) == 0xB85548EC) *bVer = 2; 68 | } 69 | } 70 | 71 | #elif defined _GTA_VC 72 | 73 | inline void InitializeVersions() 74 | { 75 | signed char* bVer = GetVer(); 76 | 77 | if ( *bVer == -1 ) 78 | { 79 | if (*(uint32_t*)DynBaseAddress(0x667BF5) == 0xB85548EC) *bVer = 0; 80 | else if (*(uint32_t*)DynBaseAddress(0x667C45) == 0xB85548EC) *bVer = 1; 81 | else if (*(uint32_t*)DynBaseAddress(0x666BA5) == 0xB85548EC) *bVer = 2; 82 | } 83 | } 84 | 85 | #elif defined _GTA_SA 86 | 87 | inline bool TryMatch_10() 88 | { 89 | if ( *(uint32_t*)DynBaseAddress(0x82457C) == 0x94BF ) 90 | { 91 | // 1.0 US 92 | *GetVer() = 0; 93 | *GetEuropean() = false; 94 | return true; 95 | } 96 | if ( *(uint32_t*)DynBaseAddress(0x8245BC) == 0x94BF ) 97 | { 98 | // 1.0 EU 99 | *GetVer() = 0; 100 | *GetEuropean() = true; 101 | return true; 102 | } 103 | return false; 104 | } 105 | 106 | inline bool TryMatch_11() 107 | { 108 | if ( *(uint32_t*)DynBaseAddress(0x8252FC) == 0x94BF ) 109 | { 110 | // 1.01 US 111 | *GetVer() = 1; 112 | *GetEuropean() = false; 113 | return true; 114 | } 115 | if ( *(uint32_t*)DynBaseAddress(0x82533C) == 0x94BF ) 116 | { 117 | // 1.01 EU 118 | *GetVer() = 1; 119 | *GetEuropean() = true; 120 | return true; 121 | } 122 | return false; 123 | } 124 | 125 | inline bool TryMatch_30() 126 | { 127 | if (*(uint32_t*)DynBaseAddress(0x85EC4A) == 0x94BF ) 128 | { 129 | // 3.0 130 | *GetVer() = 2; 131 | *GetEuropean() = false; 132 | return true; 133 | } 134 | return false; 135 | } 136 | 137 | inline bool TryMatch_newsteam_r1() 138 | { 139 | if ( *(uint32_t*)DynBaseAddress(0x858D21) == 0x3539F633 ) 140 | { 141 | // newsteam r1 142 | *GetVer() = 3; 143 | *GetEuropean() = false; 144 | return true; 145 | } 146 | return false; 147 | } 148 | 149 | inline bool TryMatch_newsteam_r2() 150 | { 151 | if ( *(uint32_t*)DynBaseAddress(0x858D51) == 0x3539F633 ) 152 | { 153 | // newsteam r2 154 | *GetVer() = 4; 155 | *GetEuropean() = false; 156 | return true; 157 | } 158 | return false; 159 | } 160 | 161 | inline bool TryMatch_newsteam_r2_lv() 162 | { 163 | if ( *(uint32_t*)DynBaseAddress(0x858C61) == 0x3539F633 ) 164 | { 165 | // newsteam r2 lv 166 | *GetVer() = 5; 167 | *GetEuropean() = false; 168 | return true; 169 | } 170 | return false; 171 | } 172 | 173 | inline bool TryMatch_RGL() 174 | { 175 | if ( *(uint32_t*)DynBaseAddress(0x858501) == 0x3539F633 ) 176 | { 177 | // RGL (1.0.22.0) 178 | *GetVer() = 6; 179 | *GetEuropean() = false; 180 | return true; 181 | } 182 | return false; 183 | } 184 | 185 | inline void InitializeVersions() 186 | { 187 | if ( *GetVer() == -1 ) 188 | { 189 | if ( TryMatch_10() ) return; 190 | if ( TryMatch_11() ) return; 191 | if ( TryMatch_30() ) return; 192 | if ( TryMatch_newsteam_r1() ) return; 193 | if ( TryMatch_newsteam_r2() ) return; 194 | if ( TryMatch_newsteam_r2_lv() ) return; 195 | if ( TryMatch_RGL() ) return; 196 | 197 | // If not matched, from now on we assume this is a "future" EXE 198 | // and try to use newsteam/RGL patterns anyway 199 | *GetVer() = INT8_MAX; 200 | } 201 | } 202 | 203 | inline void InitializeRegion_10() 204 | { 205 | signed char* bVer = GetVer(); 206 | 207 | if ( *bVer == -1 ) 208 | { 209 | if ( !TryMatch_10() ) 210 | { 211 | #ifdef assert 212 | assert(!"AddressByRegion_10 on non-1.0 EXE!"); 213 | #endif 214 | } 215 | } 216 | } 217 | 218 | inline void InitializeRegion_11() 219 | { 220 | signed char* bVer = GetVer(); 221 | 222 | if ( *bVer == -1 ) 223 | { 224 | if ( !TryMatch_11() ) 225 | { 226 | #ifdef assert 227 | assert(!"AddressByRegion_11 on non-1.01 EXE!"); 228 | #endif 229 | } 230 | } 231 | } 232 | 233 | inline uintptr_t AdjustAddress_10(uintptr_t address10) 234 | { 235 | if ( *GetEuropean() ) 236 | { 237 | if ( address10 >= 0x746720 && address10 < 0x857000 ) 238 | { 239 | if ( address10 >= 0x7BA940 ) 240 | address10 += 0x40; 241 | else 242 | address10 += 0x50; 243 | } 244 | } 245 | return address10; 246 | } 247 | 248 | inline uintptr_t AdjustAddress_11(uintptr_t address11) 249 | { 250 | if ( !(*GetEuropean()) && address11 > 0x746FA0 ) 251 | { 252 | if ( address11 < 0x7BB240 ) 253 | address11 -= 0x50; 254 | else 255 | address11 -= 0x40; 256 | } 257 | return address11; 258 | } 259 | 260 | inline uintptr_t AddressByVersion(AddrVariant address10, AddrVariant address11, AddrVariant addressSteam, PatternAndOffset patternNewExes) 261 | { 262 | InitializeVersions(); 263 | 264 | signed char bVer = *GetVer(); 265 | 266 | switch ( bVer ) 267 | { 268 | case 0: 269 | if ( auto pao = std::get_if(&address10) ) return HandlePattern( *pao ); 270 | else 271 | { 272 | const uintptr_t addr = std::make_unsigned_t(*std::get_if(&address10)); 273 | #ifdef assert 274 | assert(addr); 275 | #endif 276 | // Adjust to EU if needed 277 | return AdjustAddress_10(addr); 278 | } 279 | break; 280 | case 1: 281 | if ( auto pao = std::get_if(&address11) ) return HandlePattern( *pao ); 282 | else 283 | { 284 | const uintptr_t addr = std::make_unsigned_t(*std::get_if(&address11)); 285 | #ifdef assert 286 | assert(addr); 287 | #endif 288 | 289 | // Safety measures - if null or ignored, return dummy var pointer to prevent a crash 290 | if ( addr == 0 || addr == UINTPTR_MAX ) 291 | return GetDummy(); 292 | 293 | // Adjust to US if needed 294 | return AdjustAddress_11(addr); 295 | } 296 | case 2: 297 | if ( auto pao = std::get_if(&addressSteam) ) return HandlePattern( *pao ); 298 | else 299 | { 300 | const uintptr_t addr = std::make_unsigned_t(*std::get_if(&addressSteam)); 301 | #ifdef assert 302 | assert(addr); 303 | #endif 304 | // Safety measures - if null or ignored, return dummy var pointer to prevent a crash 305 | if ( addr == 0 || addr == UINTPTR_MAX ) 306 | return GetDummy(); 307 | 308 | return addr; 309 | } 310 | default: 311 | if ( !patternNewExes.Valid() ) 312 | return GetDummy(); 313 | 314 | return HandlePattern( patternNewExes ); 315 | } 316 | } 317 | 318 | inline uintptr_t AddressByRegion_10(uintptr_t address10) 319 | { 320 | InitializeRegion_10(); 321 | 322 | // Adjust to EU if needed 323 | return AdjustAddress_10(address10); 324 | } 325 | 326 | inline uintptr_t AddressByRegion_11(uintptr_t address11) 327 | { 328 | InitializeRegion_11(); 329 | 330 | // Adjust to US if needed 331 | return AdjustAddress_11(address11); 332 | } 333 | 334 | #else 335 | 336 | inline void InitializeVersions() 337 | { 338 | } 339 | 340 | #endif 341 | 342 | #if defined _GTA_III || defined _GTA_VC 343 | 344 | inline uintptr_t AddressByVersion(uintptr_t address10, uintptr_t address11, uintptr_t addressSteam) 345 | { 346 | InitializeVersions(); 347 | 348 | signed char bVer = *GetVer(); 349 | 350 | switch ( bVer ) 351 | { 352 | case 1: 353 | #ifdef assert 354 | assert(address11); 355 | #endif 356 | return DynBaseAddress(address11); 357 | case 2: 358 | #ifdef assert 359 | assert(addressSteam); 360 | #endif 361 | return DynBaseAddress(addressSteam); 362 | default: 363 | #ifdef assert 364 | assert(address10); 365 | #endif 366 | return DynBaseAddress(address10); 367 | } 368 | } 369 | 370 | #endif 371 | 372 | } 373 | } 374 | 375 | #if defined _GTA_III || defined _GTA_VC 376 | 377 | template 378 | inline T AddressByVersion(uintptr_t address10, uintptr_t address11, uintptr_t addressSteam) 379 | { 380 | return T(Memory::internal::AddressByVersion( address10, address11, addressSteam )); 381 | } 382 | 383 | #elif defined _GTA_SA 384 | 385 | template 386 | inline T AddressByVersion(Memory::AddrVariant address10, Memory::AddrVariant address11, Memory::AddrVariant addressSteam) 387 | { 388 | return T(Memory::internal::AddressByVersion( std::move(address10), std::move(address11), std::move(addressSteam), Memory::PatternAndOffset(std::string_view()) )); 389 | } 390 | 391 | template 392 | inline T AddressByVersion(Memory::AddrVariant address10, Memory::AddrVariant address11, Memory::AddrVariant addressSteam, Memory::PatternAndOffset patternNewExes) 393 | { 394 | return T(Memory::internal::AddressByVersion( std::move(address10), std::move(address11), std::move(addressSteam), std::move(patternNewExes) )); 395 | } 396 | 397 | template 398 | inline T AddressByVersion(Memory::AddrVariant address10, Memory::PatternAndOffset patternNewExes) 399 | { 400 | return T(Memory::internal::AddressByVersion( std::move(address10), 0, 0, std::move(patternNewExes) )); 401 | } 402 | 403 | template 404 | inline T AddressByRegion_10(uintptr_t address10) 405 | { 406 | return T(Memory::internal::AddressByRegion_10(address10)); 407 | } 408 | 409 | template 410 | inline T AddressByRegion_11(uintptr_t address11) 411 | { 412 | return T(Memory::internal::AddressByRegion_11(address11)); 413 | } 414 | 415 | #endif 416 | 417 | namespace Memory 418 | { 419 | struct VersionInfo 420 | { 421 | int8_t version; 422 | bool european; 423 | }; 424 | 425 | inline VersionInfo GetVersion() 426 | { 427 | Memory::internal::InitializeVersions(); 428 | return { *Memory::internal::GetVer(), *Memory::internal::GetEuropean() }; 429 | } 430 | }; 431 | -------------------------------------------------------------------------------- /MemoryMgr.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // Switches: 4 | // _MEMORY_NO_CRT - don't include anything "complex" like ScopedUnprotect or memset 5 | // _MEMORY_DECLS_ONLY - don't include anything but macroes 6 | 7 | #define WRAPPER __declspec(naked) 8 | #define DEPRECATED __declspec(deprecated) 9 | 10 | #ifdef _MSC_VER 11 | #define EAXJMP(a) { _asm mov eax, a _asm jmp eax } 12 | #define VARJMP(a) { _asm jmp a } 13 | #define WRAPARG(a) ((int)a) 14 | #else 15 | #define EAXJMP(a) __asm__ volatile("mov eax, %0\n" "jmp eax" :: "i" (a)); 16 | #define VARJMP(a) __asm__ volatile("jmp %0" :: "m" (a)); 17 | #define WRAPARG(a) 18 | #endif 19 | 20 | #ifdef _MSC_VER 21 | #define NOVMT __declspec(novtable) 22 | #else 23 | #define NOVMT 24 | #endif 25 | 26 | #define SETVMT(a) *((uintptr_t*)this) = (uintptr_t)a 27 | 28 | #ifndef _MEMORY_DECLS_ONLY 29 | 30 | #define WIN32_LEAN_AND_MEAN 31 | #include 32 | 33 | #include 34 | #include 35 | 36 | #ifndef _MEMORY_NO_CRT 37 | #include 38 | #include 39 | #include 40 | #endif 41 | 42 | namespace Memory 43 | { 44 | enum class HookType 45 | { 46 | Call, 47 | Jump, 48 | }; 49 | 50 | template 51 | inline AT DynBaseAddress(AT address) 52 | { 53 | static_assert(sizeof(AT) == sizeof(uintptr_t), "AT must be pointer sized"); 54 | #ifdef _WIN64 55 | return (ptrdiff_t)GetModuleHandle(nullptr) - 0x140000000 + address; 56 | #else 57 | return (ptrdiff_t)GetModuleHandle(nullptr) - 0x400000 + address; 58 | #endif 59 | } 60 | 61 | template 62 | inline void Patch(AT address, T value) 63 | { 64 | static_assert(sizeof(AT) == sizeof(uintptr_t), "AT must be pointer sized"); 65 | *(T*)address = value; 66 | } 67 | 68 | #ifndef _MEMORY_NO_CRT 69 | template 70 | inline void Patch(AT address, std::initializer_list list ) 71 | { 72 | static_assert(sizeof(AT) == sizeof(uintptr_t), "AT must be pointer sized"); 73 | uint8_t* addr = reinterpret_cast(address); 74 | std::copy( list.begin(), list.end(), addr ); 75 | } 76 | #endif 77 | 78 | template 79 | inline void Nop(AT address, size_t count) 80 | #ifndef _MEMORY_NO_CRT 81 | { 82 | static_assert(sizeof(AT) == sizeof(uintptr_t), "AT must be pointer sized"); 83 | memset((void*)address, 0x90, count); 84 | } 85 | #else 86 | { do { 87 | *(uint8_t*)address++ = 0x90; 88 | } while ( --count != 0 ); } 89 | #endif 90 | 91 | template 92 | inline void WriteOffsetValue(AT address, Var var) 93 | { 94 | static_assert(sizeof(AT) == sizeof(uintptr_t), "AT must be pointer sized"); 95 | intptr_t dstAddr = (intptr_t)address; 96 | intptr_t srcAddr; 97 | memcpy( &srcAddr, std::addressof(var), sizeof(srcAddr) ); 98 | *(int32_t*)dstAddr = static_cast(srcAddr - dstAddr - (4 + extraBytesAfterOffset)); 99 | } 100 | 101 | template 102 | inline void ReadOffsetValue(AT address, Var& var) 103 | { 104 | static_assert(sizeof(AT) == sizeof(uintptr_t), "AT must be pointer sized"); 105 | intptr_t srcAddr = (intptr_t)address; 106 | intptr_t dstAddr = srcAddr + (4 + extraBytesAfterOffset) + *(int32_t*)srcAddr; 107 | var = {}; 108 | memcpy( std::addressof(var), &dstAddr, sizeof(dstAddr) ); 109 | } 110 | 111 | template 112 | inline void InjectHook(AT address, Func hook) 113 | { 114 | WriteOffsetValue( (intptr_t)address + 1, hook ); 115 | } 116 | 117 | template 118 | inline void InjectHook(AT address, Func hook, HookType type) 119 | { 120 | *(uint8_t*)address = type == HookType::Jump ? 0xE9 : 0xE8; 121 | InjectHook(address, hook); 122 | } 123 | 124 | template 125 | inline void ReadCall(AT address, Func& func) 126 | { 127 | ReadOffsetValue( (intptr_t)address+1, func ); 128 | } 129 | 130 | template 131 | inline void* ReadCallFrom(AT address, ptrdiff_t offset = 0) 132 | { 133 | uintptr_t addr; 134 | ReadCall( address, addr ); 135 | return reinterpret_cast( addr + offset ); 136 | } 137 | 138 | inline auto InterceptCall = [](auto address, auto&& func, auto&& hook) 139 | { 140 | ReadCall(address, func); 141 | InjectHook(address, hook); 142 | }; 143 | 144 | #ifndef _MEMORY_NO_CRT 145 | inline bool MemEquals(uintptr_t address, std::initializer_list val) 146 | { 147 | const uint8_t* mem = reinterpret_cast(address); 148 | return std::equal( val.begin(), val.end(), mem ); 149 | } 150 | #endif 151 | 152 | template 153 | inline AT Verify(AT address, uintptr_t expected) 154 | { 155 | static_assert(sizeof(AT) == sizeof(uintptr_t), "AT must be pointer sized"); 156 | assert( uintptr_t(address) == expected ); 157 | return address; 158 | } 159 | 160 | namespace DynBase 161 | { 162 | enum class HookType 163 | { 164 | Call, 165 | Jump, 166 | }; 167 | 168 | using Memory::DynBaseAddress; 169 | 170 | template 171 | inline void Patch(AT address, T value) 172 | { 173 | Memory::Patch(DynBaseAddress(address), value); 174 | } 175 | 176 | #ifndef _MEMORY_NO_CRT 177 | template 178 | inline void Patch(AT address, std::initializer_list list ) 179 | { 180 | Memory::Patch(DynBaseAddress(address), std::move(list)); 181 | } 182 | #endif 183 | 184 | template 185 | inline void Nop(AT address, size_t count) 186 | { 187 | Memory::Nop(DynBaseAddress(address), count); 188 | } 189 | 190 | template 191 | inline void WriteOffsetValue(AT address, Var var) 192 | { 193 | Memory::WriteOffsetValue(DynBaseAddress(address), var); 194 | } 195 | 196 | template 197 | inline void ReadOffsetValue(AT address, Var& var) 198 | { 199 | Memory::ReadOffsetValue(DynBaseAddress(address), var); 200 | } 201 | 202 | template 203 | inline void InjectHook(AT address, Func hook) 204 | { 205 | Memory::InjectHook(DynBaseAddress(address), hook); 206 | } 207 | 208 | template 209 | inline void InjectHook(AT address, Func hook, HookType type) 210 | { 211 | Memory::InjectHook(DynBaseAddress(address), hook, static_cast(type)); 212 | } 213 | 214 | template 215 | inline void ReadCall(AT address, Func& func) 216 | { 217 | Memory::ReadCall(DynBaseAddress(address), func); 218 | } 219 | 220 | template 221 | inline void* ReadCallFrom(AT address, ptrdiff_t offset = 0) 222 | { 223 | return Memory::ReadCallFrom(DynBaseAddress(address), offset); 224 | } 225 | 226 | constexpr auto InterceptCall = [](auto address, auto&& func, auto&& hook) 227 | { 228 | Memory::InterceptCall(DynBaseAddress(address), func, hook); 229 | }; 230 | 231 | #ifndef _MEMORY_NO_CRT 232 | inline bool MemEquals(uintptr_t address, std::initializer_list val) 233 | { 234 | return Memory::MemEquals(DynBaseAddress(address), std::move(val)); 235 | } 236 | 237 | template 238 | inline AT Verify(AT address, uintptr_t expected) 239 | { 240 | return Memory::Verify(address, DynBaseAddress(expected)); 241 | } 242 | #endif 243 | }; 244 | 245 | namespace VP 246 | { 247 | enum class HookType 248 | { 249 | Call, 250 | Jump, 251 | }; 252 | 253 | using Memory::DynBaseAddress; 254 | 255 | template 256 | inline void Patch(AT address, T value) 257 | { 258 | DWORD dwProtect; 259 | VirtualProtect((void*)address, sizeof(T), PAGE_EXECUTE_READWRITE, &dwProtect); 260 | Memory::Patch( address, value ); 261 | VirtualProtect((void*)address, sizeof(T), dwProtect, &dwProtect); 262 | } 263 | 264 | #ifndef _MEMORY_NO_CRT 265 | template 266 | inline void Patch(AT address, std::initializer_list list ) 267 | { 268 | DWORD dwProtect; 269 | VirtualProtect((void*)address, list.size(), PAGE_EXECUTE_READWRITE, &dwProtect); 270 | Memory::Patch(address, std::move(list)); 271 | VirtualProtect((void*)address, list.size(), dwProtect, &dwProtect); 272 | } 273 | #endif 274 | 275 | template 276 | inline void Nop(AT address, size_t count) 277 | { 278 | DWORD dwProtect; 279 | VirtualProtect((void*)address, count, PAGE_EXECUTE_READWRITE, &dwProtect); 280 | Memory::Nop( address, count ); 281 | VirtualProtect((void*)address, count, dwProtect, &dwProtect); 282 | } 283 | 284 | template 285 | inline void WriteOffsetValue(AT address, Var var) 286 | { 287 | DWORD dwProtect; 288 | 289 | VirtualProtect((void*)address, 4, PAGE_EXECUTE_READWRITE, &dwProtect); 290 | Memory::WriteOffsetValue(address, var); 291 | VirtualProtect((void*)address, 4, dwProtect, &dwProtect); 292 | } 293 | 294 | template 295 | inline void ReadOffsetValue(AT address, Var& var) 296 | { 297 | Memory::ReadOffsetValue(address, var); 298 | } 299 | 300 | template 301 | inline void InjectHook(AT address, Func hook) 302 | { 303 | DWORD dwProtect; 304 | 305 | VirtualProtect((void*)((DWORD_PTR)address + 1), 4, PAGE_EXECUTE_READWRITE, &dwProtect); 306 | Memory::InjectHook( address, hook ); 307 | VirtualProtect((void*)((DWORD_PTR)address + 1), 4, dwProtect, &dwProtect); 308 | } 309 | 310 | template 311 | inline void InjectHook(AT address, Func hook, HookType type) 312 | { 313 | DWORD dwProtect; 314 | 315 | VirtualProtect((void*)address, 5, PAGE_EXECUTE_READWRITE, &dwProtect); 316 | Memory::InjectHook( address, hook, static_cast(type) ); 317 | VirtualProtect((void*)address, 5, dwProtect, &dwProtect); 318 | } 319 | 320 | template 321 | inline void ReadCall(AT address, Func& func) 322 | { 323 | Memory::ReadCall(address, func); 324 | } 325 | 326 | template 327 | inline void* ReadCallFrom(AT address, ptrdiff_t offset = 0) 328 | { 329 | return Memory::ReadCallFrom(address, offset); 330 | } 331 | 332 | constexpr auto InterceptCall = [](auto address, auto&& func, auto&& hook) 333 | { 334 | DWORD dwProtect; 335 | 336 | VirtualProtect((void*)address, 5, PAGE_EXECUTE_READWRITE, &dwProtect); 337 | Memory::InterceptCall(address, func, hook); 338 | VirtualProtect((void*)address, 5, dwProtect, &dwProtect); 339 | }; 340 | 341 | #ifndef _MEMORY_NO_CRT 342 | inline bool MemEquals(uintptr_t address, std::initializer_list val) 343 | { 344 | return Memory::MemEquals(address, std::move(val)); 345 | } 346 | #endif 347 | 348 | template 349 | inline AT Verify(AT address, uintptr_t expected) 350 | { 351 | return Memory::Verify(address, expected); 352 | } 353 | 354 | namespace DynBase 355 | { 356 | enum class HookType 357 | { 358 | Call, 359 | Jump, 360 | }; 361 | 362 | using Memory::DynBaseAddress; 363 | 364 | template 365 | inline void Patch(AT address, T value) 366 | { 367 | VP::Patch(DynBaseAddress(address), value); 368 | } 369 | 370 | #ifndef _MEMORY_NO_CRT 371 | template 372 | inline void Patch(AT address, std::initializer_list list ) 373 | { 374 | VP::Patch(DynBaseAddress(address), std::move(list)); 375 | } 376 | #endif 377 | 378 | template 379 | inline void Nop(AT address, size_t count) 380 | { 381 | VP::Nop(DynBaseAddress(address), count); 382 | } 383 | 384 | template 385 | inline void WriteOffsetValue(AT address, Var var) 386 | { 387 | VP::WriteOffsetValue(DynBaseAddress(address), var); 388 | } 389 | 390 | template 391 | inline void ReadOffsetValue(AT address, Var& var) 392 | { 393 | VP::ReadOffsetValue(DynBaseAddress(address), var); 394 | } 395 | 396 | template 397 | inline void InjectHook(AT address, Func hook) 398 | { 399 | VP::InjectHook(DynBaseAddress(address), hook); 400 | } 401 | 402 | template 403 | inline void InjectHook(AT address, Func hook, HookType type) 404 | { 405 | VP::InjectHook(DynBaseAddress(address), hook, static_cast(type)); 406 | } 407 | 408 | template 409 | inline void ReadCall(AT address, Func& func) 410 | { 411 | Memory::ReadCall(DynBaseAddress(address), func); 412 | } 413 | 414 | template 415 | inline void* ReadCallFrom(AT address, ptrdiff_t offset = 0) 416 | { 417 | return Memory::ReadCallFrom(DynBaseAddress(address), offset); 418 | } 419 | 420 | constexpr auto InterceptCall = [](auto address, auto&& func, auto&& hook) 421 | { 422 | VP::InterceptCall(DynBaseAddress(address), func, hook); 423 | }; 424 | 425 | #ifndef _MEMORY_NO_CRT 426 | inline bool MemEquals(uintptr_t address, std::initializer_list val) 427 | { 428 | return Memory::MemEquals(DynBaseAddress(address), std::move(val)); 429 | } 430 | #endif 431 | 432 | template 433 | inline AT Verify(AT address, uintptr_t expected) 434 | { 435 | return Memory::Verify(address, DynBaseAddress(expected)); 436 | } 437 | 438 | }; 439 | }; 440 | }; 441 | 442 | #endif 443 | -------------------------------------------------------------------------------- /ModuleList.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | // Stores a list of loaded modules with their names, WITHOUT extension 9 | class ModuleList 10 | { 11 | public: 12 | struct LazyEnumerate_t {}; 13 | static constexpr LazyEnumerate_t LazyEnumerate {}; 14 | 15 | ModuleList() 16 | { 17 | Enumerate(); 18 | } 19 | 20 | explicit ModuleList( LazyEnumerate_t ) 21 | { 22 | } 23 | 24 | // Initializes module list 25 | // Needs to be called before any calls to Get or GetAll 26 | void Enumerate() 27 | { 28 | // Cannot enumerate twice without cleaing 29 | assert( m_moduleList.size() == 0 ); 30 | 31 | constexpr size_t INITIAL_SIZE = sizeof(HMODULE) * 256; 32 | HMODULE* modules = static_cast(malloc( INITIAL_SIZE )); 33 | if ( modules != nullptr ) 34 | { 35 | typedef BOOL (WINAPI * Func)(HANDLE hProcess, HMODULE *lphModule, DWORD cb, LPDWORD lpcbNeeded); 36 | 37 | HMODULE hLib = LoadLibrary( TEXT("kernel32") ); 38 | assert( hLib != nullptr ); // If this fails then everything is probably broken anyway 39 | 40 | Func pEnumProcessModules = reinterpret_cast(GetProcAddress( hLib, "K32EnumProcessModules" )); 41 | if ( pEnumProcessModules == nullptr ) 42 | { 43 | // Try psapi 44 | FreeLibrary( hLib ); 45 | hLib = LoadLibrary( TEXT("psapi") ); 46 | if ( hLib != nullptr ) 47 | { 48 | pEnumProcessModules = reinterpret_cast(GetProcAddress( hLib, "EnumProcessModules" )); 49 | } 50 | } 51 | 52 | if ( pEnumProcessModules != nullptr ) 53 | { 54 | const HANDLE currentProcess = GetCurrentProcess(); 55 | DWORD cbNeeded = 0; 56 | if ( pEnumProcessModules( currentProcess, modules, INITIAL_SIZE, &cbNeeded ) != 0 ) 57 | { 58 | if ( cbNeeded > INITIAL_SIZE ) 59 | { 60 | HMODULE* newModules = static_cast(realloc( modules, cbNeeded )); 61 | if ( newModules != nullptr ) 62 | { 63 | modules = newModules; 64 | 65 | if ( pEnumProcessModules( currentProcess, modules, cbNeeded, &cbNeeded ) != 0 ) 66 | { 67 | EnumerateInternal( modules, cbNeeded / sizeof(HMODULE) ); 68 | } 69 | } 70 | else 71 | { 72 | EnumerateInternal( modules, INITIAL_SIZE / sizeof(HMODULE) ); 73 | } 74 | } 75 | else 76 | { 77 | EnumerateInternal( modules, cbNeeded / sizeof(HMODULE) ); 78 | } 79 | } 80 | } 81 | 82 | if ( hLib != nullptr ) 83 | { 84 | FreeLibrary( hLib ); 85 | } 86 | 87 | free( modules ); 88 | } 89 | } 90 | 91 | // Recreates module list 92 | void ReEnumerate() 93 | { 94 | Clear(); 95 | Enumerate(); 96 | } 97 | 98 | // Clears module list 99 | void Clear() 100 | { 101 | m_moduleList.clear(); 102 | } 103 | 104 | // Gets handle of a loaded module with given name, NULL otherwise 105 | HMODULE Get( const wchar_t* moduleName ) const 106 | { 107 | // If vector is empty then we're trying to call it without calling Enumerate first 108 | assert( m_moduleList.size() != 0 ); 109 | 110 | auto it = std::find_if( m_moduleList.begin(), m_moduleList.end(), [&]( const auto& e ) { 111 | return _wcsicmp( moduleName, e.second.c_str() ) == 0; 112 | } ); 113 | return it != m_moduleList.end() ? it->first : nullptr; 114 | } 115 | 116 | // Gets handles to all loaded modules with given name 117 | std::vector GetAll( const wchar_t* moduleName ) const 118 | { 119 | // If vector is empty then we're trying to call it without calling Enumerate first 120 | assert( m_moduleList.size() != 0 ); 121 | 122 | std::vector results; 123 | for ( auto& e : m_moduleList ) 124 | { 125 | if ( _wcsicmp( moduleName, e.second.c_str() ) == 0 ) 126 | { 127 | results.push_back( e.first ); 128 | } 129 | } 130 | 131 | return results; 132 | } 133 | 134 | // Gets handle of a loaded module with given prefix, NULL otherwise 135 | HMODULE GetByPrefix( const wchar_t* modulePrefix ) const 136 | { 137 | // If vector is empty then we're trying to call it without calling Enumerate first 138 | assert( m_moduleList.size() != 0 ); 139 | 140 | const size_t len = wcslen( modulePrefix ); 141 | auto it = std::find_if( m_moduleList.begin(), m_moduleList.end(), [&]( const auto& e ) { 142 | return _wcsnicmp( modulePrefix, e.second.c_str(), len ) == 0; 143 | } ); 144 | return it != m_moduleList.end() ? it->first : nullptr; 145 | } 146 | 147 | // Gets handles to all loaded modules with given prefix 148 | std::vector GetAllByPrefix( const wchar_t* modulePrefix ) const 149 | { 150 | // If vector is empty then we're trying to call it without calling Enumerate first 151 | assert( m_moduleList.size() != 0 ); 152 | 153 | const size_t len = wcslen( modulePrefix ); 154 | std::vector results; 155 | for ( auto& e : m_moduleList ) 156 | { 157 | if ( _wcsnicmp( modulePrefix, e.second.c_str(), len ) == 0 ) 158 | { 159 | results.push_back( e.first ); 160 | } 161 | } 162 | 163 | return results; 164 | } 165 | 166 | private: 167 | void EnumerateInternal( HMODULE* modules, size_t numModules ) 168 | { 169 | size_t moduleNameLength = MAX_PATH; 170 | wchar_t* moduleName = static_cast( malloc( moduleNameLength * sizeof(moduleName[0]) ) ); 171 | if ( moduleName != nullptr ) 172 | { 173 | m_moduleList.reserve( numModules ); 174 | for ( size_t i = 0; i < numModules; i++ ) 175 | { 176 | // Obtain module name, with resizing if necessary 177 | DWORD size; 178 | while ( size = GetModuleFileNameW( *modules, moduleName, moduleNameLength ), size == moduleNameLength ) 179 | { 180 | wchar_t* newName = static_cast( realloc( moduleName, 2 * moduleNameLength * sizeof(moduleName[0]) ) ); 181 | if ( newName != nullptr ) 182 | { 183 | moduleName = newName; 184 | moduleNameLength *= 2; 185 | } 186 | else 187 | { 188 | size = 0; 189 | break; 190 | } 191 | } 192 | 193 | if ( size != 0 ) 194 | { 195 | const wchar_t* nameBegin = wcsrchr( moduleName, '\\' ) + 1; 196 | const wchar_t* dotPos = wcsrchr( nameBegin, '.' ); 197 | if ( dotPos != nullptr ) 198 | { 199 | m_moduleList.emplace_back( std::piecewise_construct, std::forward_as_tuple(*modules), std::forward_as_tuple(nameBegin, dotPos) ); 200 | } 201 | else 202 | { 203 | m_moduleList.emplace_back( *modules, nameBegin ); 204 | } 205 | } 206 | modules++; 207 | } 208 | 209 | free( moduleName ); 210 | } 211 | } 212 | 213 | std::vector< std::pair > m_moduleList; 214 | }; -------------------------------------------------------------------------------- /Patterns.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the CitizenFX project - http://citizen.re/ 3 | * 4 | * See LICENSE and MENTIONS in the root of the source tree for information 5 | * regarding licensing. 6 | */ 7 | 8 | #include "Patterns.h" 9 | 10 | #define WIN32_LEAN_AND_MEAN 11 | 12 | #ifndef NOMINMAX 13 | #define NOMINMAX 14 | #endif 15 | 16 | #include 17 | #include 18 | 19 | #if PATTERNS_USE_HINTS 20 | #include 21 | #endif 22 | 23 | 24 | #if PATTERNS_USE_HINTS 25 | 26 | // from boost someplace 27 | template 28 | struct basic_fnv_1 29 | { 30 | std::uint64_t operator()(std::string_view text) const 31 | { 32 | std::uint64_t hash = OffsetBasis; 33 | for (auto it : text) 34 | { 35 | hash *= FnvPrime; 36 | hash ^= it; 37 | } 38 | 39 | return hash; 40 | } 41 | }; 42 | 43 | static constexpr std::uint64_t fnv_prime = 1099511628211u; 44 | static constexpr std::uint64_t fnv_offset_basis = 14695981039346656037u; 45 | 46 | typedef basic_fnv_1 fnv_1; 47 | 48 | #endif 49 | 50 | namespace hook 51 | { 52 | 53 | ptrdiff_t details::get_process_base() 54 | { 55 | return ptrdiff_t(GetModuleHandle(nullptr)); 56 | } 57 | 58 | 59 | #if PATTERNS_USE_HINTS 60 | static auto& getHints() 61 | { 62 | static std::multimap hints; 63 | return hints; 64 | } 65 | #endif 66 | 67 | static void TransformPattern(std::string_view pattern, pattern_string& data, pattern_string& mask) 68 | { 69 | uint8_t tempDigit = 0; 70 | bool tempFlag = false; 71 | 72 | auto tol = [] (char ch) -> uint8_t 73 | { 74 | if (ch >= 'A' && ch <= 'F') return uint8_t(ch - 'A' + 10); 75 | if (ch >= 'a' && ch <= 'f') return uint8_t(ch - 'a' + 10); 76 | return uint8_t(ch - '0'); 77 | }; 78 | 79 | for (auto ch : pattern) 80 | { 81 | if (ch == ' ') 82 | { 83 | continue; 84 | } 85 | else if (ch == '?') 86 | { 87 | data.push_back(0); 88 | mask.push_back(0); 89 | } 90 | else if ((ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'F') || (ch >= 'a' && ch <= 'f')) 91 | { 92 | uint8_t thisDigit = tol(ch); 93 | 94 | if (!tempFlag) 95 | { 96 | tempDigit = thisDigit << 4; 97 | tempFlag = true; 98 | } 99 | else 100 | { 101 | tempDigit |= thisDigit; 102 | tempFlag = false; 103 | 104 | data.push_back(tempDigit); 105 | mask.push_back(0xFF); 106 | } 107 | } 108 | } 109 | } 110 | 111 | class executable_meta 112 | { 113 | private: 114 | uintptr_t m_begin; 115 | uintptr_t m_end; 116 | 117 | public: 118 | explicit executable_meta(uintptr_t module) 119 | { 120 | PIMAGE_DOS_HEADER dosHeader = reinterpret_cast(module); 121 | PIMAGE_NT_HEADERS ntHeader = reinterpret_cast(module + dosHeader->e_lfanew); 122 | 123 | m_begin = module + ntHeader->OptionalHeader.BaseOfCode; 124 | m_end = m_begin + ntHeader->OptionalHeader.SizeOfCode; 125 | 126 | // Executables with DRM bypassed may lie in their SizeOfCode and underreport severely 127 | // We can somewhat detect this by checking if the code entry point is past 128 | // these boundaries. It's not perfect, but it's safe. 129 | const uintptr_t entryPoint = module + ntHeader->OptionalHeader.AddressOfEntryPoint; 130 | if (entryPoint >= m_begin && entryPoint < m_end) 131 | { 132 | return; 133 | } 134 | 135 | // Alternate heuristics - scan the entire executable, minus headers 136 | const uintptr_t sizeOfHeaders = ntHeader->OptionalHeader.SizeOfHeaders; 137 | m_begin = module + sizeOfHeaders; 138 | m_end = module + (ntHeader->OptionalHeader.SizeOfImage - sizeOfHeaders); 139 | } 140 | 141 | executable_meta(uintptr_t begin, uintptr_t end) 142 | : m_begin(begin), m_end(end) 143 | { 144 | } 145 | 146 | inline uintptr_t begin() const { return m_begin; } 147 | inline uintptr_t end() const { return m_end; } 148 | }; 149 | 150 | namespace details 151 | { 152 | 153 | void basic_pattern_impl::Initialize(std::string_view pattern) 154 | { 155 | // get the hash for the base pattern 156 | #if PATTERNS_USE_HINTS 157 | m_hash = fnv_1()(pattern); 158 | #endif 159 | 160 | // transform the base pattern from IDA format to canonical format 161 | TransformPattern(pattern, m_bytes, m_mask); 162 | 163 | #if PATTERNS_USE_HINTS 164 | // if there's hints, try those first 165 | #if PATTERNS_CAN_SERIALIZE_HINTS 166 | if (m_rangeStart == reinterpret_cast(GetModuleHandle(nullptr))) 167 | #endif 168 | { 169 | auto range = getHints().equal_range(m_hash); 170 | 171 | if (range.first != range.second) 172 | { 173 | std::for_each(range.first, range.second, [&] (const auto& hint) 174 | { 175 | ConsiderHint(hint.second); 176 | }); 177 | 178 | // if the hints succeeded, we don't need to do anything more 179 | if (!m_matches.empty()) 180 | { 181 | m_matched = true; 182 | return; 183 | } 184 | } 185 | } 186 | #endif 187 | } 188 | 189 | void basic_pattern_impl::EnsureMatches(uint32_t maxCount) 190 | { 191 | if (m_matched) 192 | { 193 | return; 194 | } 195 | 196 | // scan the executable for code 197 | executable_meta executable = m_rangeStart != 0 && m_rangeEnd != 0 ? executable_meta(m_rangeStart, m_rangeEnd) : executable_meta(m_rangeStart); 198 | 199 | auto matchSuccess = [&] (uintptr_t address) 200 | { 201 | #if PATTERNS_USE_HINTS 202 | getHints().emplace(m_hash, address); 203 | #else 204 | (void)address; 205 | #endif 206 | 207 | return (m_matches.size() == maxCount); 208 | }; 209 | 210 | const uint8_t* pattern = m_bytes.data(); 211 | const uint8_t* mask = m_mask.data(); 212 | const size_t maskSize = m_mask.size(); 213 | const size_t lastWild = m_mask.find_last_not_of(uint8_t(0xFF)); 214 | 215 | ptrdiff_t Last[256]; 216 | 217 | std::fill(std::begin(Last), std::end(Last), lastWild == std::string::npos ? -1 : static_cast(lastWild) ); 218 | 219 | for ( ptrdiff_t i = 0; i < static_cast(maskSize); ++i ) 220 | { 221 | if ( Last[ pattern[i] ] < i ) 222 | { 223 | Last[ pattern[i] ] = i; 224 | } 225 | } 226 | 227 | for (uintptr_t i = executable.begin(), end = executable.end() - maskSize; i <= end;) 228 | { 229 | uint8_t* ptr = reinterpret_cast(i); 230 | ptrdiff_t j = maskSize - 1; 231 | 232 | while((j >= 0) && pattern[j] == (ptr[j] & mask[j])) j--; 233 | 234 | if(j < 0) 235 | { 236 | m_matches.emplace_back(ptr); 237 | 238 | if (matchSuccess(i)) 239 | { 240 | break; 241 | } 242 | i++; 243 | } 244 | else i += std::max(ptrdiff_t(1), j - Last[ ptr[j] ]); 245 | } 246 | 247 | m_matched = true; 248 | } 249 | 250 | bool basic_pattern_impl::ConsiderHint(uintptr_t offset) 251 | { 252 | uint8_t* ptr = reinterpret_cast(offset); 253 | 254 | #if PATTERNS_CAN_SERIALIZE_HINTS 255 | const uint8_t* pattern = m_bytes.data(); 256 | const uint8_t* mask = m_mask.data(); 257 | 258 | for (size_t i = 0, j = m_mask.size(); i < j; i++) 259 | { 260 | if (pattern[i] != (ptr[i] & mask[i])) 261 | { 262 | return false; 263 | } 264 | } 265 | #endif 266 | 267 | m_matches.emplace_back(ptr); 268 | 269 | return true; 270 | } 271 | 272 | #if PATTERNS_USE_HINTS && PATTERNS_CAN_SERIALIZE_HINTS 273 | void basic_pattern_impl::hint(uint64_t hash, uintptr_t address) 274 | { 275 | auto& hints = getHints(); 276 | 277 | auto range = hints.equal_range(hash); 278 | 279 | for (auto it = range.first; it != range.second; ++it) 280 | { 281 | if (it->second == address) 282 | { 283 | return; 284 | } 285 | } 286 | 287 | hints.emplace(hash, address); 288 | } 289 | #endif 290 | 291 | } 292 | } 293 | -------------------------------------------------------------------------------- /Patterns.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the CitizenFX project - http://citizen.re/ 3 | * 4 | * See LICENSE and MENTIONS in the root of the source tree for information 5 | * regarding licensing. 6 | */ 7 | 8 | #pragma once 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #if (defined(_CPPUNWIND) || defined(__EXCEPTIONS)) && !defined(PATTERNS_SUPPRESS_EXCEPTIONS) 18 | #define PATTERNS_ENABLE_EXCEPTIONS 19 | #endif 20 | 21 | namespace hook 22 | { 23 | // This is inspired by the char_traits implementation from nlohmann's json library 24 | struct pattern_traits : std::char_traits 25 | { 26 | using char_type = uint8_t; 27 | 28 | // Redefine move function 29 | static char_type* move(char_type* dest, const char_type* src, std::size_t count) noexcept 30 | { 31 | return static_cast(std::memmove(dest, src, count)); 32 | } 33 | 34 | // Redefine assign function 35 | static void assign(char_type& c1, const char_type& c2) noexcept 36 | { 37 | c1 = c2; 38 | } 39 | 40 | // Redefine copy function 41 | static char_type* copy(char_type* dest, const char_type* src, std::size_t count) noexcept 42 | { 43 | return static_cast(std::memcpy(dest, src, count)); 44 | } 45 | }; 46 | 47 | using pattern_string = std::basic_string; 48 | using pattern_string_view = std::basic_string_view; 49 | 50 | struct assert_err_policy 51 | { 52 | static void count([[maybe_unused]] bool countMatches) { assert(countMatches); } 53 | }; 54 | 55 | #ifdef PATTERNS_ENABLE_EXCEPTIONS 56 | class txn_exception 57 | { 58 | // Deliberately empty for now 59 | }; 60 | 61 | #define TXN_CATCH() catch (const hook::txn_exception&) {} 62 | 63 | struct exception_err_policy 64 | { 65 | static void count(bool countMatches) { if (!countMatches) { throw txn_exception{}; } } 66 | }; 67 | #else 68 | struct exception_err_policy : public assert_err_policy 69 | { 70 | }; 71 | #endif 72 | 73 | class pattern_match 74 | { 75 | private: 76 | void* m_pointer; 77 | 78 | public: 79 | inline pattern_match(void* pointer) 80 | : m_pointer(pointer) 81 | { 82 | } 83 | 84 | template 85 | T* get(ptrdiff_t offset = 0) const 86 | { 87 | char* ptr = reinterpret_cast(m_pointer); 88 | return reinterpret_cast(ptr + offset); 89 | } 90 | 91 | uintptr_t get_uintptr(ptrdiff_t offset = 0) const 92 | { 93 | return reinterpret_cast(get(offset)); 94 | } 95 | }; 96 | 97 | namespace details 98 | { 99 | ptrdiff_t get_process_base(); 100 | 101 | class basic_pattern_impl 102 | { 103 | protected: 104 | pattern_string m_bytes; 105 | pattern_string m_mask; 106 | 107 | #if PATTERNS_USE_HINTS 108 | uint64_t m_hash = 0; 109 | #endif 110 | 111 | std::vector m_matches; 112 | 113 | bool m_matched = false; 114 | 115 | uintptr_t m_rangeStart; 116 | uintptr_t m_rangeEnd; 117 | 118 | protected: 119 | void Initialize(std::string_view pattern); 120 | 121 | bool ConsiderHint(uintptr_t offset); 122 | 123 | void EnsureMatches(uint32_t maxCount); 124 | 125 | inline pattern_match _get_internal(size_t index) const 126 | { 127 | return m_matches[index]; 128 | } 129 | 130 | private: 131 | explicit basic_pattern_impl(uintptr_t begin, uintptr_t end = 0) 132 | : m_rangeStart(begin), m_rangeEnd(end) 133 | { 134 | } 135 | 136 | public: 137 | explicit basic_pattern_impl(std::string_view pattern) 138 | : basic_pattern_impl(get_process_base()) 139 | { 140 | Initialize(std::move(pattern)); 141 | } 142 | 143 | inline basic_pattern_impl(void* module, std::string_view pattern) 144 | : basic_pattern_impl(reinterpret_cast(module)) 145 | { 146 | Initialize(std::move(pattern)); 147 | } 148 | 149 | inline basic_pattern_impl(uintptr_t begin, uintptr_t end, std::string_view pattern) 150 | : basic_pattern_impl(begin, end) 151 | { 152 | Initialize(std::move(pattern)); 153 | } 154 | 155 | // Pretransformed patterns 156 | inline basic_pattern_impl(pattern_string_view bytes, pattern_string_view mask) 157 | : basic_pattern_impl(get_process_base()) 158 | { 159 | assert( bytes.length() == mask.length() ); 160 | m_bytes = std::move(bytes); 161 | m_mask = std::move(mask); 162 | } 163 | 164 | inline basic_pattern_impl(void* module, pattern_string_view bytes, pattern_string_view mask) 165 | : basic_pattern_impl(reinterpret_cast(module)) 166 | { 167 | assert( bytes.length() == mask.length() ); 168 | m_bytes = std::move(bytes); 169 | m_mask = std::move(mask); 170 | } 171 | 172 | inline basic_pattern_impl(uintptr_t begin, uintptr_t end, pattern_string_view bytes, pattern_string_view mask) 173 | : basic_pattern_impl(begin, end) 174 | { 175 | assert( bytes.length() == mask.length() ); 176 | m_bytes = std::move(bytes); 177 | m_mask = std::move(mask); 178 | } 179 | 180 | protected: 181 | #if PATTERNS_USE_HINTS && PATTERNS_CAN_SERIALIZE_HINTS 182 | // define a hint 183 | static void hint(uint64_t hash, uintptr_t address); 184 | #endif 185 | }; 186 | } 187 | 188 | template 189 | class basic_pattern : details::basic_pattern_impl 190 | { 191 | public: 192 | using details::basic_pattern_impl::basic_pattern_impl; 193 | 194 | inline basic_pattern&& count(uint32_t expected) 195 | { 196 | EnsureMatches(expected); 197 | err_policy::count(m_matches.size() == expected); 198 | return std::forward(*this); 199 | } 200 | 201 | inline basic_pattern&& count_hint(uint32_t expected) 202 | { 203 | EnsureMatches(expected); 204 | return std::forward(*this); 205 | } 206 | 207 | inline basic_pattern&& clear() 208 | { 209 | m_matches.clear(); 210 | m_matched = false; 211 | return std::forward(*this); 212 | } 213 | 214 | inline size_t size() 215 | { 216 | EnsureMatches(UINT32_MAX); 217 | return m_matches.size(); 218 | } 219 | 220 | inline bool empty() 221 | { 222 | return size() == 0; 223 | } 224 | 225 | inline pattern_match get(size_t index) 226 | { 227 | EnsureMatches(UINT32_MAX); 228 | return _get_internal(index); 229 | } 230 | 231 | inline pattern_match get_one() 232 | { 233 | return std::forward(*this).count(1)._get_internal(0); 234 | } 235 | 236 | template 237 | inline auto get_first(ptrdiff_t offset = 0) 238 | { 239 | return get_one().template get(offset); 240 | } 241 | 242 | template 243 | inline Pred for_each_result(Pred pred) 244 | { 245 | EnsureMatches(UINT32_MAX); 246 | for (auto match : m_matches) 247 | { 248 | pred(match); 249 | } 250 | return pred; 251 | } 252 | 253 | public: 254 | #if PATTERNS_USE_HINTS && PATTERNS_CAN_SERIALIZE_HINTS 255 | // define a hint 256 | static void hint(uint64_t hash, uintptr_t address) 257 | { 258 | details::basic_pattern_impl::hint(hash, address); 259 | } 260 | #endif 261 | }; 262 | 263 | using pattern = basic_pattern; 264 | 265 | inline auto make_module_pattern(void* module, std::string_view bytes) 266 | { 267 | return pattern(module, std::move(bytes)); 268 | } 269 | 270 | inline auto make_range_pattern(uintptr_t begin, uintptr_t end, std::string_view bytes) 271 | { 272 | return pattern(begin, end, std::move(bytes)); 273 | } 274 | 275 | template 276 | inline auto get_pattern(std::string_view pattern_string, ptrdiff_t offset = 0) 277 | { 278 | return pattern(std::move(pattern_string)).get_first(offset); 279 | } 280 | 281 | inline auto get_pattern_uintptr(std::string_view pattern_string, ptrdiff_t offset = 0) 282 | { 283 | return pattern(std::move(pattern_string)).get_one().get_uintptr(offset); 284 | } 285 | 286 | namespace txn 287 | { 288 | using pattern = hook::basic_pattern; 289 | using hook::pattern_match; 290 | 291 | inline auto make_module_pattern(void* module, std::string_view bytes) 292 | { 293 | return pattern(module, std::move(bytes)); 294 | } 295 | 296 | inline auto make_range_pattern(uintptr_t begin, uintptr_t end, std::string_view bytes) 297 | { 298 | return pattern(begin, end, std::move(bytes)); 299 | } 300 | 301 | template 302 | inline auto get_pattern(std::string_view pattern_string, ptrdiff_t offset = 0) 303 | { 304 | return pattern(std::move(pattern_string)).get_first(offset); 305 | } 306 | 307 | inline auto get_pattern_uintptr(std::string_view pattern_string, ptrdiff_t offset = 0) 308 | { 309 | return pattern(std::move(pattern_string)).get_one().get_uintptr(offset); 310 | } 311 | } 312 | } 313 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ModUtils 2 | Modding utils used in most of my projects. Mostly for internal use, but feel free to make use of those! 3 | -------------------------------------------------------------------------------- /ScopedUnprotect.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | // Object that removes write protection from the code section or the entire module for as long as the object is in scope 8 | namespace ScopedUnprotect 9 | { 10 | class Unprotect 11 | { 12 | public: 13 | ~Unprotect() 14 | { 15 | for ( auto& it : m_queriedProtects ) 16 | { 17 | DWORD dwOldProtect; 18 | VirtualProtect( std::get<0>(it), std::get<1>(it), std::get<2>(it), &dwOldProtect ); 19 | } 20 | } 21 | 22 | protected: 23 | Unprotect() = default; 24 | 25 | void UnprotectRange( DWORD_PTR BaseAddress, SIZE_T Size ) 26 | { 27 | SIZE_T QueriedSize = 0; 28 | while ( QueriedSize < Size ) 29 | { 30 | MEMORY_BASIC_INFORMATION MemoryInf; 31 | DWORD dwOldProtect; 32 | 33 | VirtualQuery( (LPCVOID)(BaseAddress + QueriedSize), &MemoryInf, sizeof(MemoryInf) ); 34 | if ( MemoryInf.State == MEM_COMMIT && (MemoryInf.Type & MEM_IMAGE) != 0 && 35 | (MemoryInf.Protect & (PAGE_EXECUTE_READWRITE|PAGE_EXECUTE_WRITECOPY|PAGE_READWRITE|PAGE_WRITECOPY)) == 0 ) 36 | { 37 | const bool wasExecutable = (MemoryInf.Protect & (PAGE_EXECUTE|PAGE_EXECUTE_READ)) != 0; 38 | VirtualProtect( MemoryInf.BaseAddress, MemoryInf.RegionSize, wasExecutable ? PAGE_EXECUTE_READWRITE : PAGE_READWRITE, &dwOldProtect ); 39 | m_queriedProtects.emplace_front( MemoryInf.BaseAddress, MemoryInf.RegionSize, MemoryInf.Protect ); 40 | } 41 | QueriedSize += MemoryInf.RegionSize; 42 | } 43 | } 44 | 45 | private: 46 | std::forward_list< std::tuple< LPVOID, SIZE_T, DWORD > > m_queriedProtects; 47 | }; 48 | 49 | class Section : public Unprotect 50 | { 51 | public: 52 | Section( HINSTANCE hInstance, const char* name ) 53 | { 54 | PIMAGE_NT_HEADERS ntHeader = (PIMAGE_NT_HEADERS)((DWORD_PTR)hInstance + ((PIMAGE_DOS_HEADER)hInstance)->e_lfanew); 55 | PIMAGE_SECTION_HEADER pSection = IMAGE_FIRST_SECTION(ntHeader); 56 | 57 | for ( SIZE_T i = 0, j = ntHeader->FileHeader.NumberOfSections; i < j; ++i, ++pSection ) 58 | { 59 | if ( strncmp( (const char*)pSection->Name, name, IMAGE_SIZEOF_SHORT_NAME ) == 0 ) 60 | { 61 | const DWORD_PTR VirtualAddress = (DWORD_PTR)hInstance + pSection->VirtualAddress; 62 | const SIZE_T VirtualSize = pSection->Misc.VirtualSize; 63 | UnprotectRange( VirtualAddress, VirtualSize ); 64 | 65 | m_locatedSection = true; 66 | break; 67 | } 68 | } 69 | }; 70 | 71 | bool SectionLocated() const { return m_locatedSection; } 72 | 73 | private: 74 | bool m_locatedSection = false; 75 | }; 76 | 77 | class FullModule : public Unprotect 78 | { 79 | public: 80 | FullModule( HINSTANCE hInstance ) 81 | { 82 | PIMAGE_NT_HEADERS ntHeader = (PIMAGE_NT_HEADERS)((DWORD_PTR)hInstance + ((PIMAGE_DOS_HEADER)hInstance)->e_lfanew); 83 | UnprotectRange( (DWORD_PTR)hInstance, ntHeader->OptionalHeader.SizeOfImage ); 84 | } 85 | }; 86 | 87 | inline std::unique_ptr UnprotectSectionOrFullModule( HINSTANCE hInstance, const char* name ) 88 | { 89 | std::unique_ptr
section = std::make_unique
( hInstance, name ); 90 | if ( !section->SectionLocated() ) 91 | { 92 | return std::make_unique( hInstance ); 93 | } 94 | return section; 95 | } 96 | }; 97 | -------------------------------------------------------------------------------- /Trampoline.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // Trampolines are useless on x86 arch 4 | #ifdef _WIN64 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | // Trampoline class for big (>2GB) jumps 12 | // Never needed in 32-bit processes so in those cases this does nothing but forwards to Memory functions 13 | // NOTE: Each Trampoline class allocates a page of executable memory for trampolines and does NOT free it when going out of scope 14 | class Trampoline 15 | { 16 | public: 17 | template 18 | static Trampoline* MakeTrampoline( T addr ) 19 | { 20 | return MakeTrampolineInternal( uintptr_t(addr), SINGLE_TRAMPOLINE_SIZE, 1 ); 21 | } 22 | 23 | template 24 | static Trampoline* MakeTrampoline( T addr, size_t size, size_t align ) 25 | { 26 | return MakeTrampolineInternal( uintptr_t(addr), size, align ); 27 | } 28 | 29 | template 30 | LPVOID Jump( Func func ) 31 | { 32 | LPVOID addr; 33 | memcpy( &addr, std::addressof(func), sizeof(addr) ); 34 | return CreateCodeTrampoline( addr ); 35 | } 36 | 37 | template 38 | auto* Pointer( size_t align = alignof(T) ) 39 | { 40 | return static_cast>*>(GetNewSpace( sizeof(T), align )); 41 | } 42 | 43 | template 44 | auto& Reference( size_t align = alignof(T) ) 45 | { 46 | return *Pointer( align ); 47 | } 48 | 49 | std::byte* RawSpace( size_t size, size_t align = 1 ) 50 | { 51 | return static_cast< std::byte* >(GetNewSpace( size, align )); 52 | } 53 | 54 | 55 | private: 56 | static Trampoline* MakeTrampolineInternal( uintptr_t addr, size_t size, size_t align ) 57 | { 58 | Trampoline* current = ms_first; 59 | while ( current != nullptr ) 60 | { 61 | if ( current->FeasibleForAddresss( addr, size, align ) ) return current; 62 | 63 | current = current->m_next; 64 | } 65 | 66 | size_t sizeToAlloc = size + ((sizeof(Trampoline) + align - 1) & ~(align - 1)); 67 | 68 | void* space = FindAndAllocateMem(addr, sizeToAlloc); 69 | void* usableSpace = reinterpret_cast(space) + sizeof(Trampoline); 70 | return new( space ) Trampoline( usableSpace, sizeToAlloc - sizeof(Trampoline) ); 71 | } 72 | 73 | 74 | Trampoline( const Trampoline& ) = delete; 75 | Trampoline& operator=( const Trampoline& ) = delete; 76 | 77 | explicit Trampoline( void* memory, size_t size ) 78 | : m_next( std::exchange( ms_first, this ) ), m_pageMemory( memory ), m_spaceLeft( size ) 79 | { 80 | } 81 | 82 | static constexpr size_t SINGLE_TRAMPOLINE_SIZE = 14; 83 | bool FeasibleForAddresss( uintptr_t addr, size_t size, size_t align ) const 84 | { 85 | const uintptr_t pageMem = reinterpret_cast(m_pageMemory); 86 | if (IsAddressFeasible(pageMem, addr)) 87 | { 88 | // Check if there is enough size (incl. alignment) 89 | // Like in std::align 90 | size_t offset = static_cast(pageMem & (align - 1)); 91 | if (offset != 0) 92 | { 93 | offset = align - offset; // number of bytes to skip 94 | } 95 | return m_spaceLeft >= offset && m_spaceLeft - offset >= size; 96 | } 97 | return false; 98 | } 99 | 100 | LPVOID CreateCodeTrampoline( LPVOID addr ) 101 | { 102 | uint8_t* trampolineSpace = static_cast(GetNewSpace( SINGLE_TRAMPOLINE_SIZE, 1 )); 103 | 104 | // Create trampoline code 105 | const uint8_t jmp[] = { 0xFF, 0x25, 0x00, 0x00, 0x00, 0x00 }; 106 | memcpy(trampolineSpace, jmp, sizeof(jmp)); 107 | memcpy(trampolineSpace + sizeof(jmp), &addr, sizeof(addr)); 108 | 109 | return trampolineSpace; 110 | } 111 | 112 | LPVOID GetNewSpace( size_t size, size_t alignment ) 113 | { 114 | void* space = std::align( alignment, size, m_pageMemory, m_spaceLeft ); 115 | if ( space != nullptr ) 116 | { 117 | m_pageMemory = static_cast(m_pageMemory) + size; 118 | m_spaceLeft -= size; 119 | } 120 | else 121 | { 122 | assert( !"Out of trampoline space!" ); 123 | } 124 | return space; 125 | } 126 | 127 | static void* FindAndAllocateMem( const uintptr_t addr, size_t& size ) 128 | { 129 | uintptr_t curAddr = addr; 130 | 131 | SYSTEM_INFO systemInfo; 132 | GetSystemInfo( &systemInfo ); 133 | const DWORD granularity = systemInfo.dwAllocationGranularity; 134 | 135 | // Align size up to allocation granularity 136 | size = (size + granularity - 1) & ~size_t(granularity - 1); 137 | 138 | // Find the first unallocated page after 'addr' and try to allocate a page for trampolines there 139 | while ( true ) 140 | { 141 | MEMORY_BASIC_INFORMATION MemoryInf; 142 | if ( VirtualQuery( reinterpret_cast(curAddr), &MemoryInf, sizeof(MemoryInf) ) == 0 ) break; 143 | if ( MemoryInf.State == MEM_FREE && MemoryInf.RegionSize >= size ) 144 | { 145 | // Align up to allocation granularity 146 | uintptr_t alignedAddr = uintptr_t(MemoryInf.BaseAddress); 147 | alignedAddr = (alignedAddr + granularity - 1) & ~uintptr_t(granularity - 1); 148 | 149 | if ( !IsAddressFeasible( alignedAddr, addr ) ) break; 150 | 151 | LPVOID mem = VirtualAlloc( reinterpret_cast(alignedAddr), size, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE ); 152 | if ( mem != nullptr ) 153 | { 154 | return mem; 155 | } 156 | } 157 | curAddr += MemoryInf.RegionSize; 158 | } 159 | return nullptr; 160 | } 161 | 162 | static bool IsAddressFeasible( uintptr_t trampolineOffset, uintptr_t addr ) 163 | { 164 | const ptrdiff_t diff = trampolineOffset - addr; 165 | return diff >= INT32_MIN && diff <= INT32_MAX; 166 | } 167 | 168 | Trampoline* m_next = nullptr; 169 | void* m_pageMemory = nullptr; 170 | size_t m_spaceLeft = 0; 171 | 172 | static inline Trampoline* ms_first = nullptr; 173 | }; 174 | 175 | 176 | #endif --------------------------------------------------------------------------------