├── .github └── workflows │ └── cmake.yml ├── CMakeLists.txt ├── LICENSE ├── README.md ├── bindings ├── cs │ ├── .gitignore │ ├── libhat-sharp.sln │ ├── libhat-sharp │ │ ├── Extensions │ │ │ └── ArrayExtensions.cs │ │ ├── Native │ │ │ ├── Functions.cs │ │ │ ├── ScanAlignment.cs │ │ │ ├── Signature.cs │ │ │ └── Status.cs │ │ ├── Pattern.cs │ │ ├── Scanner.cs │ │ ├── Utils.cs │ │ └── libhat-sharp.csproj │ └── libhat-tests │ │ ├── Program.cs │ │ └── libhat-tests.csproj └── java │ ├── .gitignore │ ├── pom.xml │ └── src │ └── main │ └── java │ └── me │ └── zero │ └── libhat │ ├── Hat.java │ ├── ProcessModule.java │ ├── ScanAlignment.java │ ├── Signature.java │ └── jna │ └── Libhat.java ├── example └── modules │ ├── CMakeLists.txt │ └── main.cpp ├── include ├── libhat.hpp └── libhat │ ├── access.hpp │ ├── c │ └── libhat.h │ ├── compressed_pair.hpp │ ├── concepts.hpp │ ├── cow.hpp │ ├── cstring_view.hpp │ ├── defines.hpp │ ├── experimental.hpp │ ├── experimental │ └── call_wrapper.hpp │ ├── export.hpp │ ├── fixed_string.hpp │ ├── memory.hpp │ ├── memory_protector.hpp │ ├── platform.h │ ├── process.hpp │ ├── result.hpp │ ├── scanner.hpp │ ├── signature.hpp │ ├── strconv.hpp │ ├── string_literal.hpp │ ├── system.hpp │ ├── type_traits.hpp │ └── utility.hpp ├── module └── libhat.cppm ├── src ├── Scanner.cpp ├── System.cpp ├── Utils.hpp ├── arch │ ├── arm │ │ └── System.cpp │ └── x86 │ │ ├── AVX2.cpp │ │ ├── AVX512.cpp │ │ ├── Frequency.hpp │ │ ├── SSE.cpp │ │ └── System.cpp ├── c │ └── libhat.cpp └── os │ ├── linux │ ├── Common.hpp │ ├── MemoryProtector.cpp │ └── Process.cpp │ ├── unix │ └── System.cpp │ └── win32 │ ├── MemoryProtector.cpp │ ├── Process.cpp │ ├── Scanner.cpp │ └── System.cpp └── test ├── CMakeLists.txt ├── benchmark ├── Compare.cpp └── vendor │ ├── UC1.hpp │ └── UC2.hpp ├── info └── HardwareInfo.cpp └── tests └── Scanner.cpp /.github/workflows/cmake.yml: -------------------------------------------------------------------------------- 1 | name: CMake 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | pull_request: 7 | branches: [ "master" ] 8 | 9 | env: 10 | BUILD_TYPE: Debug 11 | 12 | jobs: 13 | macos: 14 | strategy: 15 | matrix: 16 | cxx_standard: [ 20, 23 ] 17 | runs-on: macos-latest 18 | steps: 19 | - uses: actions/checkout@v4 20 | 21 | - name: Configure 22 | run: cmake -B ${{github.workspace}}/build -DCMAKE_CXX_STANDARD=${{matrix.cxx_standard}} -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DLIBHAT_TESTING=OFF 23 | 24 | - name: Build 25 | run: cmake --build ${{github.workspace}}/build -j 4 26 | 27 | linux: 28 | strategy: 29 | matrix: 30 | compiler: 31 | - { pkg: g++, exe: g++, version: 14 } 32 | - { pkg: clang, exe: clang++, version: 18 } 33 | cxx_standard: [ 20, 23 ] 34 | runs-on: ubuntu-24.04 35 | steps: 36 | - uses: actions/checkout@v4 37 | 38 | - name: Install Compiler 39 | run: | 40 | sudo apt update 41 | sudo apt install -y ${{matrix.compiler.pkg}}-${{matrix.compiler.version}} 42 | 43 | - name: Configure 44 | env: 45 | CXX: ${{matrix.compiler.exe}}-${{matrix.compiler.version}} 46 | run: cmake -B ${{github.workspace}}/build -DCMAKE_CXX_STANDARD=${{matrix.cxx_standard}} -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DLIBHAT_TESTING=ON 47 | 48 | - name: Build 49 | run: cmake --build ${{github.workspace}}/build -j 4 50 | 51 | - name: Test 52 | working-directory: ${{github.workspace}}/build 53 | run: ctest -C ${{env.BUILD_TYPE}} -R libhat_test_.* 54 | 55 | windows: 56 | strategy: 57 | matrix: 58 | target: [ Win32, x64 ] # ARM, ARM64 59 | toolset: [ v143, ClangCL ] 60 | cxx_standard: [ 20, 23 ] 61 | include: 62 | - target: Win32 63 | vcvars: vcvars32 64 | - target: x64 65 | vcvars: vcvars64 66 | # - target: ARM 67 | # vcvars: vcvarsamd64_arm 68 | # - target: ARM64 69 | # vcvars: vcvarsamd64_arm64 70 | exclude: 71 | - toolset: ClangCL 72 | target: ARM 73 | - toolset: ClangCL 74 | target: ARM64 75 | runs-on: windows-latest 76 | steps: 77 | - uses: actions/checkout@v4 78 | - name: Configure 79 | run: cmake -B ${{github.workspace}}/build -DCMAKE_CXX_STANDARD=${{matrix.cxx_standard}} -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DLIBHAT_SHARED_C_LIB=ON -DLIBHAT_TESTING=${{startsWith(matrix.target, 'ARM') && 'OFF' || 'ON'}} -A ${{matrix.target}} -T ${{matrix.toolset}} 80 | 81 | - name: Build 82 | run: cmake --build ${{github.workspace}}/build -j 4 83 | 84 | - name: Test 85 | if: ${{!startsWith(matrix.target, 'ARM')}} 86 | working-directory: ${{github.workspace}}/build 87 | shell: cmd 88 | run: | 89 | call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\${{matrix.vcvars}}.bat" 90 | ctest -C ${{env.BUILD_TYPE}} -R libhat_test_.* 91 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.12) 2 | 3 | set(LIBHAT_VERSION_MAJOR 0) 4 | set(LIBHAT_VERSION_MINOR 5) 5 | set(LIBHAT_VERSION_PATCH 0) 6 | set(LIBHAT_VERSION ${LIBHAT_VERSION_MAJOR}.${LIBHAT_VERSION_MINOR}.${LIBHAT_VERSION_PATCH}) 7 | 8 | project(libhat 9 | VERSION ${LIBHAT_VERSION} 10 | DESCRIPTION "A high-performance, modern, C++20 library designed around game hacking" 11 | HOMEPAGE_URL "https://github.com/BasedInc/libhat" 12 | LANGUAGES C CXX 13 | ) 14 | 15 | include(CheckCXXCompilerFlag) 16 | 17 | option(LIBHAT_STATIC_C_LIB "Build a static version of the C library" OFF) 18 | option(LIBHAT_SHARED_C_LIB "Build a shared version of the C library" OFF) 19 | option(LIBHAT_INSTALL_TARGET "Creates install rules for the libhat target" ON) 20 | option(LIBHAT_DISABLE_SSE "Disables SSE scanning" OFF) 21 | option(LIBHAT_DISABLE_AVX512 "Disables AVX512 scanning" OFF) 22 | option(LIBHAT_TESTING "Enable tests" ${PROJECT_IS_TOP_LEVEL}) 23 | option(LIBHAT_TESTING_ASAN "Enable address sanitizer when testing" ON) 24 | option(LIBHAT_TESTING_SDE "Run tests using Intel Software Development Emulator" ON) 25 | option(LIBHAT_MODULE_TARGET "Create target for the module interface" OFF) 26 | option(LIBHAT_USE_STD_MODULE "Compile the module target using the std module" OFF) 27 | option(LIBHAT_EXAMPLES "Include example targets" ${PROJECT_IS_TOP_LEVEL}) 28 | 29 | option(LIBHAT_HINT_X86_64 "Enables support for the x86_64 scan hint, requires a small (less than 1KB) data table" ON) 30 | 31 | if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") 32 | # Just assume that the compiler accepts the required x86/x64 options if it'll accept -mmmx 33 | # CMake architecture detection SUCKS!!! 34 | check_cxx_compiler_flag("-mmmx" LIBHAT_COMPILER_X86_OPTIONS) 35 | 36 | if(LIBHAT_COMPILER_X86_OPTIONS) 37 | set_source_files_properties(src/arch/x86/SSE.cpp PROPERTIES COMPILE_FLAGS "-msse4.1") 38 | set_source_files_properties(src/arch/x86/AVX2.cpp PROPERTIES COMPILE_FLAGS "-mavx -mavx2 -mbmi") 39 | set_source_files_properties(src/arch/x86/AVX512.cpp PROPERTIES COMPILE_FLAGS "-mavx512f -mavx512bw -mbmi") 40 | set_source_files_properties(src/arch/x86/System.cpp PROPERTIES COMPILE_FLAGS "-mxsave") 41 | endif() 42 | endif() 43 | 44 | if(LIBHAT_TESTING AND LIBHAT_TESTING_ASAN) 45 | if(MSVC AND NOT CMAKE_CXX_COMPILER_ID MATCHES "Clang") 46 | add_compile_options($<$:/fsanitize=address>) 47 | endif() 48 | # TODO: Linux 49 | endif() 50 | 51 | set(LIBHAT_SRC 52 | src/Scanner.cpp 53 | src/System.cpp 54 | 55 | src/os/linux/MemoryProtector.cpp 56 | src/os/linux/Process.cpp 57 | 58 | src/os/unix/System.cpp 59 | 60 | src/os/win32/MemoryProtector.cpp 61 | src/os/win32/Process.cpp 62 | src/os/win32/Scanner.cpp 63 | src/os/win32/System.cpp 64 | 65 | src/arch/x86/SSE.cpp 66 | src/arch/x86/AVX2.cpp 67 | src/arch/x86/AVX512.cpp 68 | src/arch/x86/System.cpp 69 | 70 | src/arch/arm/System.cpp) 71 | 72 | add_library(libhat STATIC ${LIBHAT_SRC}) 73 | add_library(libhat::libhat ALIAS libhat) 74 | 75 | if(UNIX) 76 | set_target_properties(libhat PROPERTIES POSITION_INDEPENDENT_CODE ON) 77 | endif() 78 | 79 | target_compile_features(libhat PUBLIC cxx_std_20) 80 | 81 | if (MSVC) 82 | if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") 83 | target_compile_options(libhat PRIVATE /clang:-Wall /clang:-Wextra /clang:-Wconversion /clang:-Werror) 84 | else() 85 | target_compile_options(libhat PRIVATE /W3 /WX) 86 | endif() 87 | elseif(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") 88 | target_compile_options(libhat PRIVATE -Wall -Wextra -Wconversion -Werror 89 | # temp fix for macOS CI failing due to incorrect LIBHAT_COMPILER_X86_OPTIONS value 90 | -Wno-unused-command-line-argument 91 | ) 92 | endif() 93 | 94 | target_include_directories(libhat PUBLIC 95 | $ 96 | $ 97 | ) 98 | 99 | target_compile_definitions(libhat PUBLIC 100 | "$<$:LIBHAT_DISABLE_SSE>" 101 | "$<$:LIBHAT_DISABLE_AVX512>" 102 | "$<$:LIBHAT_HINT_X86_64>" 103 | ) 104 | 105 | if(LIBHAT_STATIC_C_LIB OR LIBHAT_SHARED_C_LIB) 106 | set(LIBHAT_C_SOURCES 107 | src/c/libhat.cpp 108 | ) 109 | 110 | if(LIBHAT_STATIC_C_LIB) 111 | add_library(libhat_c STATIC ${LIBHAT_C_SOURCES}) 112 | if(UNIX) 113 | set_target_properties(libhat_c PROPERTIES POSITION_INDEPENDENT_CODE ON) 114 | endif() 115 | else() 116 | add_library(libhat_c SHARED ${LIBHAT_C_SOURCES}) 117 | target_compile_definitions(libhat_c 118 | PRIVATE LIBHAT_BUILD_SHARED_LIB 119 | INTERFACE LIBHAT_USE_SHARED_LIB 120 | ) 121 | endif() 122 | 123 | add_library(libhat::libhat_c ALIAS libhat_c) 124 | target_link_libraries(libhat_c PRIVATE libhat) 125 | target_include_directories(libhat_c PUBLIC 126 | $ 127 | $ 128 | ) 129 | endif() 130 | 131 | if(LIBHAT_MODULE_TARGET) 132 | add_library(libhat_module) 133 | target_compile_features(libhat_module PUBLIC cxx_std_20) 134 | target_link_libraries(libhat_module PRIVATE libhat::libhat) 135 | target_sources(libhat_module PUBLIC 136 | FILE_SET CXX_MODULES 137 | TYPE CXX_MODULES 138 | FILES module/libhat.cppm 139 | ) 140 | target_compile_definitions(libhat_module PRIVATE 141 | "$<$:LIBHAT_USE_STD_MODULE>" 142 | ) 143 | add_library(libhat::libhat_module ALIAS libhat_module) 144 | 145 | if(LIBHAT_EXAMPLES) 146 | add_subdirectory(example/modules) 147 | endif() 148 | endif() 149 | 150 | if(LIBHAT_TESTING) 151 | include(CTest) 152 | enable_testing() 153 | add_subdirectory(test) 154 | endif() 155 | 156 | if(LIBHAT_INSTALL_TARGET) 157 | install(TARGETS libhat 158 | EXPORT libhat-targets 159 | RUNTIME DESTINATION "bin" 160 | ARCHIVE DESTINATION "lib" 161 | LIBRARY DESTINATION "lib" 162 | ) 163 | endif() 164 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022-2024 Brady Hahn 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # libhat 2 | A modern, high-performance library for C++20 designed around game hacking 3 | 4 | ## Feature overview 5 | - Windows x86/x64 support 6 | - Partial Linux and macOS support 7 | - Vectorized scanning for byte patterns 8 | - RAII memory protector 9 | - Convenience wrappers over OS APIs 10 | - Language bindings (C, C#, etc.) 11 | 12 | ## Versioning 13 | This project adheres to [semantic versioning](https://semver.org/spec/v2.0.0.html). Any declaration that 14 | is within a `detail` or `experimental` namespace is not considered part of the public API, and usage 15 | may break at any time without the MAJOR version number being incremented. 16 | 17 | ## Benchmarks 18 | The table below compares the single threaded throughput in bytes/s (real time) between 19 | libhat and [two other](test/benchmark/vendor) commonly used implementations for pattern 20 | scanning. The input buffers were randomly generated using a fixed seed, and the pattern 21 | scanned does not contain any match in the buffer. The benchmark was run on a system with 22 | an i7-9700K (which supports libhat's [AVX2](src/arch/x86/AVX2.cpp) scanner implementation). 23 | The full source code is available [here](test/benchmark/Compare.cpp). 24 | ``` 25 | --------------------------------------------------------------------------------------- 26 | Benchmark Time CPU Iterations bytes_per_second 27 | --------------------------------------------------------------------------------------- 28 | BM_Throughput_Libhat/4MiB 131578 ns 48967 ns 21379 29.6876Gi/s 29 | BM_Throughput_Libhat/16MiB 813977 ns 413524 ns 3514 19.1959Gi/s 30 | BM_Throughput_Libhat/128MiB 6910936 ns 3993486 ns 403 18.0873Gi/s 31 | BM_Throughput_Libhat/256MiB 13959379 ns 8121906 ns 202 17.9091Gi/s 32 | 33 | BM_Throughput_UC1/4MiB 4739731 ns 2776015 ns 591 843.93Mi/s 34 | BM_Throughput_UC1/16MiB 19011485 ns 10841837 ns 147 841.597Mi/s 35 | BM_Throughput_UC1/128MiB 152277511 ns 82465278 ns 18 840.571Mi/s 36 | BM_Throughput_UC1/256MiB 304964544 ns 180555556 ns 9 839.442Mi/s 37 | 38 | BM_Throughput_UC2/4MiB 9633499 ns 4617698 ns 291 415.218Mi/s 39 | BM_Throughput_UC2/16MiB 38507193 ns 22474315 ns 73 415.507Mi/s 40 | BM_Throughput_UC2/128MiB 307989100 ns 164930556 ns 9 415.599Mi/s 41 | BM_Throughput_UC2/256MiB 616449240 ns 331250000 ns 5 415.282Mi/s 42 | ``` 43 | 44 | ## Platforms 45 | 46 | Below is a summary of the support of libhat OS APIs on various platforms: 47 | 48 | | | Windows | Linux | macOS | 49 | |--------------------------------|:-------:|:-----:|:-----:| 50 | | `hat::get_system` | ✅ | ✅ | ✅ | 51 | | `hat::memory_protector` | ✅ | ✅ | | 52 | | `hp::get_process_module` | ✅ | ✅ | | 53 | | `hp::get_module` | ✅ | ✅ | | 54 | | `hp::module_at` | ✅ | | | 55 | | `hp::is_readable` | ✅ | ✅ | | 56 | | `hp::is_writable` | ✅ | ✅ | | 57 | | `hp::is_executable` | ✅ | ✅ | | 58 | | `hp::module::get_module_data` | ✅ | | | 59 | | `hp::module::get_section_data` | ✅ | | | 60 | | `hp::module::for_each_segment` | ✅ | ✅ | | 61 | 62 | ## Quick start 63 | ### Pattern scanning 64 | ```cpp 65 | #include 66 | 67 | // Parse a pattern's string representation to an array of bytes at compile time 68 | constexpr hat::fixed_signature pattern = hat::compile_signature<"48 8D 05 ? ? ? ? E8">(); 69 | 70 | // ...or parse it at runtime 71 | using parsed_t = hat::result; 72 | parsed_t runtime_pattern = hat::parse_signature("48 8D 05 ? ? ? ? E8"); 73 | 74 | // Scan for this pattern using your CPU's vectorization features 75 | auto begin = /* a contiguous iterator over std::byte */; 76 | auto end = /* ... */; 77 | hat::scan_result result = hat::find_pattern(begin, end, pattern); 78 | 79 | // Scan a section in the process's base module 80 | hat::scan_result result = hat::find_pattern(pattern, ".text"); 81 | 82 | // Or another module loaded into the process 83 | std::optional ntdll = hat::process::get_module("ntdll.dll"); 84 | assert(ntdll.has_value()); 85 | hat::scan_result result = hat::find_pattern(pattern, *ntdll, ".text"); 86 | 87 | // Get the address pointed at by the pattern 88 | const std::byte* address = result.get(); 89 | 90 | // Resolve an RIP relative address at a given offset 91 | // 92 | // | signature matches here 93 | // | | relative address located at +3 94 | // v v 95 | // 48 8D 05 BE 53 23 01 lea rax, [rip+0x12353be] 96 | // 97 | const std::byte* relative_address = result.rel(3); 98 | ``` 99 | 100 | ### Accessing offsets 101 | ```cpp 102 | #include 103 | 104 | // An example struct and it's member offsets 105 | struct S { 106 | uint32_t a{}; // 0x0 107 | uint32_t b{}; // 0x4 108 | uint32_t c{}; // 0x8 109 | uint32_t d{}; // 0xC 110 | }; 111 | 112 | S s; 113 | 114 | // Obtain a mutable reference to 's.b' via it's offset 115 | uint32_t& b = hat::member_at(&s, 0x4); 116 | 117 | // If the provided pointer is const, the returned reference is const 118 | const uint32_t& b = hat::member_at(&std::as_const(s), 0x4); 119 | ``` 120 | 121 | ### Writing to protected memory 122 | ```cpp 123 | #include 124 | 125 | uintptr_t* vftable = ...; // Pointer to a virtual function table in read-only data 126 | size_t target_func_index = ...; // Index to an interesting function 127 | 128 | // Use memory_protector to enable write protections 129 | hat::memory_protector prot{ 130 | (uintptr_t) &vftable[target_func_index], // a pointer to the target memory 131 | sizeof(uintptr_t), // the size of the memory block 132 | hat::protection::Read | hat::protection::Write // the new protection flags 133 | }; 134 | 135 | // Overwrite function table entry to redirect to a custom callback 136 | vftable[target_func_index] = (uintptr_t) my_callback; 137 | 138 | // On scope exit, original protections will be restored 139 | prot.~memory_protector(); // compiler generated 140 | 141 | ``` 142 | -------------------------------------------------------------------------------- /bindings/cs/.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from `dotnet new gitignore` 5 | 6 | # dotenv files 7 | .env 8 | 9 | # User-specific files 10 | *.rsuser 11 | *.suo 12 | *.user 13 | *.userosscache 14 | *.sln.docstates 15 | 16 | # User-specific files (MonoDevelop/Xamarin Studio) 17 | *.userprefs 18 | 19 | # Mono auto generated files 20 | mono_crash.* 21 | 22 | # Build results 23 | [Dd]ebug/ 24 | [Dd]ebugPublic/ 25 | [Rr]elease/ 26 | [Rr]eleases/ 27 | x64/ 28 | x86/ 29 | [Ww][Ii][Nn]32/ 30 | [Aa][Rr][Mm]/ 31 | [Aa][Rr][Mm]64/ 32 | bld/ 33 | [Bb]in/ 34 | [Oo]bj/ 35 | [Ll]og/ 36 | [Ll]ogs/ 37 | 38 | # Visual Studio 2015/2017 cache/options directory 39 | .vs/ 40 | # Uncomment if you have tasks that create the project's static files in wwwroot 41 | #wwwroot/ 42 | 43 | # Visual Studio 2017 auto generated files 44 | Generated\ Files/ 45 | 46 | # MSTest test Results 47 | [Tt]est[Rr]esult*/ 48 | [Bb]uild[Ll]og.* 49 | 50 | # NUnit 51 | *.VisualState.xml 52 | TestResult.xml 53 | nunit-*.xml 54 | 55 | # Build Results of an ATL Project 56 | [Dd]ebugPS/ 57 | [Rr]eleasePS/ 58 | dlldata.c 59 | 60 | # Benchmark Results 61 | BenchmarkDotNet.Artifacts/ 62 | 63 | # .NET 64 | project.lock.json 65 | project.fragment.lock.json 66 | artifacts/ 67 | 68 | # Tye 69 | .tye/ 70 | 71 | # ASP.NET Scaffolding 72 | ScaffoldingReadMe.txt 73 | 74 | # StyleCop 75 | StyleCopReport.xml 76 | 77 | # Files built by Visual Studio 78 | *_i.c 79 | *_p.c 80 | *_h.h 81 | *.ilk 82 | *.meta 83 | *.obj 84 | *.iobj 85 | *.pch 86 | *.pdb 87 | *.ipdb 88 | *.pgc 89 | *.pgd 90 | *.rsp 91 | *.sbr 92 | *.tlb 93 | *.tli 94 | *.tlh 95 | *.tmp 96 | *.tmp_proj 97 | *_wpftmp.csproj 98 | *.log 99 | *.tlog 100 | *.vspscc 101 | *.vssscc 102 | .builds 103 | *.pidb 104 | *.svclog 105 | *.scc 106 | 107 | # Chutzpah Test files 108 | _Chutzpah* 109 | 110 | # Visual C++ cache files 111 | ipch/ 112 | *.aps 113 | *.ncb 114 | *.opendb 115 | *.opensdf 116 | *.sdf 117 | *.cachefile 118 | *.VC.db 119 | *.VC.VC.opendb 120 | 121 | # Visual Studio profiler 122 | *.psess 123 | *.vsp 124 | *.vspx 125 | *.sap 126 | 127 | # Visual Studio Trace Files 128 | *.e2e 129 | 130 | # TFS 2012 Local Workspace 131 | $tf/ 132 | 133 | # Guidance Automation Toolkit 134 | *.gpState 135 | 136 | # ReSharper is a .NET coding add-in 137 | _ReSharper*/ 138 | *.[Rr]e[Ss]harper 139 | *.DotSettings.user 140 | 141 | # TeamCity is a build add-in 142 | _TeamCity* 143 | 144 | # DotCover is a Code Coverage Tool 145 | *.dotCover 146 | 147 | # AxoCover is a Code Coverage Tool 148 | .axoCover/* 149 | !.axoCover/settings.json 150 | 151 | # Coverlet is a free, cross platform Code Coverage Tool 152 | coverage*.json 153 | coverage*.xml 154 | coverage*.info 155 | 156 | # Visual Studio code coverage results 157 | *.coverage 158 | *.coveragexml 159 | 160 | # NCrunch 161 | _NCrunch_* 162 | .*crunch*.local.xml 163 | nCrunchTemp_* 164 | 165 | # MightyMoose 166 | *.mm.* 167 | AutoTest.Net/ 168 | 169 | # Web workbench (sass) 170 | .sass-cache/ 171 | 172 | # Installshield output folder 173 | [Ee]xpress/ 174 | 175 | # DocProject is a documentation generator add-in 176 | DocProject/buildhelp/ 177 | DocProject/Help/*.HxT 178 | DocProject/Help/*.HxC 179 | DocProject/Help/*.hhc 180 | DocProject/Help/*.hhk 181 | DocProject/Help/*.hhp 182 | DocProject/Help/Html2 183 | DocProject/Help/html 184 | 185 | # Click-Once directory 186 | publish/ 187 | 188 | # Publish Web Output 189 | *.[Pp]ublish.xml 190 | *.azurePubxml 191 | # Note: Comment the next line if you want to checkin your web deploy settings, 192 | # but database connection strings (with potential passwords) will be unencrypted 193 | *.pubxml 194 | *.publishproj 195 | 196 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 197 | # checkin your Azure Web App publish settings, but sensitive information contained 198 | # in these scripts will be unencrypted 199 | PublishScripts/ 200 | 201 | # NuGet Packages 202 | *.nupkg 203 | # NuGet Symbol Packages 204 | *.snupkg 205 | # The packages folder can be ignored because of Package Restore 206 | **/[Pp]ackages/* 207 | # except build/, which is used as an MSBuild target. 208 | !**/[Pp]ackages/build/ 209 | # Uncomment if necessary however generally it will be regenerated when needed 210 | #!**/[Pp]ackages/repositories.config 211 | # NuGet v3's project.json files produces more ignorable files 212 | *.nuget.props 213 | *.nuget.targets 214 | 215 | # Microsoft Azure Build Output 216 | csx/ 217 | *.build.csdef 218 | 219 | # Microsoft Azure Emulator 220 | ecf/ 221 | rcf/ 222 | 223 | # Windows Store app package directories and files 224 | AppPackages/ 225 | BundleArtifacts/ 226 | Package.StoreAssociation.xml 227 | _pkginfo.txt 228 | *.appx 229 | *.appxbundle 230 | *.appxupload 231 | 232 | # Visual Studio cache files 233 | # files ending in .cache can be ignored 234 | *.[Cc]ache 235 | # but keep track of directories ending in .cache 236 | !?*.[Cc]ache/ 237 | 238 | # Others 239 | ClientBin/ 240 | ~$* 241 | *~ 242 | *.dbmdl 243 | *.dbproj.schemaview 244 | *.jfm 245 | *.pfx 246 | *.publishsettings 247 | orleans.codegen.cs 248 | 249 | # Including strong name files can present a security risk 250 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 251 | #*.snk 252 | 253 | # Since there are multiple workflows, uncomment next line to ignore bower_components 254 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 255 | #bower_components/ 256 | 257 | # RIA/Silverlight projects 258 | Generated_Code/ 259 | 260 | # Backup & report files from converting an old project file 261 | # to a newer Visual Studio version. Backup files are not needed, 262 | # because we have git ;-) 263 | _UpgradeReport_Files/ 264 | Backup*/ 265 | UpgradeLog*.XML 266 | UpgradeLog*.htm 267 | ServiceFabricBackup/ 268 | *.rptproj.bak 269 | 270 | # SQL Server files 271 | *.mdf 272 | *.ldf 273 | *.ndf 274 | 275 | # Business Intelligence projects 276 | *.rdl.data 277 | *.bim.layout 278 | *.bim_*.settings 279 | *.rptproj.rsuser 280 | *- [Bb]ackup.rdl 281 | *- [Bb]ackup ([0-9]).rdl 282 | *- [Bb]ackup ([0-9][0-9]).rdl 283 | 284 | # Microsoft Fakes 285 | FakesAssemblies/ 286 | 287 | # GhostDoc plugin setting file 288 | *.GhostDoc.xml 289 | 290 | # Node.js Tools for Visual Studio 291 | .ntvs_analysis.dat 292 | node_modules/ 293 | 294 | # Visual Studio 6 build log 295 | *.plg 296 | 297 | # Visual Studio 6 workspace options file 298 | *.opt 299 | 300 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 301 | *.vbw 302 | 303 | # Visual Studio 6 auto-generated project file (contains which files were open etc.) 304 | *.vbp 305 | 306 | # Visual Studio 6 workspace and project file (working project files containing files to include in project) 307 | *.dsw 308 | *.dsp 309 | 310 | # Visual Studio 6 technical files 311 | *.ncb 312 | *.aps 313 | 314 | # Visual Studio LightSwitch build output 315 | **/*.HTMLClient/GeneratedArtifacts 316 | **/*.DesktopClient/GeneratedArtifacts 317 | **/*.DesktopClient/ModelManifest.xml 318 | **/*.Server/GeneratedArtifacts 319 | **/*.Server/ModelManifest.xml 320 | _Pvt_Extensions 321 | 322 | # Paket dependency manager 323 | .paket/paket.exe 324 | paket-files/ 325 | 326 | # FAKE - F# Make 327 | .fake/ 328 | 329 | # CodeRush personal settings 330 | .cr/personal 331 | 332 | # Python Tools for Visual Studio (PTVS) 333 | __pycache__/ 334 | *.pyc 335 | 336 | # Cake - Uncomment if you are using it 337 | # tools/** 338 | # !tools/packages.config 339 | 340 | # Tabs Studio 341 | *.tss 342 | 343 | # Telerik's JustMock configuration file 344 | *.jmconfig 345 | 346 | # BizTalk build output 347 | *.btp.cs 348 | *.btm.cs 349 | *.odx.cs 350 | *.xsd.cs 351 | 352 | # OpenCover UI analysis results 353 | OpenCover/ 354 | 355 | # Azure Stream Analytics local run output 356 | ASALocalRun/ 357 | 358 | # MSBuild Binary and Structured Log 359 | *.binlog 360 | 361 | # NVidia Nsight GPU debugger configuration file 362 | *.nvuser 363 | 364 | # MFractors (Xamarin productivity tool) working folder 365 | .mfractor/ 366 | 367 | # Local History for Visual Studio 368 | .localhistory/ 369 | 370 | # Visual Studio History (VSHistory) files 371 | .vshistory/ 372 | 373 | # BeatPulse healthcheck temp database 374 | healthchecksdb 375 | 376 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 377 | MigrationBackup/ 378 | 379 | # Ionide (cross platform F# VS Code tools) working folder 380 | .ionide/ 381 | 382 | # Fody - auto-generated XML schema 383 | FodyWeavers.xsd 384 | 385 | # VS Code files for those working on multiple tools 386 | .vscode/* 387 | !.vscode/settings.json 388 | !.vscode/tasks.json 389 | !.vscode/launch.json 390 | !.vscode/extensions.json 391 | *.code-workspace 392 | 393 | # Local History for Visual Studio Code 394 | .history/ 395 | 396 | # Windows Installer files from build outputs 397 | *.cab 398 | *.msi 399 | *.msix 400 | *.msm 401 | *.msp 402 | 403 | # JetBrains Rider 404 | *.sln.iml 405 | .idea 406 | 407 | ## 408 | ## Visual studio for Mac 409 | ## 410 | 411 | 412 | # globs 413 | Makefile.in 414 | *.userprefs 415 | *.usertasks 416 | config.make 417 | config.status 418 | aclocal.m4 419 | install-sh 420 | autom4te.cache/ 421 | *.tar.gz 422 | tarballs/ 423 | test-results/ 424 | 425 | # Mac bundle stuff 426 | *.dmg 427 | *.app 428 | 429 | # content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore 430 | # General 431 | .DS_Store 432 | .AppleDouble 433 | .LSOverride 434 | 435 | # Icon must end with two \r 436 | Icon 437 | 438 | 439 | # Thumbnails 440 | ._* 441 | 442 | # Files that might appear in the root of a volume 443 | .DocumentRevisions-V100 444 | .fseventsd 445 | .Spotlight-V100 446 | .TemporaryItems 447 | .Trashes 448 | .VolumeIcon.icns 449 | .com.apple.timemachine.donotpresent 450 | 451 | # Directories potentially created on remote AFP share 452 | .AppleDB 453 | .AppleDesktop 454 | Network Trash Folder 455 | Temporary Items 456 | .apdisk 457 | 458 | # content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore 459 | # Windows thumbnail cache files 460 | Thumbs.db 461 | ehthumbs.db 462 | ehthumbs_vista.db 463 | 464 | # Dump file 465 | *.stackdump 466 | 467 | # Folder config file 468 | [Dd]esktop.ini 469 | 470 | # Recycle Bin used on file shares 471 | $RECYCLE.BIN/ 472 | 473 | # Windows Installer files 474 | *.cab 475 | *.msi 476 | *.msix 477 | *.msm 478 | *.msp 479 | 480 | # Windows shortcuts 481 | *.lnk 482 | 483 | # Vim temporary swap files 484 | *.swp 485 | 486 | # JetBrains Rider 487 | .idea/ -------------------------------------------------------------------------------- /bindings/cs/libhat-sharp.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "libhat-sharp", "libhat-sharp\libhat-sharp.csproj", "{EF020BDB-543C-4EA3-9C95-A3668E2E5E65}" 4 | EndProject 5 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "libhat-tests", "libhat-tests\libhat-tests.csproj", "{1FDDD9F4-F557-45C9-96CD-7CDF111B8176}" 6 | EndProject 7 | Global 8 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 9 | Debug|Any CPU = Debug|Any CPU 10 | Release|Any CPU = Release|Any CPU 11 | EndGlobalSection 12 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 13 | {EF020BDB-543C-4EA3-9C95-A3668E2E5E65}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 14 | {EF020BDB-543C-4EA3-9C95-A3668E2E5E65}.Debug|Any CPU.Build.0 = Debug|Any CPU 15 | {EF020BDB-543C-4EA3-9C95-A3668E2E5E65}.Release|Any CPU.ActiveCfg = Release|Any CPU 16 | {EF020BDB-543C-4EA3-9C95-A3668E2E5E65}.Release|Any CPU.Build.0 = Release|Any CPU 17 | {1FDDD9F4-F557-45C9-96CD-7CDF111B8176}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 18 | {1FDDD9F4-F557-45C9-96CD-7CDF111B8176}.Debug|Any CPU.Build.0 = Debug|Any CPU 19 | {1FDDD9F4-F557-45C9-96CD-7CDF111B8176}.Release|Any CPU.ActiveCfg = Release|Any CPU 20 | {1FDDD9F4-F557-45C9-96CD-7CDF111B8176}.Release|Any CPU.Build.0 = Release|Any CPU 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /bindings/cs/libhat-sharp/Extensions/ArrayExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace Hat.Extensions; 2 | 3 | public static class ArrayExtensions 4 | { 5 | /// 6 | /// Creates a pattern from a nullable byte array, where null represents a wildcard. 7 | /// 8 | /// A pattern containing the bytes from this array. 9 | public static Pattern AsPattern(this IEnumerable bytes) 10 | { 11 | return new Pattern(bytes); 12 | } 13 | 14 | /// 15 | /// Creates a pattern from a byte array. 16 | /// 17 | /// A pattern containing the bytes from this array. 18 | public static Pattern AsPattern(this IEnumerable bytes) 19 | { 20 | return new Pattern(bytes); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /bindings/cs/libhat-sharp/Native/Functions.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | 3 | namespace Hat.Native; 4 | 5 | internal static unsafe partial class Functions 6 | { 7 | private const string LIBRARY_NAME = "libhat_c"; 8 | 9 | [LibraryImport(LIBRARY_NAME)] 10 | internal static partial Status libhat_parse_signature( 11 | [MarshalAs(UnmanagedType.LPStr)] string signatureStr, out Signature* signature); 12 | 13 | [LibraryImport(LIBRARY_NAME)] 14 | internal static partial Status libhat_create_signature(byte[] bytes, byte[] mask, uint size, out Signature* signature); 15 | 16 | [LibraryImport(LIBRARY_NAME)] 17 | internal static partial nint libhat_find_pattern(Signature* signature, nint buffer, uint size, ScanAlignment align); 18 | 19 | [LibraryImport(LIBRARY_NAME)] 20 | internal static partial nint libhat_find_pattern_mod(Signature* signature, nint module, 21 | [MarshalAs(UnmanagedType.LPStr)] string section, ScanAlignment align); 22 | 23 | [LibraryImport(LIBRARY_NAME)] 24 | internal static partial void libhat_free(nint data); 25 | } 26 | -------------------------------------------------------------------------------- /bindings/cs/libhat-sharp/Native/ScanAlignment.cs: -------------------------------------------------------------------------------- 1 | namespace Hat.Native; 2 | 3 | /// 4 | /// Represents the byte alignment of the result of a scan. 5 | /// 6 | public enum ScanAlignment 7 | { 8 | /// 9 | /// The result is not aligned. 10 | /// 11 | X1, 12 | 13 | /// 14 | /// The result is aligned to 16 bytes. 15 | /// 16 | X16 17 | } 18 | -------------------------------------------------------------------------------- /bindings/cs/libhat-sharp/Native/Signature.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | 3 | namespace Hat.Native; 4 | 5 | [StructLayout(LayoutKind.Sequential)] 6 | internal struct Signature 7 | { 8 | public nint Data; 9 | public nint Length; 10 | } 11 | -------------------------------------------------------------------------------- /bindings/cs/libhat-sharp/Native/Status.cs: -------------------------------------------------------------------------------- 1 | namespace Hat.Native; 2 | 3 | internal enum Status 4 | { 5 | Success, 6 | UnknownError, 7 | InvalidSig, 8 | EmptySig, 9 | NoBytesInSig 10 | } 11 | -------------------------------------------------------------------------------- /bindings/cs/libhat-sharp/Pattern.cs: -------------------------------------------------------------------------------- 1 | using Hat.Native; 2 | 3 | namespace Hat; 4 | 5 | /// 6 | /// Represents a pattern, which can be used to scan for a sequence of bytes. 7 | /// 8 | public unsafe class Pattern : IDisposable 9 | { 10 | internal Signature* Signature; 11 | 12 | /// 13 | /// Creates a pattern from its string representation, e.g. "48 8B 05 ? ? ? ? 48 8B 48 08". 14 | /// 15 | /// 16 | /// The string representation of the pattern, where ? represents a wildcard. 17 | /// 18 | /// The signature is empty, invalid, or only contains wildcards. 19 | /// An unknown error occurred. 20 | public Pattern(string signature) 21 | { 22 | Utils.CheckStatus(Functions.libhat_parse_signature(signature, out Signature)); 23 | } 24 | 25 | /// 26 | /// Creates a pattern from a byte array. 27 | /// 28 | /// An array of bytes. 29 | /// This function does not support wildcards. For a function that does, see . 30 | /// The signature is empty, invalid, or only contains wildcards. 31 | /// An unknown error occurred. 32 | public Pattern(IEnumerable signature) : this(signature.Select(b => (byte?) b)) {} 33 | 34 | /// 35 | /// Creates a pattern from a nullable byte array, where null represents a wildcard. 36 | /// 37 | /// An array of nullable bytes. 38 | /// The signature is empty, invalid, or only contains wildcards. 39 | /// An unknown error occurred. 40 | public Pattern(IEnumerable signature) 41 | { 42 | var arr = signature.ToArray(); 43 | var bytes = arr.Select(b => b.GetValueOrDefault(0)).ToArray(); 44 | var mask = arr.Select(b => (byte) (b.HasValue ? 0xFF : 0x00)).ToArray(); 45 | Utils.CheckStatus(Functions.libhat_create_signature(bytes, mask, (uint) arr.Length, out Signature)); 46 | } 47 | 48 | ~Pattern() 49 | { 50 | Dispose(); 51 | } 52 | 53 | /// 54 | /// Frees the memory allocated for the pattern. 55 | /// 56 | public void Dispose() 57 | { 58 | if (Signature == (Signature*)nint.Zero) return; 59 | 60 | Functions.libhat_free((nint) Signature); 61 | Signature = (Signature*)nint.Zero; 62 | GC.SuppressFinalize(this); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /bindings/cs/libhat-sharp/Scanner.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Runtime.CompilerServices; 3 | using Hat.Native; 4 | 5 | namespace Hat; 6 | 7 | /// 8 | /// Scans for a pattern in a process module or memory buffer. 9 | /// 10 | public unsafe class Scanner 11 | { 12 | private readonly string? _section; 13 | private readonly nint? _module; 14 | private readonly nint? _buffer; 15 | private readonly uint? _size; 16 | 17 | /// 18 | /// Creates a scanner for a process module. 19 | /// 20 | /// The base address of the module. 21 | /// The section to scan in. Defaults to .text. 22 | public Scanner(nint module, string section = ".text") 23 | { 24 | _section = section; 25 | _module = module; 26 | } 27 | 28 | /// 29 | /// Creates a scanner for a memory buffer. 30 | /// 31 | /// The start address of the buffer. 32 | /// The size of the buffer. 33 | public Scanner(nint buffer, uint size) 34 | { 35 | _buffer = buffer; 36 | _size = size; 37 | } 38 | 39 | /// 40 | /// Creates a scanner for a . 41 | /// 42 | /// The module to scan in. 43 | /// The section to scan in. Defaults to .text. 44 | public Scanner(ProcessModule module, string section = ".text") 45 | { 46 | _section = section; 47 | _module = module.BaseAddress; 48 | } 49 | 50 | /// 51 | /// Creates a scanner for a of bytes. 52 | /// 53 | /// The buffer to scan in. 54 | public Scanner(Span buffer) 55 | { 56 | _buffer = (nint)Unsafe.AsPointer(ref buffer[0]); 57 | _size = (uint)buffer.Length; 58 | } 59 | 60 | /// 61 | /// Scans for a pattern. 62 | /// 63 | /// The pattern to scan for. 64 | /// The byte alignment of the result. 65 | /// The address of the pattern if found, otherwise, 0. 66 | public nint FindPattern(Pattern pattern, ScanAlignment alignment = ScanAlignment.X1) 67 | { 68 | if (_buffer is not null && _size is not null) 69 | { 70 | return Functions.libhat_find_pattern(pattern.Signature, _buffer.Value, _size.Value, alignment); 71 | } 72 | 73 | if (_section is not null && _module is not null) 74 | { 75 | return Functions.libhat_find_pattern_mod(pattern.Signature, _module.Value, _section, alignment); 76 | } 77 | 78 | throw new InvalidOperationException("Scanner is not initialized."); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /bindings/cs/libhat-sharp/Utils.cs: -------------------------------------------------------------------------------- 1 | using Hat.Native; 2 | 3 | namespace Hat; 4 | 5 | internal static class Utils 6 | { 7 | internal static void CheckStatus(Status status) 8 | { 9 | if (status == Status.Success) return; 10 | 11 | throw status switch 12 | { 13 | Status.EmptySig => new ArgumentException("Signature is empty."), 14 | Status.InvalidSig => new ArgumentException("Signature is invalid."), 15 | Status.NoBytesInSig => new ArgumentException("Signature contains no bytes, or only contains wildcards."), 16 | Status.UnknownError => new InvalidOperationException("Unknown error occurred."), 17 | _ => new ArgumentOutOfRangeException() 18 | }; 19 | } 20 | } -------------------------------------------------------------------------------- /bindings/cs/libhat-sharp/libhat-sharp.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0 5 | Hat 6 | enable 7 | enable 8 | true 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /bindings/cs/libhat-tests/Program.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Runtime.InteropServices; 3 | using Hat; 4 | using Hat.Extensions; 5 | 6 | Console.WriteLine("libhat tests\n"); 7 | 8 | Console.WriteLine("scanning in memory buffer:"); 9 | var randomBytes = new byte[0x10000]; 10 | new Random().NextBytes(randomBytes); 11 | 12 | var pattern = randomBytes.AsSpan().Slice(0x1000, 0x10).ToArray().AsPattern(); 13 | var scanner = new Scanner(Marshal.UnsafeAddrOfPinnedArrayElement(randomBytes, 0), (uint)randomBytes.Length); 14 | var address = scanner.FindPattern(pattern); 15 | 16 | Console.WriteLine($"found pattern at 0x{address:X}"); 17 | 18 | Console.WriteLine("\nscanning in module:"); 19 | 20 | var modulePattern = new Pattern("48 89 5C 24 ? 48 89 6C 24 ? 48 89 74 24 ? 57 48 81 EC"); 21 | 22 | var module = Process.GetCurrentProcess().MainModule!; 23 | var moduleScanner = new Scanner(module); 24 | var moduleAddress = moduleScanner.FindPattern(modulePattern); 25 | 26 | Console.WriteLine($"found pattern at 0x{moduleAddress:X}"); -------------------------------------------------------------------------------- /bindings/cs/libhat-tests/libhat-tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net8.0 6 | Hat.Tests 7 | enable 8 | enable 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /bindings/java/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | !**/src/main/**/target/ 4 | !**/src/test/**/target/ 5 | 6 | ### IntelliJ IDEA ### 7 | .idea 8 | *.iws 9 | *.iml 10 | *.ipr 11 | 12 | ### Eclipse ### 13 | .apt_generated 14 | .classpath 15 | .factorypath 16 | .project 17 | .settings 18 | .springBeans 19 | .sts4-cache 20 | 21 | ### NetBeans ### 22 | /nbproject/private/ 23 | /nbbuild/ 24 | /dist/ 25 | /nbdist/ 26 | /.nb-gradle/ 27 | build/ 28 | !**/src/main/**/build/ 29 | !**/src/test/**/build/ 30 | 31 | ### VS Code ### 32 | .vscode/ 33 | 34 | ### Mac OS ### 35 | .DS_Store -------------------------------------------------------------------------------- /bindings/java/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.github.ZeroMemes 8 | libhat 9 | 1.0.0-SNAPSHOT 10 | 11 | 12 | 13 | net.java.dev.jna 14 | jna 15 | 5.14.0 16 | 17 | 18 | org.jetbrains 19 | annotations 20 | 24.1.0 21 | 22 | 23 | 24 | 25 | 8 26 | 8 27 | UTF-8 28 | 29 | 30 | -------------------------------------------------------------------------------- /bindings/java/src/main/java/me/zero/libhat/Hat.java: -------------------------------------------------------------------------------- 1 | package me.zero.libhat; 2 | 3 | import com.sun.jna.Native; 4 | import com.sun.jna.Pointer; 5 | import me.zero.libhat.jna.Libhat; 6 | import org.jetbrains.annotations.NotNull; 7 | import org.jetbrains.annotations.Nullable; 8 | 9 | import java.nio.ByteBuffer; 10 | import java.util.Objects; 11 | import java.util.Optional; 12 | import java.util.OptionalInt; 13 | 14 | /** 15 | * @author Brady 16 | */ 17 | public final class Hat { 18 | 19 | private Hat() {} 20 | 21 | public enum Status { 22 | SUCCESS, 23 | ERR_UNKNOWN, 24 | SIG_INVALID, 25 | SIG_EMPTY, 26 | SIG_NO_BYTE 27 | } 28 | 29 | /** 30 | * Creates a new {@link Signature} from the specified string representation of a byte pattern. For example: 31 | *
    32 | *
  • 48 8B 8D ? ? ? ? 48
  • 33 | *
  • E8 ? ? ? ? 88 86
  • 34 | *
35 | * The returned {@link Signature} is backed by a native heap allocation, and {@link Signature#close()} must be 36 | * called when the object is done being used, either explicitly or through a try-with-resources block. 37 | * 38 | * @param signature The string representation of a byte pattern 39 | * @return The created signature 40 | * @throws RuntimeException if an internal error occurred, indicated by {@code status != Status.SUCCESS} 41 | * @throws NullPointerException if any arguments are {@code null} 42 | */ 43 | public static @NotNull Signature parseSignature(@NotNull final String signature) { 44 | Objects.requireNonNull(signature); 45 | final Pointer[] handle = new Pointer[1]; 46 | final int status = Libhat.INSTANCE.libhat_parse_signature(signature, handle); 47 | if (status != 0) { 48 | throw new RuntimeException("libhat internal error " + Status.values()[status]); 49 | } 50 | return new Signature(handle[0]); 51 | } 52 | 53 | /** 54 | * Creates a new {@link Signature} that describes a pattern matching the specified {@code bytes} where the 55 | * corresponding {@code mask} value is non-zero. In other words, a mask value of {@code 0} indicates a wildcard. 56 | * The returned {@link Signature} is backed by a native heap allocation, and {@link Signature#close()} must be 57 | * called when the object is done being used, either explicitly or through a try-with-resources block. 58 | * 59 | * @param bytes The bytes to match 60 | * @param mask The mask applied to bytes 61 | * @return The created signature 62 | * @throws IllegalArgumentException if {@code bytes.length != mask.length} 63 | * @throws RuntimeException if an internal error occurred, indicated by {@code status != Status.SUCCESS} 64 | * @throws NullPointerException if any arguments are {@code null} 65 | */ 66 | public static @NotNull Signature createSignature(final byte @NotNull[] bytes, final byte @NotNull[] mask) { 67 | Objects.requireNonNull(bytes); 68 | Objects.requireNonNull(mask); 69 | if (bytes.length != mask.length) { 70 | throw new IllegalArgumentException("Mismatch between bytes.length and mask.length"); 71 | } 72 | final Pointer[] handle = new Pointer[1]; 73 | final int status = Libhat.INSTANCE.libhat_create_signature(bytes, mask, bytes.length, handle); 74 | if (status != 0) { 75 | throw new RuntimeException("libhat internal error " + Status.values()[status]); 76 | } 77 | return new Signature(handle[0]); 78 | } 79 | 80 | /** 81 | * Finds the byte pattern described by the given {@link Signature} in the specified {@code buffer}, searching the 82 | * range including {@link ByteBuffer#position()} and up to but excluding {@link ByteBuffer#limit()}. If a match is 83 | * found, an {@link OptionalInt} containing the absolute position into {@code buffer} is returned. The underlying 84 | * memory address of the returned result will not be aligned on any particular boundary. The specified 85 | * {@link ByteBuffer} must be a direct buffer. 86 | * 87 | * @param signature The pattern to match 88 | * @param buffer The buffer to search 89 | * @return The absolute position into {@code buffer} where a match was found, 90 | * or {@link OptionalInt#empty()} if there was no match 91 | * @throws IllegalArgumentException if the buffer is not direct or the signature has already been closed 92 | * @throws NullPointerException if any arguments are {@code null} 93 | */ 94 | public static OptionalInt findPattern(@NotNull final Signature signature, @NotNull final ByteBuffer buffer) { 95 | return findPattern(signature, buffer, ScanAlignment.X1); 96 | } 97 | 98 | /** 99 | * Finds the byte pattern described by the given {@link Signature} in the specified {@code buffer}, searching the 100 | * range including {@link ByteBuffer#position()} and up to but excluding {@link ByteBuffer#limit()}. If a match is 101 | * found, an {@link OptionalInt} containing the absolute position into {@code buffer} is returned. The underlying 102 | * memory address of the returned result will be byte aligned per the specified {@link ScanAlignment}. The specified 103 | * {@link ByteBuffer} must be a direct buffer. 104 | * 105 | * @param signature The pattern to match 106 | * @param buffer The buffer to search 107 | * @param alignment The result address alignment 108 | * @return The absolute position into {@code buffer} where a match was found, 109 | * or {@link OptionalInt#empty()} if there was no match 110 | * @throws IllegalArgumentException if the buffer is not direct 111 | * @throws NullPointerException if any arguments are {@code null} 112 | */ 113 | public static OptionalInt findPattern(@NotNull final Signature signature, @NotNull final ByteBuffer buffer, 114 | @NotNull final ScanAlignment alignment) { 115 | Objects.requireNonNull(signature); 116 | Objects.requireNonNull(buffer); 117 | Objects.requireNonNull(alignment); 118 | 119 | if (!buffer.isDirect()) { 120 | throw new IllegalArgumentException("Provided buffer must be direct"); 121 | } 122 | 123 | final long start = Pointer.nativeValue(Native.getDirectBufferPointer(buffer)) + buffer.position(); 124 | final int count = buffer.remaining(); 125 | 126 | final Pointer result = Libhat.INSTANCE.libhat_find_pattern( 127 | Objects.requireNonNull(signature.handle), 128 | new Pointer(start), 129 | count, 130 | alignment.ordinal() 131 | ); 132 | 133 | if (result == Pointer.NULL) { 134 | return OptionalInt.empty(); 135 | } 136 | return OptionalInt.of((int) (Pointer.nativeValue(result) - start) + buffer.position()); 137 | } 138 | 139 | /** 140 | * Finds the byte pattern described by the given {@link Signature} in the specified {@code section} of the 141 | * specified {@code module}. If a match is found, an {@link Optional} containing a Pointer to the match is returned. 142 | * The underlying memory address of the returned result will not be aligned on any particular boundary. 143 | * 144 | * @param signature The pattern to match 145 | * @param module The target module 146 | * @param section The section to search in the module 147 | * @return A pointer to the memory where a match was identified, or {@link Optional#empty()} if none was found. 148 | * @throws NullPointerException if any arguments are {@code null} 149 | */ 150 | public static Optional findPattern(@NotNull final Signature signature, @NotNull final ProcessModule module, 151 | @NotNull final String section) { 152 | return findPattern(signature, module, section, ScanAlignment.X1); 153 | } 154 | 155 | /** 156 | * Finds the byte pattern described by the given {@link Signature} in the specified {@code section} of the 157 | * specified {@code module}. If a match is found, an {@link Optional} containing a Pointer to the match is returned. 158 | * The underlying memory address of the returned result will be byte aligned per the specified {@link ScanAlignment}. 159 | * 160 | * @param signature The pattern to match 161 | * @param module The target module 162 | * @param section The section to search in the module 163 | * @param alignment The result address alignment 164 | * @return A pointer to the memory where a match was identified, or {@link Optional#empty()} if none was found. 165 | * @throws NullPointerException if any arguments are {@code null} 166 | */ 167 | public static Optional findPattern(@NotNull final Signature signature, @NotNull final ProcessModule module, 168 | @NotNull final String section, @NotNull final ScanAlignment alignment) { 169 | Objects.requireNonNull(signature); 170 | Objects.requireNonNull(module); 171 | Objects.requireNonNull(section); 172 | Objects.requireNonNull(alignment); 173 | 174 | final Pointer result = Libhat.INSTANCE.libhat_find_pattern_mod( 175 | Objects.requireNonNull(signature.handle), 176 | module.handle, 177 | section, 178 | alignment.ordinal() 179 | ); 180 | 181 | return Optional.ofNullable(result); 182 | } 183 | 184 | /** 185 | * Wrapper around {@link #parseSignature(String)} and {@link #findPattern(Signature, ByteBuffer)} 186 | * 187 | * @param signature A byte pattern string 188 | * @param buffer The buffer to search 189 | * @return The search result 190 | * @throws NullPointerException if any arguments are {@code null} 191 | */ 192 | public static OptionalInt findPattern(@NotNull final String signature, @NotNull final ByteBuffer buffer) { 193 | try (final Signature sig = parseSignature(signature)) { 194 | return findPattern(sig, buffer); 195 | } 196 | } 197 | 198 | /** 199 | * Wrapper around {@link #parseSignature(String)} and {@link #findPattern(Signature, ByteBuffer, ScanAlignment)} 200 | * 201 | * @param signature A byte pattern string 202 | * @param buffer The buffer to search 203 | * @param alignment The memory address alignment of the result 204 | * @return The search result 205 | * @throws NullPointerException if any arguments are {@code null} 206 | */ 207 | public static OptionalInt findPattern(@NotNull final String signature, @NotNull final ByteBuffer buffer, 208 | @NotNull final ScanAlignment alignment) { 209 | try (final Signature sig = parseSignature(signature)) { 210 | return findPattern(sig, buffer, alignment); 211 | } 212 | } 213 | 214 | /** 215 | * Returns the module for the executable used to create this process 216 | * 217 | * @return The module 218 | * @throws IllegalStateException If the process module could not be retrieved 219 | * @throws NullPointerException if any arguments are {@code null} 220 | */ 221 | public static @NotNull ProcessModule getProcessModule() { 222 | return getModule(null).orElseThrow(() -> new IllegalStateException("Process module was nullptr")); 223 | } 224 | 225 | /** 226 | * Returns an {@link Optional} containing a handle to the module with the specified name, if such a module exists, 227 | * otherwise the returned optional is empty. If the provided name is {@code null}, the module for the executable 228 | * used to create the current process is returned. 229 | * 230 | * @param module The module name, may be {@code null} 231 | * @return The module 232 | */ 233 | public static Optional getModule(@Nullable final String module) { 234 | final Pointer addr = Libhat.INSTANCE.libhat_get_module(module); 235 | if (addr == Pointer.NULL) { 236 | return Optional.empty(); 237 | } 238 | return Optional.of(new ProcessModule(addr)); 239 | } 240 | } 241 | -------------------------------------------------------------------------------- /bindings/java/src/main/java/me/zero/libhat/ProcessModule.java: -------------------------------------------------------------------------------- 1 | package me.zero.libhat; 2 | 3 | import com.sun.jna.Pointer; 4 | import org.jetbrains.annotations.NotNull; 5 | 6 | /** 7 | * @author Brady 8 | */ 9 | public final class ProcessModule { 10 | 11 | @NotNull 12 | Pointer handle; 13 | 14 | ProcessModule(@NotNull final Pointer handle) { 15 | this.handle = handle; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /bindings/java/src/main/java/me/zero/libhat/ScanAlignment.java: -------------------------------------------------------------------------------- 1 | package me.zero.libhat; 2 | 3 | /** 4 | * @author Brady 5 | */ 6 | public enum ScanAlignment { 7 | /** 8 | * No byte alignment 9 | */ 10 | X1, 11 | 12 | /** 13 | * 16 byte alignment 14 | */ 15 | X16 16 | } 17 | -------------------------------------------------------------------------------- /bindings/java/src/main/java/me/zero/libhat/Signature.java: -------------------------------------------------------------------------------- 1 | package me.zero.libhat; 2 | 3 | import com.sun.jna.Pointer; 4 | import me.zero.libhat.jna.Libhat; 5 | import org.jetbrains.annotations.NotNull; 6 | import org.jetbrains.annotations.Nullable; 7 | 8 | import java.util.Objects; 9 | 10 | /** 11 | * @author Brady 12 | */ 13 | public final class Signature implements AutoCloseable { 14 | 15 | @Nullable 16 | Pointer handle; 17 | 18 | Signature(@NotNull final Pointer handle) { 19 | this.handle = Objects.requireNonNull(handle); 20 | } 21 | 22 | @Override 23 | public void close() { 24 | if (this.handle != Pointer.NULL) { 25 | Libhat.INSTANCE.libhat_free(this.handle); 26 | this.handle = Pointer.NULL; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /bindings/java/src/main/java/me/zero/libhat/jna/Libhat.java: -------------------------------------------------------------------------------- 1 | package me.zero.libhat.jna; 2 | 3 | import com.sun.jna.*; 4 | 5 | /** 6 | * @author Brady 7 | */ 8 | public interface Libhat extends Library { 9 | 10 | Libhat INSTANCE = Native.load("libhat_c", Libhat.class); 11 | 12 | /* 13 | * libhat_status_t libhat_parse_signature( 14 | * const char* signatureStr, 15 | * signature_t** signatureOut 16 | * ); 17 | */ 18 | int libhat_parse_signature(String signatureStr, Pointer[] signatureOut); 19 | 20 | /* 21 | * libhat_status_t libhat_create_signature( 22 | * const char* bytes, 23 | * const char* mask, 24 | * size_t size, 25 | * signature_t** signatureOut 26 | * ); 27 | */ 28 | int libhat_create_signature(byte[] bytes, byte[] mask, int size, Pointer[] signatureOut); 29 | 30 | /* 31 | * const void* libhat_find_pattern( 32 | * const signature_t* signature, 33 | * const void* buffer, 34 | * size_t size, 35 | * scan_alignment align 36 | * ); 37 | */ 38 | Pointer libhat_find_pattern(Pointer signature, Pointer buffer, long size, int align); 39 | 40 | /* 41 | * const void* libhat_find_pattern_mod( 42 | * const signature_t* signature, 43 | * const void* module, 44 | * const char* section, 45 | * scan_alignment align 46 | * ); 47 | */ 48 | Pointer libhat_find_pattern_mod(Pointer signature, Pointer module, String section, int align); 49 | 50 | /* 51 | * const void* libhat_get_module(const char* name); 52 | */ 53 | Pointer libhat_get_module(String name); 54 | 55 | /* 56 | * void libhat_free(void* mem); 57 | */ 58 | void libhat_free(Pointer mem); 59 | } 60 | -------------------------------------------------------------------------------- /example/modules/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(libhat_module_test main.cpp) 2 | 3 | set_target_properties(libhat_module_test 4 | PROPERTIES CXX_SCAN_FOR_MODULES ON) 5 | 6 | target_link_libraries(libhat_module_test 7 | PRIVATE libhat::libhat_module) 8 | -------------------------------------------------------------------------------- /example/modules/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | import libhat; 6 | 7 | int main() { 8 | using namespace std::literals; 9 | 10 | constexpr auto str = "abcdefghijklmnopqrstuvwxyz0123456789"sv; 11 | const auto sig = hat::string_to_signature("xyz"sv); 12 | const auto buf = std::as_bytes(std::span{str}); 13 | 14 | const auto result = hat::find_pattern(buf.begin(), buf.end(), sig); 15 | if (result.has_result()) { 16 | const uintptr_t offset = std::bit_cast(result.get()) - std::bit_cast(buf.data()); 17 | printf("Found at %zu\n", offset); // Prints "Found at 23" 18 | } else { 19 | printf("Not found\n"); 20 | return 1; 21 | } 22 | return 0; 23 | } 24 | -------------------------------------------------------------------------------- /include/libhat.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "libhat/access.hpp" 4 | #include "libhat/compressed_pair.hpp" 5 | #include "libhat/concepts.hpp" 6 | #include "libhat/cow.hpp" 7 | #include "libhat/cstring_view.hpp" 8 | #include "libhat/defines.hpp" 9 | #include "libhat/fixed_string.hpp" 10 | #include "libhat/memory.hpp" 11 | #include "libhat/memory_protector.hpp" 12 | #include "libhat/process.hpp" 13 | #include "libhat/result.hpp" 14 | #include "libhat/scanner.hpp" 15 | #include "libhat/signature.hpp" 16 | #include "libhat/strconv.hpp" 17 | #include "libhat/string_literal.hpp" 18 | #include "libhat/system.hpp" 19 | #include "libhat/type_traits.hpp" 20 | #include "libhat/utility.hpp" 21 | 22 | #include "libhat/experimental.hpp" 23 | -------------------------------------------------------------------------------- /include/libhat/access.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifndef LIBHAT_MODULE 4 | #include 5 | #include 6 | #endif 7 | 8 | #include "export.hpp" 9 | #include "type_traits.hpp" 10 | 11 | LIBHAT_EXPORT namespace hat { 12 | 13 | template 14 | auto& member_at(Base* ptr, std::integral auto offset) noexcept { 15 | using Ret = hat::constness_as_t; 16 | return *reinterpret_cast(reinterpret_cast(ptr) + offset); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /include/libhat/c/libhat.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../platform.h" 4 | 5 | #if defined(LIBHAT_WINDOWS) 6 | #if defined(LIBHAT_BUILD_SHARED_LIB) 7 | #define LIBHAT_API __declspec(dllexport) 8 | #elif defined(LIBHAT_USE_SHARED_LIB) 9 | #define LIBHAT_API __declspec(dllimport) 10 | #else 11 | #define LIBHAT_API 12 | #endif 13 | #elif defined(LIBHAT_UNIX) 14 | #if defined(LIBHAT_BUILD_SHARED_LIB) 15 | #define LIBHAT_API __attribute__((visibility("default"))) 16 | #else 17 | #define LIBHAT_API 18 | #endif 19 | #else 20 | #define LIBHAT_API 21 | #endif 22 | 23 | #include 24 | 25 | #ifdef __cplusplus 26 | extern "C" { 27 | #endif 28 | 29 | typedef enum libhat_status_t { 30 | libhat_success, // The operation was successful 31 | libhat_err_unknown, 32 | libhat_err_sig_invalid, // The signature contained an invalid byte 33 | libhat_err_sig_empty, // The signature is empty 34 | libhat_err_sig_nobyte, // The signature did not contain a present byte, only wildcards 35 | } libhat_status_t; 36 | 37 | typedef enum scan_alignment { 38 | scan_alignment_x1, 39 | scan_alignment_x16, 40 | } scan_alignment_t; 41 | 42 | typedef struct signature { 43 | void* data; 44 | size_t count; 45 | } signature_t; 46 | 47 | LIBHAT_API libhat_status_t libhat_parse_signature( 48 | const char* signatureStr, 49 | signature_t** signatureOut 50 | ); 51 | 52 | LIBHAT_API libhat_status_t libhat_create_signature( 53 | const char* bytes, 54 | const char* mask, 55 | size_t size, 56 | signature_t** signatureOut 57 | ); 58 | 59 | LIBHAT_API const void* libhat_find_pattern( 60 | const signature_t* signature, 61 | const void* buffer, 62 | size_t size, 63 | scan_alignment_t align 64 | ); 65 | 66 | LIBHAT_API const void* libhat_find_pattern_mod( 67 | const signature_t* signature, 68 | const void* module, 69 | const char* section, 70 | scan_alignment_t align 71 | ); 72 | 73 | LIBHAT_API const void* libhat_get_module(const char* name); 74 | 75 | LIBHAT_API void libhat_free(void* mem); 76 | 77 | #ifdef __cplusplus 78 | } 79 | #endif 80 | -------------------------------------------------------------------------------- /include/libhat/compressed_pair.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifndef LIBHAT_MODULE 4 | #include 5 | #endif 6 | 7 | #include "defines.hpp" 8 | #include "type_traits.hpp" 9 | 10 | namespace hat::detail { 11 | 12 | template 13 | struct compressed_pair { 14 | using first_type = T1; 15 | using second_type = T2; 16 | 17 | private: 18 | static constexpr bool explicit_default_ctor = !hat::is_implicitly_default_constructible_v 19 | || !hat::is_implicitly_default_constructible_v; 20 | 21 | static constexpr bool explicit_copy_init = !std::is_convertible_v 22 | || !std::is_convertible_v; 23 | 24 | template 25 | static constexpr bool explicit_forward_init = !std::is_convertible_v 26 | || !std::is_convertible_v; 27 | 28 | public: 29 | LIBHAT_NO_UNIQUE_ADDRESS first_type first; 30 | LIBHAT_NO_UNIQUE_ADDRESS second_type second; 31 | 32 | constexpr explicit(explicit_default_ctor) compressed_pair() 33 | noexcept(std::is_nothrow_default_constructible_v && std::is_nothrow_default_constructible_v) 34 | requires(std::is_default_constructible_v && std::is_default_constructible_v) = default; 35 | 36 | constexpr explicit(explicit_copy_init) compressed_pair(const first_type& first, const second_type& second) 37 | requires(std::is_copy_constructible_v && std::is_copy_constructible_v) 38 | : first(first), second(second) {} 39 | 40 | template 41 | constexpr explicit(explicit_forward_init) compressed_pair(U1&& first, U2&& second) 42 | requires(std::is_constructible_v && std::is_constructible_v) 43 | : first(std::forward(first)), second(std::forward(second)) {} 44 | 45 | template 46 | constexpr compressed_pair(std::piecewise_construct_t, std::tuple firstArgs, std::tuple secondArgs) 47 | : compressed_pair(firstArgs, secondArgs, std::index_sequence_for{}, std::index_sequence_for{}) {} 48 | 49 | private: 50 | template 51 | constexpr compressed_pair(Tuple1& first, Tuple2& second, std::index_sequence, std::index_sequence) 52 | : first(std::get(std::move(first))...), second(std::get(std::move(second))...) {} 53 | }; 54 | } 55 | -------------------------------------------------------------------------------- /include/libhat/concepts.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifndef LIBHAT_MODULE 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #endif 10 | 11 | #include "export.hpp" 12 | 13 | LIBHAT_EXPORT namespace hat { 14 | 15 | template 16 | concept integer = std::integral && !std::is_same_v, bool>; 17 | } 18 | 19 | namespace hat::detail { 20 | 21 | template 22 | concept supplier = requires(Fn&& fn) { 23 | { fn() } -> std::same_as; 24 | }; 25 | 26 | template 27 | concept function = std::is_function_v; 28 | 29 | template 30 | concept reinterpret_as = sizeof(To) == sizeof(From) 31 | && std::is_trivially_copyable_v 32 | && std::is_trivially_copyable_v 33 | && std::is_trivially_constructible_v; 34 | 35 | template 36 | concept byte_input_iterator = std::input_iterator 37 | && std::forward_iterator 38 | && std::contiguous_iterator 39 | && std::same_as, std::byte>; 40 | 41 | template 42 | concept byte_input_range = std::ranges::input_range 43 | && std::ranges::contiguous_range 44 | && std::ranges::sized_range 45 | && std::same_as, std::byte>; 46 | 47 | template 48 | concept char_iterator = std::is_same_v, char>; 49 | 50 | template 51 | concept non_const = !std::is_const_v; 52 | } 53 | -------------------------------------------------------------------------------- /include/libhat/cow.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifndef LIBHAT_MODULE 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #endif 13 | 14 | #include "compressed_pair.hpp" 15 | #include "concepts.hpp" 16 | #include "cstring_view.hpp" 17 | #include "export.hpp" 18 | 19 | LIBHAT_EXPORT namespace hat { 20 | 21 | /// Tag type that tells `cow` that allocator support is not needed. 22 | struct no_allocator { 23 | no_allocator() = delete; 24 | }; 25 | } 26 | 27 | namespace hat::detail { 28 | template 29 | struct owned_type { 30 | using type = typename Traits::template owned_type; 31 | }; 32 | 33 | template 34 | struct owned_type { 35 | using type = typename Traits::owned_type; 36 | }; 37 | } 38 | 39 | LIBHAT_EXPORT namespace hat { 40 | 41 | struct in_place_viewed_t{}; 42 | struct in_place_owned_t{}; 43 | 44 | /// Construct a cow's viewed type in place. 45 | inline constexpr in_place_viewed_t in_place_viewed{}; 46 | /// Construct a cow's owned type in place. 47 | inline constexpr in_place_owned_t in_place_owned{}; 48 | 49 | template 50 | struct cow_traits; 51 | 52 | /** 53 | * A copy-on-write wrapper for dynamically-allocated containers. It's also useful for representing potentially owned data. 54 | * 55 | * @tparam T A type representing a non-owning view to the data. 56 | * @tparam Traits A traits type describing how to convert T to an owned container, among other things. 57 | * @tparam Allocator An allocator to use for the owned container. 58 | */ 59 | template, typename Allocator = typename Traits::default_allocator_type> 60 | requires std::is_trivially_copyable_v 61 | struct cow { 62 | // Required by STL 63 | using allocator_type = Allocator; 64 | 65 | using traits_type = Traits; 66 | 67 | static constexpr bool uses_allocator = !std::is_same_v; 68 | 69 | static_assert(uses_allocator || std::is_same_v, "The traits for this cow do not support allocators"); 70 | static_assert(!uses_allocator || !std::is_same_v, "The traits for this cow do not accept no_allocator"); 71 | 72 | using viewed_type = T; 73 | using owned_type = typename detail::owned_type::type; 74 | 75 | static_assert(!uses_allocator || std::uses_allocator_v, "Traits implementation is incorrect: the owned type does not support the given allocator."); 76 | 77 | private: 78 | static constexpr bool default_construct_alloc = !uses_allocator || std::is_default_constructible_v; 79 | 80 | using impl_t = std::variant< 81 | std::conditional_t, viewed_type>, 82 | owned_type>; 83 | 84 | constexpr viewed_type& viewed_unchecked() { 85 | if constexpr (uses_allocator) { 86 | return std::get<0>(this->impl).first; 87 | } else { 88 | return std::get<0>(this->impl); 89 | } 90 | } 91 | 92 | constexpr const viewed_type& viewed_unchecked() const { 93 | if constexpr (uses_allocator) { 94 | return std::get<0>(this->impl).first; 95 | } else { 96 | return std::get<0>(this->impl); 97 | } 98 | } 99 | 100 | public: 101 | // Default constructor 102 | constexpr cow() noexcept(std::is_nothrow_default_constructible_v) requires(std::is_default_constructible_v) = default; 103 | 104 | // Default construct with an allocator 105 | constexpr explicit cow(const allocator_type& alloc) 106 | noexcept(std::is_nothrow_default_constructible_v) 107 | requires(uses_allocator && std::is_default_constructible_v) 108 | : impl(std::in_place_index<0>, std::piecewise_construct, std::tuple{}, std::forward_as_tuple(alloc)) {} 109 | 110 | // Copy constructor for not uses_allocator 111 | constexpr cow(const cow&) noexcept(std::is_nothrow_copy_constructible_v) requires(!uses_allocator) = default; 112 | 113 | // Copy constructor for uses_allocator (per the standard, we need to do these methods to construct it) 114 | constexpr cow(const cow& other) noexcept(std::is_nothrow_copy_constructible_v) requires(uses_allocator) 115 | : impl(other.is_viewed() 116 | ? impl_t{ std::in_place_index<0>, other.viewed_unchecked(), std::allocator_traits::select_on_container_copy_construction(other.get_allocator()) } 117 | : other.impl) {} 118 | 119 | // Move constructor, behavior doesn't vary with uses_allocator 120 | constexpr cow(cow&&) noexcept(!std::is_nothrow_move_constructible_v) = default; 121 | 122 | // In-place constructor for viewed_type for not no_allocator, default initializes the allocator 123 | template 124 | requires (uses_allocator) 125 | constexpr explicit cow(in_place_viewed_t, Args&&... args) noexcept(std::is_nothrow_constructible_v) requires(default_construct_alloc) 126 | : impl{ std::in_place_index<0>, std::piecewise_construct, std::forward_as_tuple(args...), std::forward_as_tuple(allocator_type{}) } {} 127 | 128 | // In-place constructor for viewed_type for not_allocator, takes in an allocator 129 | template 130 | requires (uses_allocator) 131 | constexpr cow(std::allocator_arg_t, const allocator_type& alloc, in_place_viewed_t, Args&&... args) noexcept(std::is_nothrow_constructible_v) 132 | : impl{ std::in_place_index<0>, std::piecewise_construct, std::forward_as_tuple(args...), std::forward_as_tuple(alloc) } {} 133 | 134 | // In-place constructor for viewed_type with no_allocator 135 | template 136 | requires (!uses_allocator) 137 | constexpr explicit cow(in_place_viewed_t, Args&&... args) noexcept(std::is_nothrow_constructible_v) 138 | : impl{ std::in_place_index<0>, std::forward(args)... } {} 139 | 140 | // In-place constructor for owned_type, doesn't take an allocator 141 | template 142 | constexpr explicit cow(in_place_owned_t, Args&&... args) noexcept(std::is_nothrow_constructible_v) 143 | : impl{ std::in_place_type, std::forward(args)... } {} 144 | 145 | // In-place constructor for owned_type that takes an allocator. Needs some hacks to push the allocator into the underlying owned type. 146 | template 147 | constexpr explicit cow(std::allocator_arg_t, const allocator_type& alloc, in_place_owned_t, Args&&... args) noexcept(std::is_nothrow_constructible_v) 148 | : cow(owned_from_tuple_hack{}, std::uses_allocator_construction_args(alloc, std::forward(args)...)) {} 149 | 150 | // Construct with viewed_t, doesn't take an allocator 151 | constexpr explicit(false) cow(viewed_type view) noexcept requires(default_construct_alloc) : cow(in_place_viewed, view) {} 152 | 153 | // Construct with viewed_type and an allocator 154 | constexpr cow(viewed_type view, const allocator_type& alloc) noexcept requires(uses_allocator) : cow(std::allocator_arg, alloc, in_place_viewed, view) {} 155 | 156 | // Construct with owned_type, doesn't take an allocator 157 | constexpr explicit cow(owned_type owned) noexcept(std::is_nothrow_move_constructible_v) requires(default_construct_alloc) : cow(in_place_owned, std::move(owned)) {} 158 | 159 | // Construct with owned_type and an allocator 160 | constexpr cow(owned_type owned, const allocator_type& alloc) noexcept(std::is_nothrow_move_constructible_v) : cow(std::allocator_arg, alloc, in_place_owned, std::move(owned)) {} 161 | 162 | // Convert U to viewed_type and construct, doesn't take an allocator 163 | template U> 164 | requires traits_type::template allow_viewed_conversion 165 | constexpr explicit(false) cow(U&& val) requires(default_construct_alloc) : cow(viewed_type{std::forward(val)}) {} 166 | 167 | // Convert U to viewed_type and construct, takes an allocator 168 | template U> 169 | requires traits_type::template allow_viewed_conversion 170 | constexpr explicit(false) cow(U&& val, const allocator_type& alloc) : cow(viewed_type{std::forward(val)}, alloc) {} 171 | 172 | constexpr cow& operator=(const cow&) noexcept(std::is_nothrow_copy_assignable_v) requires(!uses_allocator) = default; 173 | constexpr cow& operator=(cow&&) noexcept(std::is_nothrow_move_assignable_v) requires(!uses_allocator) = default; 174 | 175 | constexpr cow& operator=(const cow& other) noexcept(std::is_nothrow_copy_assignable_v) requires(uses_allocator) { 176 | if constexpr (std::allocator_traits::propagate_on_container_copy_assignment::value) { 177 | this->impl = other.impl; 178 | } else { 179 | if (other.is_viewed()) { 180 | if (this->is_viewed()) { 181 | this->viewed_unchecked() = other.viewed_unchecked(); 182 | } else { 183 | this->impl.template emplace<0>(other.viewed_unchecked(), this->get_allocator()); 184 | } 185 | } else { 186 | this->impl.template emplace<1>(std::make_obj_using_allocator(this->get_allocator(), std::get(other.impl))); 187 | } 188 | } 189 | return *this; 190 | } 191 | 192 | constexpr cow& operator=(cow&& other) noexcept(std::is_nothrow_move_assignable_v) requires(uses_allocator) { 193 | if constexpr (std::allocator_traits::propagate_on_container_move_assignment::value) { 194 | this->impl = std::move(other.impl); 195 | } else { 196 | if (other.is_viewed()) { 197 | // NOTE: This relies on T being trivially_copyable (which is explicitly required right now) 198 | if (this->is_viewed()) { 199 | this->viewed_unchecked() = other.viewed_unchecked(); 200 | } else { 201 | this->impl.template emplace<0>(other.viewed_unchecked(), this->get_allocator()); 202 | } 203 | } else { 204 | this->impl.template emplace<1>(std::make_obj_using_allocator(this->get_allocator(), std::get(std::move(other.impl)))); 205 | } 206 | } 207 | return *this; 208 | } 209 | 210 | [[nodiscard]] constexpr bool is_viewed() const noexcept { 211 | return this->impl.index() == 0; 212 | } 213 | 214 | [[nodiscard]] constexpr bool is_owned() const noexcept { 215 | return std::holds_alternative(this->impl); 216 | } 217 | 218 | [[nodiscard]] constexpr viewed_type viewed() const noexcept(noexcept(traits_type::into_viewed(std::declval()))) { 219 | if (this->is_viewed()) { 220 | return this->viewed_unchecked(); 221 | } 222 | 223 | return traits_type::into_viewed(std::get(this->impl)); 224 | } 225 | 226 | [[nodiscard]] constexpr owned_type& owned() & noexcept(noexcept(traits_type::into_owned(std::declval()))) requires(!uses_allocator) { 227 | if (this->is_owned()) { 228 | return std::get(this->impl); 229 | } 230 | 231 | return this->impl.template emplace(traits_type::into_owned(this->viewed_unchecked())); 232 | } 233 | 234 | [[nodiscard]] constexpr owned_type& owned() & noexcept(noexcept(traits_type::into_owned(std::declval(), std::declval()))) requires(uses_allocator) { 235 | if (this->is_owned()) { 236 | return std::get(this->impl); 237 | } 238 | 239 | auto& [viewed, allocator] = std::get<0>(this->impl); 240 | return this->impl.template emplace(traits_type::into_owned(viewed, allocator)); 241 | } 242 | 243 | [[nodiscard]] constexpr owned_type&& owned() && noexcept(noexcept(this->owned())) { 244 | return std::move(this->owned()); 245 | } 246 | 247 | [[nodiscard]] constexpr allocator_type get_allocator() const noexcept(noexcept(traits_type::get_allocator(std::declval()))) requires(uses_allocator) { 248 | if (this->is_viewed()) { 249 | return std::get<0>(this->impl).second; 250 | } 251 | 252 | return traits_type::get_allocator(std::get(this->impl)); 253 | } 254 | 255 | constexpr void ensure_owned() { 256 | std::ignore = this->owned(); 257 | } 258 | 259 | template 260 | constexpr const viewed_type& emplace_viewed(Args&&... args) noexcept(std::is_nothrow_constructible_v) { 261 | if constexpr (uses_allocator) { 262 | // If we are holding an owned value, we need to take the allocator back out. 263 | return this->impl.template emplace<0>( 264 | std::piecewise_construct, 265 | std::forward_as_tuple(args...), 266 | std::forward_as_tuple(this->get_allocator())).first; 267 | 268 | // TODO: Since the allocator won't be in use anymore, it might make sense to allow to change the allocator 269 | } else { 270 | return this->impl.template emplace<0>(std::forward(args)...); 271 | } 272 | } 273 | 274 | template 275 | constexpr owned_type& emplace_owned(Args&&... args) noexcept(noexcept(this->impl.template emplace(std::forward(args)...))) { 276 | if constexpr (uses_allocator) { 277 | // I deeply apologize for this diabolical hack to preserve the current allocator. 278 | return [&](std::tuple tup) -> owned_type& { 279 | return [&](std::index_sequence) -> owned_type& { 280 | return this->impl.template emplace(std::get(std::move(tup))...); 281 | }(std::make_index_sequence{}); 282 | }(std::uses_allocator_construction_args(this->get_allocator(), std::forward(args)...)); 283 | 284 | // TODO: Maybe allow to change the allocator? 285 | } else { 286 | return this->impl.template emplace(std::forward(args)...); 287 | } 288 | } 289 | 290 | // ReSharper disable once CppNonExplicitConversionOperator 291 | constexpr explicit(false) operator viewed_type() const { // NOLINT(*-explicit-constructor) 292 | return this->viewed(); 293 | } 294 | 295 | private: 296 | impl_t impl; 297 | 298 | // We figure out the args needed to construct owned_type with an allocator using uses_allocator_construction_args, which returns a tuple. 299 | // Since it's a tuple, we need these two hacky constructors to get those arguments into the std::variant constructor in-place. 300 | 301 | struct owned_from_tuple_hack {}; 302 | 303 | template 304 | constexpr explicit cow(owned_from_tuple_hack, std::tuple args) : cow(std::move(args), std::make_index_sequence{}) {} 305 | 306 | template 307 | constexpr explicit cow(std::tuple args, std::index_sequence) : cow(in_place_owned, std::get(std::move(args))...) {} 308 | }; 309 | 310 | template 311 | struct cow_traits> { 312 | using default_allocator_type = std::allocator; 313 | 314 | using viewed_type = std::basic_string_view; 315 | 316 | template 317 | using owned_type = std::basic_string; 318 | 319 | template 320 | static constexpr bool allow_viewed_conversion = true; 321 | 322 | template 323 | static constexpr viewed_type into_viewed(const owned_type& owned) noexcept { 324 | return owned; 325 | } 326 | 327 | template 328 | static constexpr owned_type into_owned(const viewed_type& viewed, Alloc alloc) { 329 | return std::make_obj_using_allocator>(alloc, viewed); 330 | } 331 | 332 | template 333 | static constexpr Alloc get_allocator(const owned_type& owned) noexcept { 334 | return owned.get_allocator(); 335 | } 336 | }; 337 | 338 | template 339 | cow(std::basic_string_view str) -> cow>; 340 | 341 | template, typename Alloc = std::allocator> 342 | using basic_cow_string = cow, cow_traits>, Alloc>; 343 | 344 | using cow_string = basic_cow_string; 345 | using cow_u8string = basic_cow_string; 346 | using cow_u16string = basic_cow_string; 347 | using cow_u32string = basic_cow_string; 348 | using cow_wstring = basic_cow_string; 349 | 350 | 351 | template 352 | struct cow_traits> { 353 | using default_allocator_type = std::allocator; 354 | 355 | using viewed_type = basic_cstring_view; 356 | 357 | template 358 | using owned_type = std::basic_string; 359 | 360 | template 361 | static constexpr bool allow_viewed_conversion = true; 362 | 363 | template 364 | static constexpr viewed_type into_viewed(const owned_type& owned) noexcept { 365 | return owned; 366 | } 367 | 368 | template 369 | static constexpr owned_type into_owned(const viewed_type& viewed, Alloc alloc) { 370 | return std::make_obj_using_allocator>(alloc, viewed); 371 | } 372 | 373 | template 374 | static constexpr Alloc get_allocator(const owned_type& owned) noexcept { 375 | return owned.get_allocator(); 376 | } 377 | }; 378 | 379 | template 380 | cow(basic_cstring_view) -> cow>; 381 | 382 | template, typename Alloc = std::allocator> 383 | using basic_cow_cstring = cow, cow_traits>, Alloc>; 384 | 385 | using cow_cstring = basic_cow_cstring; 386 | using cow_u8cstring = basic_cow_cstring; 387 | using cow_u16cstring = basic_cow_cstring; 388 | using cow_u32cstring = basic_cow_cstring; 389 | using cow_wcstring = basic_cow_cstring; 390 | 391 | 392 | template 393 | struct cow_traits> { 394 | using default_allocator_type = std::allocator>; 395 | 396 | using viewed_type = std::span; 397 | 398 | template 399 | using owned_type = std::vector, Alloc>; 400 | 401 | template 402 | static constexpr bool allow_viewed_conversion = true; 403 | 404 | template 405 | static constexpr viewed_type into_viewed(const owned_type& owned) noexcept { 406 | return owned; 407 | } 408 | 409 | template 410 | static constexpr owned_type into_owned(const viewed_type& viewed, Alloc alloc) { 411 | return std::make_obj_using_allocator>(alloc, viewed.begin(), viewed.end()); 412 | } 413 | 414 | template 415 | static constexpr Alloc get_allocator(const owned_type& owned) noexcept { 416 | return owned.get_allocator(); 417 | } 418 | }; 419 | 420 | template>> 421 | using cow_span = cow>, cow_traits>>, Alloc>; 422 | 423 | template> 424 | using cow_writable_span = cow, cow_traits>, Alloc>; 425 | 426 | namespace pmr { 427 | template> 428 | using basic_cow_string = cow, cow_traits>, std::pmr::polymorphic_allocator>; 429 | 430 | using cow_string = basic_cow_string; 431 | using cow_u8string = basic_cow_string; 432 | using cow_u16string = basic_cow_string; 433 | using cow_u32string = basic_cow_string; 434 | using cow_wstring = basic_cow_string; 435 | 436 | 437 | template> 438 | using basic_cow_cstring = cow, cow_traits>, std::pmr::polymorphic_allocator>; 439 | 440 | using cow_cstring = basic_cow_cstring; 441 | using cow_u8cstring = basic_cow_cstring; 442 | using cow_u16cstring = basic_cow_cstring; 443 | using cow_u32cstring = basic_cow_cstring; 444 | using cow_wcstring = basic_cow_cstring; 445 | 446 | 447 | template 448 | using cow_span = cow_span>>; 449 | 450 | template 451 | using cow_writable_span = cow_writable_span>; 452 | } 453 | } 454 | -------------------------------------------------------------------------------- /include/libhat/cstring_view.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifndef LIBHAT_MODULE 4 | #include 5 | #include 6 | #include 7 | #endif 8 | 9 | #include "export.hpp" 10 | 11 | LIBHAT_EXPORT namespace hat { 12 | 13 | inline constexpr struct null_terminated_t{} null_terminated{}; 14 | 15 | /// Wrapper around std::basic_string_view with a null-terminator invariant 16 | template> 17 | class basic_cstring_view { 18 | using impl_t = std::basic_string_view; 19 | public: 20 | using traits_type = typename impl_t::traits_type; 21 | using value_type = typename impl_t::value_type; 22 | using pointer = typename impl_t::pointer; 23 | using const_pointer = typename impl_t::const_pointer; 24 | using reference = typename impl_t::reference; 25 | using const_reference = typename impl_t::const_reference; 26 | using const_iterator = typename impl_t::const_iterator; 27 | using iterator = typename impl_t::iterator; 28 | using const_reverse_iterator = typename impl_t::const_reverse_iterator; 29 | using reverse_iterator = typename impl_t::reverse_iterator; 30 | using size_type = typename impl_t::size_type; 31 | using difference_type = typename impl_t::difference_type; 32 | 33 | static constexpr auto npos = impl_t::npos; 34 | 35 | constexpr basic_cstring_view() noexcept = default; 36 | constexpr basic_cstring_view(const CharT* const c_str) noexcept(noexcept(impl_t{c_str})) : impl(c_str) {} 37 | constexpr basic_cstring_view(null_terminated_t, const CharT* const c_str, size_t size) 38 | noexcept(noexcept(impl_t{c_str, size})) : impl(c_str, size) {} 39 | constexpr basic_cstring_view(const std::basic_string& str) noexcept : impl(str) {} 40 | constexpr basic_cstring_view(const basic_cstring_view&) noexcept = default; 41 | basic_cstring_view(std::nullptr_t) = delete; 42 | 43 | constexpr basic_cstring_view& operator=(const basic_cstring_view&) noexcept = default; 44 | 45 | [[nodiscard]] constexpr const_iterator begin() const noexcept { 46 | return this->impl.begin(); 47 | } 48 | 49 | [[nodiscard]] constexpr const_iterator cbegin() const noexcept { 50 | return this->impl.cbegin(); 51 | } 52 | 53 | [[nodiscard]] constexpr const_iterator end() const noexcept { 54 | return this->impl.end(); 55 | } 56 | 57 | [[nodiscard]] constexpr const_iterator cend() const noexcept { 58 | return this->impl.cend(); 59 | } 60 | 61 | [[nodiscard]] constexpr const_iterator rbegin() const noexcept { 62 | return this->impl.rbegin(); 63 | } 64 | 65 | [[nodiscard]] constexpr const_iterator crbegin() const noexcept { 66 | return this->impl.crbegin(); 67 | } 68 | 69 | [[nodiscard]] constexpr const_iterator rend() const noexcept { 70 | return this->impl.rend(); 71 | } 72 | 73 | [[nodiscard]] constexpr const_iterator crend() const noexcept { 74 | return this->impl.crend(); 75 | } 76 | 77 | [[nodiscard]] constexpr const_reference operator[](const size_type pos) const { 78 | return this->impl[pos]; 79 | } 80 | 81 | [[nodiscard]] constexpr const_reference at(const size_type pos) const { 82 | return this->impl.at(pos); 83 | } 84 | 85 | [[nodiscard]] constexpr const_reference front() const { 86 | return this->impl.front(); 87 | } 88 | 89 | [[nodiscard]] constexpr const_reference back() const { 90 | return this->impl.back(); 91 | } 92 | 93 | [[nodiscard]] constexpr const_pointer data() const noexcept { 94 | return this->impl.data(); 95 | } 96 | 97 | [[nodiscard]] constexpr const_pointer c_str() const noexcept { 98 | return this->impl.data(); 99 | } 100 | 101 | [[nodiscard]] constexpr size_type size() const noexcept { 102 | return this->impl.size(); 103 | } 104 | 105 | [[nodiscard]] constexpr size_type length() const noexcept { 106 | return this->impl.length(); 107 | } 108 | 109 | [[nodiscard]] constexpr size_type max_size() const noexcept { 110 | return this->impl.max_size(); 111 | } 112 | 113 | [[nodiscard]] constexpr bool empty() const noexcept { 114 | return this->impl.empty(); 115 | } 116 | 117 | constexpr void remove_prefix(const size_type n) { 118 | this->impl.remove_prefix(n); 119 | } 120 | 121 | constexpr void swap(basic_cstring_view& v) noexcept { 122 | this->impl.swap(v.impl); 123 | } 124 | 125 | size_type copy(CharT* const dest, const size_type count, const size_type pos = 0) const { 126 | return this->impl.copy(dest, count, pos); 127 | } 128 | 129 | constexpr basic_cstring_view substr(const size_type pos = 0) const { 130 | return {this->impl.substr(pos)}; 131 | } 132 | 133 | constexpr impl_t substr(const size_type pos, const size_type count) const { 134 | return this->impl.substr(pos, count); 135 | } 136 | 137 | [[nodiscard]] constexpr int compare(const impl_t v) const noexcept { 138 | return this->impl.compare(v); 139 | } 140 | 141 | [[nodiscard]] constexpr int compare(const size_type pos1, const size_type count1, const impl_t v) const { 142 | return this->impl.compare(pos1, count1, v); 143 | } 144 | 145 | [[nodiscard]] constexpr int compare(const size_type pos1, const size_type count1, const impl_t v, 146 | const size_type pos2, const size_type count2) const { 147 | return this->impl.compare(pos1, count1, v, pos2, count2); 148 | } 149 | 150 | [[nodiscard]] constexpr int compare(const CharT* const s) const { 151 | return this->impl.compare(s); 152 | } 153 | 154 | [[nodiscard]] constexpr int compare(const size_type pos1, const size_type count1, const CharT* const s) const { 155 | return this->impl.compare(pos1, count1, s); 156 | } 157 | 158 | [[nodiscard]] constexpr int compare(const size_type pos1, const size_type count1, const CharT* const s, 159 | const size_type count2) const { 160 | return this->impl.compare(pos1, count1, s, count2); 161 | } 162 | 163 | [[nodiscard]] constexpr bool starts_with(const impl_t sv) const noexcept { 164 | return this->impl.starts_with(sv); 165 | } 166 | 167 | [[nodiscard]] constexpr bool starts_with(const CharT ch) const noexcept { 168 | return this->impl.starts_with(ch); 169 | } 170 | 171 | [[nodiscard]] constexpr bool starts_with(const CharT* const s) const { 172 | return this->impl.starts_with(s); 173 | } 174 | 175 | [[nodiscard]] constexpr bool ends_with(const impl_t sv) const noexcept { 176 | return this->impl.ends_with(sv); 177 | } 178 | 179 | [[nodiscard]] constexpr bool ends_with(const CharT ch) const noexcept { 180 | return this->impl.ends_with(ch); 181 | } 182 | 183 | [[nodiscard]] constexpr bool ends_with(const CharT* const s) const { 184 | return this->impl.ends_with(s); 185 | } 186 | 187 | [[nodiscard]] constexpr bool contains(const impl_t sv) const noexcept { 188 | return this->impl.contains(sv); 189 | } 190 | 191 | [[nodiscard]] constexpr bool contains(const CharT c) const noexcept { 192 | return this->impl.contains(c); 193 | } 194 | 195 | [[nodiscard]] constexpr bool contains(const CharT* const s) const { 196 | return this->impl.contains(s); 197 | } 198 | 199 | [[nodiscard]] constexpr size_type find(const impl_t v, const size_type pos = 0) const noexcept { 200 | return this->impl.find(v, pos); 201 | } 202 | 203 | [[nodiscard]] constexpr size_type find(const CharT ch, const size_type pos = 0) const noexcept { 204 | return this->impl.find(ch, pos); 205 | } 206 | 207 | [[nodiscard]] constexpr size_type find(const CharT* const s, const size_type pos, const size_type count) const { 208 | return this->impl.find(s, pos, count); 209 | } 210 | 211 | [[nodiscard]] constexpr size_type find(const CharT* const s, const size_type pos = 0) const { 212 | return this->impl.find(s, pos); 213 | } 214 | 215 | [[nodiscard]] constexpr size_type rfind(const impl_t v, const size_type pos = npos) const noexcept { 216 | return this->impl.rfind(v, pos); 217 | } 218 | 219 | [[nodiscard]] constexpr size_type rfind(const CharT ch, const size_type pos = npos) const noexcept { 220 | return this->impl.rfind(ch, pos); 221 | } 222 | 223 | [[nodiscard]] constexpr size_type rfind(const CharT* const s, const size_type pos, const size_type count) const { 224 | return this->impl.rfind(s, pos, count); 225 | } 226 | 227 | [[nodiscard]] constexpr size_type rfind(const CharT* const s, const size_type pos = npos) const { 228 | return this->impl.rfind(s, pos); 229 | } 230 | 231 | [[nodiscard]] constexpr size_type find_first_of(const impl_t v, const size_type pos = 0) const noexcept { 232 | return this->impl.find_first_of(v, pos); 233 | } 234 | 235 | [[nodiscard]] constexpr size_type find_first_of(const CharT ch, const size_type pos = 0) const noexcept { 236 | return this->impl.find_first_of(ch, pos); 237 | } 238 | 239 | [[nodiscard]] constexpr size_type find_first_of(const CharT* const s, const size_type pos, const size_type count) const { 240 | return this->impl.find_first_of(s, pos, count); 241 | } 242 | 243 | [[nodiscard]] constexpr size_type find_first_of(const CharT* const s, const size_type pos = 0) const { 244 | return this->impl.find_first_of(s, pos); 245 | } 246 | 247 | [[nodiscard]] constexpr size_type find_last_of(const impl_t v, const size_type pos = npos) const noexcept { 248 | return this->impl.find_last_of(v, pos); 249 | } 250 | 251 | [[nodiscard]] constexpr size_type find_last_of(const CharT ch, const size_type pos = npos) const noexcept { 252 | return this->impl.find_last_of(ch, pos); 253 | } 254 | 255 | [[nodiscard]] constexpr size_type find_last_of(const CharT* const s, const size_type pos, const size_type count) const { 256 | return this->impl.find_last_of(s, pos, count); 257 | } 258 | 259 | [[nodiscard]] constexpr size_type find_last_of(const CharT* const s, const size_type pos = npos) const { 260 | return this->impl.find_last_of(s, pos); 261 | } 262 | 263 | [[nodiscard]] constexpr size_type find_first_not_of(const impl_t v, const size_type pos = 0) const noexcept { 264 | return this->impl.find_first_not_of(v, pos); 265 | } 266 | 267 | [[nodiscard]] constexpr size_type find_first_not_of(const CharT ch, const size_type pos = 0) const noexcept { 268 | return this->impl.find_first_not_of(ch, pos); 269 | } 270 | 271 | [[nodiscard]] constexpr size_type find_first_not_of(const CharT* const s, const size_type pos, const size_type count) const { 272 | return this->impl.find_first_not_of(s, pos, count); 273 | } 274 | 275 | [[nodiscard]] constexpr size_type find_first_not_of(const CharT* const s, const size_type pos = 0) const { 276 | return this->impl.find_first_not_of(s, pos); 277 | } 278 | 279 | [[nodiscard]] constexpr size_type find_last_not_of(const impl_t v, const size_type pos = npos) const noexcept { 280 | return this->impl.find_last_not_of(v, pos); 281 | } 282 | 283 | [[nodiscard]] constexpr size_type find_last_not_of(const CharT ch, const size_type pos = npos) const noexcept { 284 | return this->impl.find_last_not_of(ch, pos); 285 | } 286 | 287 | [[nodiscard]] constexpr size_type find_last_not_of(const CharT* const s, const size_type pos, const size_type count) const { 288 | return this->impl.find_last_not_of(s, pos, count); 289 | } 290 | 291 | [[nodiscard]] constexpr size_type find_last_not_of(const CharT* const s, const size_type pos = npos) const { 292 | return this->impl.find_last_not_of(s, pos); 293 | } 294 | 295 | constexpr operator impl_t() const noexcept { 296 | return this->impl; 297 | } 298 | 299 | private: 300 | constexpr basic_cstring_view(impl_t impl) noexcept : impl(impl) {} 301 | impl_t impl{}; 302 | }; 303 | 304 | using cstring_view = basic_cstring_view; 305 | using wcstring_view = basic_cstring_view; 306 | #ifdef __cpp_lib_char8_t 307 | using u8cstring_view = basic_cstring_view; 308 | #endif 309 | using u16cstring_view = basic_cstring_view; 310 | using u32cstring_view = basic_cstring_view; 311 | } 312 | 313 | namespace hat::detail { 314 | 315 | template 316 | struct comparison_category { 317 | using type = std::weak_ordering; 318 | }; 319 | 320 | template requires requires { typename Traits::comparison_category; } 321 | struct comparison_category { 322 | using type = typename Traits::comparison_category; 323 | }; 324 | 325 | template 326 | using comparison_category_t = typename comparison_category::type; 327 | } 328 | 329 | LIBHAT_EXPORT template 330 | constexpr hat::detail::comparison_category_t operator<=>( 331 | hat::basic_cstring_view lhs, 332 | std::type_identity_t> rhs) noexcept 333 | { 334 | return std::basic_string_view{lhs} <=> rhs; 335 | } 336 | 337 | LIBHAT_EXPORT namespace hat::inline literals::inline cstring_view_literals { 338 | 339 | [[nodiscard]] constexpr cstring_view operator""_csv(const char* str, size_t size) { 340 | return {null_terminated, str, size}; 341 | } 342 | 343 | [[nodiscard]] constexpr wcstring_view operator""_csv(const wchar_t* str, size_t size) { 344 | return {null_terminated, str, size}; 345 | } 346 | 347 | #ifdef __cpp_lib_char8_t 348 | [[nodiscard]] constexpr u8cstring_view operator""_csv(const char8_t* str, size_t size) { 349 | return {null_terminated, str, size}; 350 | } 351 | #endif 352 | 353 | [[nodiscard]] constexpr u16cstring_view operator""_csv(const char16_t* str, size_t size) { 354 | return {null_terminated, str, size}; 355 | } 356 | 357 | [[nodiscard]] constexpr u32cstring_view operator""_csv(const char32_t* str, size_t size) { 358 | return {null_terminated, str, size}; 359 | } 360 | } 361 | 362 | template<> struct std::hash : std::hash {}; 363 | template<> struct std::hash : std::hash {}; 364 | 365 | #ifdef __cpp_lib_char8_t 366 | template<> struct std::hash : std::hash {}; 367 | #endif 368 | 369 | template<> struct std::hash : std::hash {}; 370 | template<> struct std::hash : std::hash {}; 371 | 372 | template 373 | inline constexpr bool std::ranges::enable_borrowed_range> = true; 374 | 375 | template 376 | inline constexpr bool std::ranges::enable_view> = true; 377 | -------------------------------------------------------------------------------- /include/libhat/defines.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "platform.h" 4 | 5 | #ifdef _MSC_VER 6 | #ifndef LIBHAT_MODULE 7 | #include 8 | #endif 9 | #define LIBHAT_RETURN_ADDRESS() _ReturnAddress() 10 | #else 11 | #define LIBHAT_RETURN_ADDRESS() __builtin_extract_return_addr(__builtin_return_address(0)) 12 | #endif 13 | 14 | #if __cpp_if_consteval >= 202106L 15 | #define LIBHAT_IF_CONSTEVAL consteval 16 | #else 17 | #ifndef LIBHAT_MODULE 18 | #include 19 | #endif 20 | #define LIBHAT_IF_CONSTEVAL (std::is_constant_evaluated()) 21 | #endif 22 | 23 | #if __has_cpp_attribute(likely) 24 | #define LIBHAT_LIKELY [[likely]] 25 | #else 26 | #define LIBHAT_LIKELY 27 | #endif 28 | 29 | #if __has_cpp_attribute(unlikely) 30 | #define LIBHAT_UNLIKELY [[unlikely]] 31 | #else 32 | #define LIBHAT_UNLIKELY 33 | #endif 34 | 35 | #if __cpp_lib_unreachable >= 202202L 36 | #ifndef LIBHAT_MODULE 37 | #include 38 | #endif 39 | #define LIBHAT_UNREACHABLE() std::unreachable() 40 | #elif defined(__GNUC__) || defined(__clang__) 41 | #define LIBHAT_UNREACHABLE() __builtin_unreachable() 42 | #elif defined(_MSC_VER) 43 | #define LIBHAT_UNREACHABLE() __assume(false) 44 | #else 45 | #ifndef LIBHAT_MODULE 46 | #include 47 | #endif 48 | namespace hat::detail { 49 | [[noreturn]] inline void unreachable_impl() noexcept { 50 | std::abort(); 51 | } 52 | } 53 | #define LIBHAT_UNREACHABLE() hat::detail::unreachable_impl() 54 | #endif 55 | 56 | #if __has_cpp_attribute(assume) 57 | #define LIBHAT_ASSUME(...) [[assume(__VA_ARGS__)]] 58 | #elif defined(__clang__) 59 | #define LIBHAT_ASSUME(...) __builtin_assume(__VA_ARGS__) 60 | #elif defined(_MSC_VER) 61 | #define LIBHAT_ASSUME(...) __assume(__VA_ARGS__) 62 | #else 63 | #define LIBHAT_ASSUME(...) \ 64 | do { \ 65 | if (!(__VA_ARGS__)) { \ 66 | LIBHAT_UNREACHABLE(); \ 67 | } \ 68 | } while (0) 69 | #endif 70 | 71 | #if defined(__GNUC__) || defined(__clang__) 72 | #define LIBHAT_FORCEINLINE inline __attribute__((always_inline)) 73 | #elif defined(_MSC_VER) 74 | #define LIBHAT_FORCEINLINE __forceinline 75 | #else 76 | #define LIBHAT_FORCEINLINE inline 77 | #endif 78 | 79 | #if __has_cpp_attribute(no_unique_address) 80 | #define LIBHAT_NO_UNIQUE_ADDRESS [[no_unique_address]] 81 | #elif __has_cpp_attribute(msvc::no_unique_address) 82 | #define LIBHAT_NO_UNIQUE_ADDRESS [[msvc::no_unique_address]] 83 | #else 84 | #define LIBHAT_NO_UNIQUE_ADDRESS 85 | #endif 86 | -------------------------------------------------------------------------------- /include/libhat/experimental.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "experimental/call_wrapper.hpp" 4 | -------------------------------------------------------------------------------- /include/libhat/experimental/call_wrapper.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifndef LIBHAT_MODULE 4 | #include 5 | #include 6 | #endif 7 | 8 | #include "../concepts.hpp" 9 | #include "../defines.hpp" 10 | #include "../export.hpp" 11 | 12 | namespace hat::detail { 13 | 14 | template 15 | struct wrapper_util; 16 | 17 | template 18 | struct wrapper_util { 19 | using function_type = Ret(Args...); 20 | 21 | template Capture> 22 | struct context { 23 | Ret operator()(Args&&... args) const { 24 | return original(std::forward(args)...); 25 | } 26 | 27 | Ret call() const { 28 | return captured(); 29 | } 30 | 31 | void* return_address() const { 32 | return ret_addr; 33 | } 34 | 35 | context(function_type* original, Capture&& captured, void* ret_addr) 36 | : original(original), captured(captured), ret_addr(ret_addr) {} 37 | 38 | context(const context&) = delete; 39 | context(context&&) = delete; 40 | private: 41 | function_type* original; 42 | Capture captured; 43 | void* ret_addr; 44 | }; 45 | 46 | template 47 | context(function_type*, T&&, void*) -> context; 48 | 49 | template 50 | struct caller { 51 | using wrapper_type = decltype(wrapper); 52 | 53 | static Ret invoke(Args... args) { 54 | const auto original = get_original(); 55 | const context ctx{ 56 | original, 57 | [&]() { return original(std::forward(args)...); }, 58 | LIBHAT_RETURN_ADDRESS() 59 | }; 60 | using ctx_t = decltype(ctx); 61 | 62 | constexpr bool invoke_with_args = std::is_invocable_v; 63 | 64 | if constexpr (invoke_with_args) { 65 | // If the wrapper wants to receive the function arguments 66 | static_assert(std::is_same_v>); 67 | return wrapper(ctx, std::forward(args)...); 68 | } else { 69 | // Otherwise don't bother 70 | static_assert(std::is_same_v>); 71 | return wrapper(ctx); 72 | } 73 | }; 74 | }; 75 | 76 | template Provider> 77 | struct provider_storage { 78 | static inline function_type* get_original() { 79 | return (*std::launder(reinterpret_cast(instance)))(); 80 | } 81 | 82 | static inline void set_provider(Provider&& provider) { 83 | new (instance) Provider{std::move(provider)}; 84 | } 85 | private: 86 | alignas(Provider) static inline std::byte instance[sizeof(Provider)]{}; 87 | }; 88 | }; 89 | } 90 | 91 | LIBHAT_EXPORT namespace hat::experimental { 92 | 93 | template< 94 | detail::function Fn, 95 | std::default_initializable Wrapper, 96 | detail::supplier Wrapped 97 | > requires std::default_initializable 98 | constexpr auto make_dynamic_wrapper(Wrapper&&, Wrapped&&) -> Fn* { 99 | return detail::wrapper_util::template caller::invoke; 100 | } 101 | 102 | template< 103 | detail::function Fn, 104 | std::default_initializable Wrapper, 105 | detail::supplier Wrapped 106 | > requires (!std::default_initializable) 107 | auto make_dynamic_wrapper(Wrapper&&, Wrapped&& wrapped) -> Fn* { 108 | using util_t = detail::wrapper_util; 109 | const auto provider = [wrapped = std::move(wrapped)]() -> Fn* { 110 | return wrapped(); 111 | }; 112 | 113 | using storage = typename util_t::template provider_storage; 114 | storage::set_provider(std::move(provider)); 115 | 116 | return util_t::template caller::invoke; 117 | } 118 | 119 | template> 120 | constexpr auto make_static_wrapper(std::default_initializable auto&& wrapper) { 121 | return make_dynamic_wrapper(std::move(wrapper), []() { return wrapped; }); 122 | } 123 | 124 | template 125 | auto make_static_wrapper(std::default_initializable auto&& wrapper, Fn* wrapped) { 126 | return make_dynamic_wrapper(std::move(wrapper), [=]() { return wrapped; }); 127 | } 128 | 129 | template 130 | auto make_static_wrapper(std::default_initializable auto&& wrapper, Ret(*wrapped)(Args...)) { 131 | return make_static_wrapper(std::move(wrapper), wrapped); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /include/libhat/export.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifdef LIBHAT_MODULE 4 | #define LIBHAT_EXPORT export 5 | #else 6 | #define LIBHAT_EXPORT 7 | #endif 8 | -------------------------------------------------------------------------------- /include/libhat/fixed_string.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifndef LIBHAT_MODULE 4 | #include 5 | #include 6 | #include 7 | #include 8 | #endif 9 | 10 | #include "export.hpp" 11 | 12 | LIBHAT_EXPORT namespace hat { 13 | 14 | template typename Derived> 15 | struct basic_fixed_string { 16 | using value_type = Char; 17 | using pointer = Char*; 18 | using const_pointer = const Char*; 19 | using reference = Char&; 20 | using const_reference = const Char&; 21 | using iterator = pointer; 22 | using const_iterator = const_pointer; 23 | 24 | static constexpr auto npos = static_cast(-1); 25 | 26 | constexpr basic_fixed_string(std::basic_string_view str) { 27 | std::copy_n(str.data(), N, value); 28 | } 29 | 30 | constexpr basic_fixed_string(const Char (&str)[N + 1]) { 31 | std::copy_n(str, N, value); 32 | } 33 | 34 | [[nodiscard]] constexpr iterator begin() noexcept { 35 | return this->c_str(); 36 | } 37 | 38 | [[nodiscard]] constexpr iterator end() noexcept { 39 | return this->begin() + this->size(); 40 | } 41 | 42 | [[nodiscard]] constexpr const_iterator begin() const noexcept { 43 | return this->c_str(); 44 | } 45 | 46 | [[nodiscard]] constexpr const_iterator end() const noexcept { 47 | return this->begin() + this->size(); 48 | } 49 | 50 | [[nodiscard]] constexpr const_iterator cbegin() const noexcept { 51 | return this->begin(); 52 | } 53 | 54 | [[nodiscard]] constexpr const_iterator cend() const noexcept { 55 | return this->end(); 56 | } 57 | 58 | [[nodiscard]] constexpr reference operator[](size_t pos) noexcept { 59 | return this->value[pos]; 60 | } 61 | 62 | [[nodiscard]] constexpr reference at(size_t pos) noexcept { 63 | return this->value[pos]; 64 | } 65 | 66 | [[nodiscard]] constexpr const_reference operator[](size_t pos) const noexcept { 67 | return this->value[pos]; 68 | } 69 | 70 | [[nodiscard]] constexpr const_reference at(size_t pos) const noexcept { 71 | return this->value[pos]; 72 | } 73 | 74 | [[nodiscard]] constexpr reference front() noexcept { 75 | return this->value[0]; 76 | } 77 | 78 | [[nodiscard]] constexpr reference back() noexcept { 79 | return this->value[size() - 1]; 80 | } 81 | 82 | [[nodiscard]] constexpr const_reference front() const noexcept { 83 | return this->value[0]; 84 | } 85 | 86 | [[nodiscard]] constexpr const_reference back() const noexcept { 87 | return this->value[size() - 1]; 88 | } 89 | 90 | [[nodiscard]] constexpr pointer c_str() noexcept { 91 | return static_cast(this->value); 92 | } 93 | 94 | [[nodiscard]] constexpr const_pointer c_str() const noexcept { 95 | return static_cast(this->value); 96 | } 97 | 98 | [[nodiscard]] constexpr pointer data() noexcept { 99 | return this->c_str(); 100 | } 101 | 102 | [[nodiscard]] constexpr const_pointer data() const noexcept { 103 | return this->c_str(); 104 | } 105 | 106 | [[nodiscard]] constexpr size_t size() const noexcept { 107 | return N; 108 | } 109 | 110 | [[nodiscard]] constexpr bool empty() const noexcept { 111 | return this->size() == 0; 112 | } 113 | 114 | template< 115 | size_t Pos = 0, 116 | size_t Count = npos, 117 | size_t M = (Count == npos ? N - Pos : Count) 118 | > 119 | [[nodiscard]] constexpr auto substr() const -> Derived { 120 | static_assert(Pos + M <= N); 121 | Char buf[M + 1]{}; 122 | std::copy_n(this->value + Pos, M, buf); 123 | return buf; 124 | } 125 | 126 | template 127 | constexpr auto operator+(const basic_fixed_string& str) const -> Derived { 128 | Char buf[K + 1]{}; 129 | std::copy_n(this->value, this->size(), buf); 130 | std::copy_n(str.value, str.size(), buf + this->size()); 131 | return buf; 132 | } 133 | 134 | template 135 | constexpr auto operator+(const Char (&str)[M]) const { 136 | return *this + Derived{str}; 137 | } 138 | 139 | template 140 | constexpr bool operator==(const basic_fixed_string& str) const { 141 | return std::equal(this->begin(), this->end(), str.begin(), str.end()); 142 | } 143 | 144 | constexpr bool operator==(const std::basic_string& str) const { 145 | return std::equal(this->begin(), this->end(), str.begin(), str.end()); 146 | } 147 | 148 | constexpr bool operator==(std::basic_string_view str) const { 149 | return std::equal(this->begin(), this->end(), str.begin(), str.end()); 150 | } 151 | 152 | constexpr bool operator==(const Char* str) const { 153 | return std::equal(this->begin(), this->end(), str, str + std::char_traits::length(str)); 154 | } 155 | 156 | constexpr std::basic_string str() const { 157 | return {this->begin(), this->end()}; 158 | } 159 | 160 | constexpr std::basic_string_view to_view() const { 161 | return {this->begin(), this->end()}; 162 | } 163 | 164 | Char value[N + 1]{}; 165 | }; 166 | 167 | #define LIBHAT_DEFINE_FIXED_STRING(name, type) \ 168 | template \ 169 | struct name : public basic_fixed_string { \ 170 | using basic_fixed_string::basic_fixed_string; \ 171 | }; \ 172 | template \ 173 | name(const type(&str)[N]) -> name; \ 174 | template \ 175 | constexpr inline auto operator+(const type (&cstr)[N], const basic_fixed_string& lstr) { \ 176 | return name{cstr} + lstr; \ 177 | } 178 | 179 | LIBHAT_DEFINE_FIXED_STRING(fixed_string, char) 180 | LIBHAT_DEFINE_FIXED_STRING(wfixed_string, wchar_t) 181 | #ifdef __cpp_lib_char8_t 182 | LIBHAT_DEFINE_FIXED_STRING(u8fixed_string, char8_t) 183 | #endif 184 | LIBHAT_DEFINE_FIXED_STRING(u16fixed_string, char16_t) 185 | LIBHAT_DEFINE_FIXED_STRING(u32fixed_string, char32_t) 186 | 187 | #undef LIBHAT_DEFINE_FIXED_STRING 188 | } 189 | -------------------------------------------------------------------------------- /include/libhat/memory.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifndef LIBHAT_MODULE 4 | #include 5 | #include 6 | #endif 7 | 8 | #include "export.hpp" 9 | 10 | LIBHAT_EXPORT namespace hat { 11 | 12 | enum class protection : uint8_t { 13 | Read = 0b001, 14 | Write = 0b010, 15 | Execute = 0b100 16 | }; 17 | 18 | constexpr protection operator|(protection lhs, protection rhs) noexcept { 19 | using U = std::underlying_type_t; 20 | return static_cast(static_cast(lhs) | static_cast(rhs)); 21 | } 22 | 23 | constexpr protection operator&(protection lhs, protection rhs) noexcept { 24 | using U = std::underlying_type_t; 25 | return static_cast(static_cast(lhs) & static_cast(rhs)); 26 | } 27 | 28 | constexpr protection operator^(protection lhs, protection rhs) noexcept { 29 | using U = std::underlying_type_t; 30 | return static_cast(static_cast(lhs) ^ static_cast(rhs)); 31 | } 32 | 33 | constexpr protection& operator|=(protection& lhs, const protection rhs) noexcept { 34 | return lhs = lhs | rhs; 35 | } 36 | 37 | constexpr protection& operator&=(protection& lhs, const protection rhs) noexcept { 38 | return lhs = lhs & rhs; 39 | } 40 | 41 | constexpr protection& operator^=(protection& lhs, const protection rhs) noexcept { 42 | return lhs = lhs ^ rhs; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /include/libhat/memory_protector.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifndef LIBHAT_MODULE 4 | #include 5 | #include 6 | #include 7 | #include 8 | #endif 9 | 10 | #include "export.hpp" 11 | #include "memory.hpp" 12 | 13 | LIBHAT_EXPORT namespace hat { 14 | 15 | /// RAII wrapper for setting memory protection flags 16 | class memory_protector { 17 | public: 18 | [[nodiscard]] memory_protector(uintptr_t address, size_t size, protection flags); 19 | 20 | ~memory_protector() { 21 | if (this->set) { 22 | this->restore(); 23 | } 24 | } 25 | 26 | memory_protector(memory_protector&& o) noexcept : 27 | address(o.address), 28 | size(o.size), 29 | oldProtection(o.oldProtection), 30 | set(std::exchange(o.set, false)) {} 31 | 32 | memory_protector& operator=(memory_protector&& o) = delete; 33 | memory_protector(const memory_protector&) = delete; 34 | memory_protector& operator=(const memory_protector&) = delete; 35 | 36 | /// Returns true if the memory protect operation was successful 37 | [[nodiscard]] bool is_set() const noexcept { 38 | return this->set; 39 | } 40 | 41 | private: 42 | void restore(); 43 | 44 | uintptr_t address; 45 | size_t size; 46 | uint32_t oldProtection{}; // Memory protection flags native to Operating System 47 | bool set{}; 48 | }; 49 | } 50 | -------------------------------------------------------------------------------- /include/libhat/platform.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // Detect CPU Architecture 4 | #if defined(_M_X64) || defined(__amd64__) 5 | #define LIBHAT_X86_64 6 | #elif defined(_M_IX86) || defined(__i386__) 7 | #define LIBHAT_X86 8 | #elif defined(_M_ARM64) || defined(__aarch64__) 9 | #define LIBHAT_AARCH64 10 | #elif defined(_M_ARM) || defined(__arm__) 11 | #define LIBHAT_ARM 12 | #else 13 | #error Unsupported Architecture 14 | #endif 15 | 16 | // Detect Operating System 17 | #if defined(_WIN32) 18 | #define LIBHAT_WINDOWS 19 | #elif defined(linux) || defined(__linux__) || defined(__linux) 20 | #define LIBHAT_UNIX 21 | #define LIBHAT_LINUX 22 | #elif defined(__APPLE__) && defined(__MACH__) 23 | #define LIBHAT_UNIX 24 | #define LIBHAT_MAC 25 | #else 26 | #error Unsupported Operating System 27 | #endif 28 | -------------------------------------------------------------------------------- /include/libhat/process.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifndef LIBHAT_MODULE 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #endif 11 | 12 | #include "export.hpp" 13 | #include "memory.hpp" 14 | 15 | LIBHAT_EXPORT namespace hat::process { 16 | 17 | class module { 18 | public: 19 | /// Returns the base address of the module in memory, as a uintptr_t 20 | [[nodiscard]] uintptr_t address() const { 21 | return this->baseAddress; 22 | } 23 | 24 | /// Returns the complete memory region for the given module. This may include portions which are uncommitted. 25 | /// To verify whether the region is safe to read, use hat::process::is_readable. 26 | [[nodiscard]] std::span get_module_data() const; 27 | 28 | /// Returns the memory region for a named section 29 | [[nodiscard]] std::span get_section_data(std::string_view name) const; 30 | 31 | /// Invokes the callback for each memory segment defined by this module as long as it returns true. Depending on 32 | /// the platform, a segment may be represented by multiple linker sections. The returned byte range is not 33 | /// guaranteed to have page aligned begin and end addresses. 34 | void for_each_segment(const std::function, hat::protection)>& callback) const; 35 | 36 | private: 37 | explicit module(const uintptr_t baseAddress) 38 | : baseAddress(baseAddress) {} 39 | 40 | friend hat::process::module get_process_module(); 41 | friend std::optional get_module(std::string_view); 42 | friend std::optional module_at(void* address, std::optional size); 43 | 44 | uintptr_t baseAddress{}; 45 | }; 46 | 47 | /// Returns whether the entirety of a given memory region is readable 48 | [[nodiscard]] bool is_readable(std::span region); 49 | 50 | /// Returns whether the entirety of a given memory region is writable 51 | [[nodiscard]] bool is_writable(std::span region); 52 | 53 | /// Returns whether the entirety of a given memory region is executable 54 | [[nodiscard]] bool is_executable(std::span region); 55 | 56 | /// Returns the module for the current process's base executable 57 | [[nodiscard]] hat::process::module get_process_module(); 58 | 59 | /// Returns an optional containing the module with the given name in the current process 60 | /// If the module is not found, std::nullopt is returned instead 61 | [[nodiscard]] std::optional get_module(std::string_view name); 62 | 63 | /// Returns the module located at the specified base address. Optionally, a size may be provided to indicate 64 | /// the size of the allocation pointed to by address, preventing a potential out-of-bounds read. 65 | /// If the given address does not point to a valid module, std::nullopt is returned instead 66 | [[nodiscard]] std::optional module_at(void* address, std::optional size = {}); 67 | } 68 | -------------------------------------------------------------------------------- /include/libhat/result.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifndef LIBHAT_MODULE 4 | #include 5 | #endif 6 | 7 | #if __cpp_lib_expected >= 202202L 8 | #define LIBHAT_CONSTEXPR_RESULT constexpr 9 | #define LIBHAT_RESULT_EXPECTED 10 | #ifndef LIBHAT_MODULE 11 | #include 12 | #endif 13 | #elif __cpp_lib_variant >= 202106L 14 | #define LIBHAT_CONSTEXPR_RESULT constexpr 15 | #else 16 | #define LIBHAT_CONSTEXPR_RESULT inline 17 | #endif 18 | 19 | #include "export.hpp" 20 | 21 | LIBHAT_EXPORT namespace hat { 22 | 23 | template 24 | class result_error { 25 | template 26 | friend class result; 27 | 28 | public: 29 | constexpr explicit result_error(const E& t) noexcept(std::is_nothrow_copy_constructible_v) 30 | : error(t) {} 31 | 32 | constexpr explicit result_error(E&& t) noexcept(std::is_nothrow_move_constructible_v) 33 | : error(std::move(t)) {} 34 | 35 | private: 36 | E error; 37 | }; 38 | 39 | /// Highly simplified version of C++23's std::expected 40 | /// If available, std::expected is used as the underlying implementation. Otherwise, std::variant is used. 41 | template 42 | class result { 43 | #ifdef LIBHAT_RESULT_EXPECTED 44 | using underlying_type = std::expected; 45 | #else 46 | using underlying_type = std::variant; 47 | #endif 48 | underlying_type impl; 49 | 50 | public: 51 | #ifdef LIBHAT_RESULT_EXPECTED 52 | LIBHAT_CONSTEXPR_RESULT result(const T& t) noexcept( 53 | std::is_nothrow_constructible_v) 54 | : impl(std::in_place, t) {} 55 | 56 | LIBHAT_CONSTEXPR_RESULT result(T&& t) noexcept( 57 | std::is_nothrow_constructible_v) 58 | : impl(std::in_place, std::move(t)) {} 59 | 60 | LIBHAT_CONSTEXPR_RESULT result(const result_error& e) noexcept( 61 | std::is_nothrow_constructible_v&>) 62 | : impl(std::unexpect, e.error) {} 63 | 64 | LIBHAT_CONSTEXPR_RESULT result(result_error&& e) noexcept( 65 | std::is_nothrow_constructible_v&&>) 66 | : impl(std::unexpect, std::move(e.error)) {} 67 | #else 68 | LIBHAT_CONSTEXPR_RESULT result(const T& t) noexcept( 69 | std::is_nothrow_constructible_v, const T&>) 70 | : impl(std::in_place_index<0>, t) {} 71 | 72 | LIBHAT_CONSTEXPR_RESULT result(T&& t) noexcept( 73 | std::is_nothrow_constructible_v, T&&>) 74 | : impl(std::in_place_index<0>, std::move(t)) {} 75 | 76 | LIBHAT_CONSTEXPR_RESULT result(const result_error& e) noexcept( 77 | std::is_nothrow_constructible_v, const result_error&>) 78 | : impl(std::in_place_index<1>, e.error) {} 79 | 80 | LIBHAT_CONSTEXPR_RESULT result(result_error&& e) noexcept( 81 | std::is_nothrow_constructible_v, result_error&&>) 82 | : impl(std::in_place_index<1>, std::move(e.error)) {} 83 | #endif 84 | 85 | LIBHAT_CONSTEXPR_RESULT result(const result&) noexcept( 86 | std::is_nothrow_copy_constructible_v) = default; 87 | 88 | LIBHAT_CONSTEXPR_RESULT result(result&&) noexcept( 89 | std::is_nothrow_move_constructible_v) = default; 90 | 91 | [[nodiscard]] LIBHAT_CONSTEXPR_RESULT bool has_value() const noexcept { 92 | #ifdef LIBHAT_RESULT_EXPECTED 93 | return impl.has_value(); 94 | #else 95 | return impl.index() == 0; 96 | #endif 97 | } 98 | 99 | LIBHAT_CONSTEXPR_RESULT T& value() { 100 | #ifdef LIBHAT_RESULT_EXPECTED 101 | return impl.value(); 102 | #else 103 | return std::get<0>(impl); 104 | #endif 105 | } 106 | 107 | LIBHAT_CONSTEXPR_RESULT const T& value() const { 108 | #ifdef LIBHAT_RESULT_EXPECTED 109 | return impl.value(); 110 | #else 111 | return std::get<0>(impl); 112 | #endif 113 | } 114 | 115 | LIBHAT_CONSTEXPR_RESULT E& error() { 116 | #ifdef LIBHAT_RESULT_EXPECTED 117 | return impl.error(); 118 | #else 119 | return std::get<1>(impl); 120 | #endif 121 | } 122 | 123 | LIBHAT_CONSTEXPR_RESULT const E& error() const { 124 | #ifdef LIBHAT_RESULT_EXPECTED 125 | return impl.error(); 126 | #else 127 | return std::get<1>(impl); 128 | #endif 129 | } 130 | }; 131 | } 132 | 133 | #undef LIBHAT_RESULT_EXPECTED 134 | -------------------------------------------------------------------------------- /include/libhat/signature.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifndef LIBHAT_MODULE 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #endif 11 | 12 | #include "export.hpp" 13 | #include "strconv.hpp" 14 | #include "fixed_string.hpp" 15 | 16 | LIBHAT_EXPORT namespace hat { 17 | 18 | /// Effectively std::optional, but with the added flexibility of being able to use std::bit_cast on 19 | /// instances of the class in constant expressions. 20 | struct signature_element { 21 | constexpr signature_element() noexcept {} 22 | constexpr signature_element(std::nullopt_t) noexcept {} 23 | constexpr signature_element(const std::byte valueIn) noexcept : val(valueIn), present(true) {} 24 | 25 | constexpr signature_element& operator=(std::nullopt_t) noexcept { 26 | return *this = signature_element{}; 27 | } 28 | 29 | constexpr signature_element& operator=(const std::byte valueIn) noexcept { 30 | return *this = signature_element{valueIn}; 31 | } 32 | 33 | constexpr void reset() noexcept { 34 | *this = std::nullopt; 35 | } 36 | 37 | [[nodiscard]] constexpr bool has_value() const noexcept { 38 | return this->present; 39 | } 40 | 41 | [[nodiscard]] constexpr std::byte value() const noexcept { 42 | return this->val; 43 | } 44 | 45 | [[nodiscard]] constexpr operator bool() const noexcept { 46 | return this->has_value(); 47 | } 48 | 49 | [[nodiscard]] constexpr std::byte operator*() const noexcept { 50 | return this->value(); 51 | } 52 | private: 53 | std::byte val{}; 54 | bool present = false; 55 | }; 56 | 57 | using signature = std::vector; 58 | using signature_view = std::span; 59 | 60 | template 61 | using fixed_signature = std::array; 62 | 63 | /// Convert raw byte storage into a signature 64 | [[nodiscard]] constexpr signature bytes_to_signature(std::span bytes) { 65 | return {bytes.begin(), bytes.end()}; 66 | } 67 | 68 | template 69 | [[nodiscard]] constexpr signature object_to_signature(const T& value) { 70 | using bytes = std::array; 71 | return bytes_to_signature(std::bit_cast(value)); 72 | } 73 | 74 | template 75 | [[nodiscard]] constexpr signature string_to_signature(std::basic_string_view str) { 76 | const auto bytes = std::as_bytes(std::span{str}); 77 | signature sig{}; 78 | sig.reserve(bytes.size()); 79 | for (std::byte byte : bytes) { 80 | sig.emplace_back(byte); 81 | } 82 | return sig; 83 | } 84 | 85 | template 86 | [[nodiscard]] constexpr signature string_to_signature(std::basic_string str) { 87 | return string_to_signature(std::basic_string_view{str}); 88 | } 89 | 90 | enum class signature_parse_error { 91 | missing_byte, 92 | parse_error, 93 | empty_signature, 94 | }; 95 | 96 | [[nodiscard]] LIBHAT_CONSTEXPR_RESULT result parse_signature(std::string_view str) { 97 | signature sig{}; 98 | bool containsByte = false; 99 | for (const auto& word : str | std::views::split(' ')) { 100 | if (word.empty()) { 101 | continue; 102 | } 103 | if (word[0] == '?') { 104 | sig.emplace_back(std::nullopt); 105 | } else { 106 | const auto sv = std::string_view{word.begin(), word.end()}; 107 | const auto parsed = parse_int(sv, 16); 108 | if (parsed.has_value()) { 109 | sig.emplace_back(static_cast(parsed.value())); 110 | } else { 111 | return result_error{signature_parse_error::parse_error}; 112 | } 113 | containsByte = true; 114 | } 115 | } 116 | if (sig.empty()) { 117 | return result_error{signature_parse_error::empty_signature}; 118 | } 119 | if (!containsByte) { 120 | return result_error{signature_parse_error::missing_byte}; 121 | } 122 | return sig; 123 | } 124 | 125 | /// Parses a signature string at compile time and returns the result as a fixed_signature 126 | template 127 | [[nodiscard]] consteval auto compile_signature() { 128 | const auto sig = parse_signature(str.c_str()).value(); 129 | constexpr auto N = parse_signature(str.c_str()).value().size(); 130 | fixed_signature arr{}; 131 | std::ranges::move(sig, arr.begin()); 132 | return arr; 133 | } 134 | 135 | [[nodiscard]] constexpr std::string to_string(const signature_view signature) { 136 | constexpr std::string_view hex{"0123456789ABCDEF"}; 137 | std::string ret; 138 | ret.reserve(signature.size() * 3); 139 | for (auto& element : signature) { 140 | if (element.has_value()) { 141 | ret += { 142 | hex[static_cast(element.value() >> 4) & 0xFu], 143 | hex[static_cast(element.value() >> 0) & 0xFu], 144 | ' ' 145 | }; 146 | } else { 147 | ret += "? "; 148 | } 149 | } 150 | ret.pop_back(); 151 | return ret; 152 | } 153 | } 154 | 155 | LIBHAT_EXPORT namespace hat::inline literals::inline signature_literals { 156 | template 157 | consteval auto operator""_sig() noexcept { 158 | return hat::compile_signature(); 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /include/libhat/strconv.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifndef LIBHAT_MODULE 4 | #include 5 | #include 6 | #endif 7 | 8 | #include "concepts.hpp" 9 | #include "export.hpp" 10 | #include "result.hpp" 11 | 12 | LIBHAT_EXPORT namespace hat { 13 | 14 | enum class parse_int_error { 15 | invalid_base, 16 | illegal_char 17 | }; 18 | 19 | template 20 | LIBHAT_CONSTEXPR_RESULT result parse_int(Iter begin, Iter end, uint8_t base = 10) noexcept { 21 | if (base < 2 || base > 36) { 22 | return result_error{parse_int_error::invalid_base}; 23 | } 24 | 25 | Integer sign = 1; 26 | Integer value = 0; 27 | const int digits = base < 10 ? base : 10; 28 | const int letters = base > 10 ? base - 10 : 0; 29 | 30 | for (auto iter = begin; iter != end; iter++) { 31 | const char ch = *iter; 32 | 33 | if constexpr (std::is_signed_v) { 34 | if (iter == begin) { 35 | if (ch == '+') { 36 | continue; 37 | } else if (ch == '-') { 38 | sign = -1; 39 | continue; 40 | } 41 | } 42 | } 43 | 44 | value *= base; 45 | if (ch >= '0' && ch < '0' + digits) { 46 | value += static_cast(ch - '0'); 47 | } else if (ch >= 'A' && ch < 'A' + letters) { 48 | value += static_cast(ch - 'A' + 10); 49 | } else if (ch >= 'a' && ch < 'a' + letters) { 50 | value += static_cast(ch - 'a' + 10); 51 | } else { 52 | // Throws an exception at runtime AND prevents constexpr evaluation 53 | return result_error{parse_int_error::illegal_char}; 54 | } 55 | } 56 | return sign * value; 57 | } 58 | 59 | template 60 | LIBHAT_CONSTEXPR_RESULT result parse_int(std::string_view str, uint8_t base = 10) noexcept { 61 | return parse_int(str.cbegin(), str.cend(), base); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /include/libhat/string_literal.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "export.hpp" 4 | #include "fixed_string.hpp" 5 | 6 | LIBHAT_EXPORT namespace hat { 7 | 8 | template 9 | struct basic_string_literal { 10 | static constexpr auto storage = str; 11 | }; 12 | 13 | template 14 | using string_literal = basic_string_literal; 15 | 16 | template 17 | using wstring_literal = basic_string_literal; 18 | 19 | #ifdef __cpp_lib_char8_t 20 | template 21 | using u8string_literal = basic_string_literal; 22 | #endif 23 | 24 | template 25 | using u16string_literal = basic_string_literal; 26 | 27 | template 28 | using u32string_literal = basic_string_literal; 29 | } 30 | 31 | LIBHAT_EXPORT namespace hat::inline literals::inline string_literals { 32 | 33 | template 34 | consteval auto operator""_s() noexcept { 35 | return string_literal(); 36 | } 37 | 38 | template 39 | consteval auto operator""_ws() noexcept { 40 | return wstring_literal(); 41 | } 42 | 43 | #ifdef __cpp_lib_char8_t 44 | template 45 | consteval auto operator""_u8s() noexcept { 46 | return u8string_literal(); 47 | } 48 | #endif 49 | 50 | template 51 | consteval auto operator""_u16s() noexcept { 52 | return u16string_literal(); 53 | } 54 | 55 | template 56 | consteval auto operator""_u32s() noexcept { 57 | return u32string_literal(); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /include/libhat/system.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifndef LIBHAT_MODULE 4 | #include 5 | #endif 6 | 7 | #include "defines.hpp" 8 | #include "export.hpp" 9 | 10 | LIBHAT_EXPORT namespace hat { 11 | struct system_info { 12 | size_t page_size{}; 13 | 14 | system_info(const system_info&) = delete; 15 | system_info& operator=(const system_info&) = delete; 16 | system_info(system_info&&) = delete; 17 | system_info& operator=(system_info&&) = delete; 18 | protected: 19 | system_info(); 20 | }; 21 | } 22 | 23 | #if defined(LIBHAT_X86) || defined(LIBHAT_X86_64) 24 | LIBHAT_EXPORT namespace hat { 25 | 26 | struct system_info_x86 : hat::system_info { 27 | std::string cpu_vendor{}; 28 | std::string cpu_brand{}; 29 | struct { 30 | bool sse; 31 | bool sse2; 32 | bool sse3; 33 | bool ssse3; 34 | bool sse41; 35 | bool sse42; 36 | bool avx; 37 | bool avx2; 38 | bool avx512f; 39 | bool avx512bw; 40 | bool popcnt; 41 | bool bmi; 42 | } extensions{}; 43 | private: 44 | system_info_x86(); 45 | friend const system_info_x86& get_system(); 46 | static const system_info_x86 instance; 47 | }; 48 | 49 | using system_info_impl = system_info_x86; 50 | } 51 | #elif defined(LIBHAT_ARM) || defined(LIBHAT_AARCH64) 52 | LIBHAT_EXPORT namespace hat { 53 | 54 | struct system_info_arm : hat::system_info { 55 | private: 56 | system_info_arm() = default; 57 | friend const system_info_arm& get_system(); 58 | static const system_info_arm instance; 59 | }; 60 | 61 | using system_info_impl = system_info_arm; 62 | } 63 | #endif 64 | 65 | LIBHAT_EXPORT namespace hat { 66 | 67 | const system_info_impl& get_system(); 68 | } 69 | -------------------------------------------------------------------------------- /include/libhat/type_traits.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifndef LIBHAT_MODULE 4 | #include 5 | #endif 6 | 7 | #include "export.hpp" 8 | 9 | namespace hat::detail { 10 | 11 | template 12 | void implicitly_default_construct(const T&); 13 | } 14 | 15 | LIBHAT_EXPORT namespace hat { 16 | 17 | template 18 | struct is_implicitly_default_constructible : std::false_type {}; 19 | 20 | template requires requires { hat::detail::implicitly_default_construct({}); } 21 | struct is_implicitly_default_constructible : std::true_type {}; 22 | 23 | template 24 | constexpr bool is_implicitly_default_constructible_v = is_implicitly_default_constructible::value; 25 | 26 | template 27 | struct constness_as : std::type_identity> {}; 28 | 29 | template 30 | struct constness_as : std::type_identity {}; 31 | 32 | template 33 | using constness_as_t = typename constness_as::type; 34 | } 35 | -------------------------------------------------------------------------------- /include/libhat/utility.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "concepts.hpp" 4 | #include "export.hpp" 5 | 6 | LIBHAT_EXPORT namespace hat { 7 | 8 | template 9 | struct div_t { 10 | T quot; 11 | T rem; 12 | }; 13 | 14 | /// Generic version of 's div, with the added benefit of returning a type that isn't reliant 15 | /// on the implementation defined ordering of the "quot" and "rem" members in div_t. 16 | template 17 | constexpr div_t div(const T numerator, const T denominator) { 18 | return { 19 | static_cast(numerator / denominator), 20 | static_cast(numerator % denominator) 21 | }; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /module/libhat.cppm: -------------------------------------------------------------------------------- 1 | module; 2 | 3 | #include 4 | 5 | #if _MSC_VER 6 | #include 7 | #endif 8 | 9 | #ifndef LIBHAT_USE_STD_MODULE 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #if __cpp_lib_expected >= 202202L 35 | #include 36 | #endif 37 | #endif 38 | 39 | export module libhat; 40 | 41 | #ifdef LIBHAT_USE_STD_MODULE 42 | import std.compat; 43 | #endif 44 | 45 | extern "C++" { 46 | #define LIBHAT_MODULE 47 | #include "libhat.hpp" 48 | } 49 | -------------------------------------------------------------------------------- /src/Scanner.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | #ifdef LIBHAT_HINT_X86_64 7 | #include "arch/x86/Frequency.hpp" 8 | #endif 9 | 10 | namespace hat::detail { 11 | 12 | void scan_context::apply_hints(const scanner_context& scanner) { 13 | const bool pair0 = static_cast(this->hints & scan_hint::pair0); 14 | 15 | #ifdef LIBHAT_HINT_X86_64 16 | const bool x86_64 = static_cast(this->hints & scan_hint::x86_64); 17 | if (x86_64 && !pair0 && scanner.vectorSize && this->alignment == hat::scan_alignment::X1) { 18 | static constexpr auto getScore = [](const std::byte a, const std::byte b) { 19 | constexpr auto& pairs = hat::detail::x86_64::pairs_x1; 20 | const auto it = std::ranges::find(pairs, std::pair{a, b}); 21 | return static_cast(it - pairs.begin()); 22 | }; 23 | 24 | std::optional> bestPair{}; 25 | for (auto it = this->signature.begin(); it != std::prev(this->signature.end()); it++) { 26 | const auto i = static_cast(it - this->signature.begin()); 27 | auto& a = *it; 28 | auto& b = *std::next(it); 29 | 30 | if (a.has_value() && b.has_value()) { 31 | const auto score = getScore(a.value(), b.value()); 32 | if (!bestPair || score > bestPair->second) { 33 | bestPair.emplace(i, score); 34 | } 35 | } 36 | } 37 | 38 | if (bestPair) { 39 | this->pairIndex = bestPair->first; 40 | } 41 | } 42 | #endif 43 | 44 | // If no "optimal" pair was found, find the first byte pair in the signature 45 | if (!this->pairIndex.has_value() && this->alignment == hat::scan_alignment::X1) { 46 | for (auto it = this->signature.begin(); it != std::prev(this->signature.end()); it++) { 47 | const auto i = static_cast(it - this->signature.begin()); 48 | auto& a = *it; 49 | auto& b = *std::next(it); 50 | 51 | if (a.has_value() && b.has_value()) { 52 | this->pairIndex = i; 53 | break; 54 | } 55 | if (i == 0 && pair0) { 56 | break; 57 | } 58 | } 59 | } 60 | } 61 | 62 | void scan_context::auto_resolve_scanner() { 63 | #if defined(LIBHAT_X86) || defined(LIBHAT_X86_64) 64 | const auto& ext = get_system().extensions; 65 | if (ext.bmi) { 66 | #if defined(LIBHAT_X86_64) && !defined(LIBHAT_DISABLE_AVX512) 67 | if (ext.avx512f && ext.avx512bw) { 68 | this->scanner = resolve_scanner(*this); 69 | return; 70 | } 71 | #endif 72 | if (ext.avx2) { 73 | this->scanner = resolve_scanner(*this); 74 | return; 75 | } 76 | } 77 | #if !defined(LIBHAT_DISABLE_SSE) 78 | if (ext.sse41) { 79 | this->scanner = resolve_scanner(*this); 80 | return; 81 | } 82 | #endif 83 | #endif 84 | // If none of the vectorized implementations are available/supported, then fallback to scanning per-byte 85 | this->scanner = resolve_scanner(*this); 86 | } 87 | } 88 | 89 | // Validate return value const-ness for the root find_pattern impl 90 | namespace hat { 91 | static_assert(std::is_same_v(), 93 | std::declval(), 94 | std::declval()))>); 95 | 96 | static_assert(std::is_same_v(), 98 | std::declval(), 99 | std::declval()))>); 100 | 101 | consteval auto count_matches() { 102 | constexpr std::array a{std::byte{1}, std::byte{2}, std::byte{3}, std::byte{4}, std::byte{1}}; 103 | constexpr hat::fixed_signature<1> s{std::byte{1}}; 104 | 105 | std::vector results{}; 106 | hat::find_all_pattern(a.cbegin(), a.cend(), std::back_inserter(results), s); 107 | return results.size(); 108 | } 109 | static_assert(count_matches() == 2); 110 | 111 | static_assert([] { 112 | constexpr std::array a{std::byte{1}, std::byte{2}, std::byte{3}, std::byte{4}, std::byte{1}}; 113 | constexpr hat::fixed_signature<1> s{std::byte{1}}; 114 | 115 | std::vector results{}; 116 | hat::find_all_pattern(a.cbegin(), a.cend(), std::back_inserter(results), s); 117 | 118 | return results == hat::find_all_pattern(a.cbegin(), a.cend(), s); 119 | }()); 120 | 121 | static_assert([] { 122 | constexpr std::array a{std::byte{1}, std::byte{2}, std::byte{3}, std::byte{4}, std::byte{1}}; 123 | constexpr hat::fixed_signature<1> s{std::byte{1}}; 124 | 125 | std::array results{}; 126 | const auto [scan_end, results_end] = hat::find_all_pattern(a.cbegin(), a.cend(), results.begin(), results.end(), s); 127 | return scan_end == a.cend() && results_end == std::next(results.begin(), 2); 128 | }()); 129 | } 130 | -------------------------------------------------------------------------------- /src/System.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace hat { 4 | 5 | const system_info_impl system_info_impl::instance{}; 6 | const system_info_impl& get_system() { 7 | return system_info_impl::instance; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/Utils.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace hat::detail { 7 | 8 | constexpr uintptr_t fast_align_down(uintptr_t address, size_t alignment) { 9 | return address & ~static_cast(alignment - 1); 10 | } 11 | 12 | constexpr uintptr_t fast_align_up(uintptr_t address, size_t alignment) { 13 | return (address + alignment - 1) & ~static_cast(alignment - 1); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/arch/arm/System.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #ifdef LIBHAT_ARM 3 | 4 | #include 5 | 6 | namespace hat { 7 | 8 | } 9 | #endif 10 | -------------------------------------------------------------------------------- /src/arch/x86/AVX2.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #if defined(LIBHAT_X86) || defined(LIBHAT_X86_64) 4 | 5 | #include 6 | 7 | #include 8 | 9 | namespace hat::detail { 10 | 11 | inline void load_signature_256(const signature_view signature, __m256i& bytes, __m256i& mask) { 12 | std::byte byteBuffer[32]{}; // The remaining signature bytes 13 | std::byte maskBuffer[32]{}; // A bitmask for the signature bytes we care about 14 | for (size_t i = 0; i < signature.size(); i++) { 15 | auto e = signature[i]; 16 | if (e.has_value()) { 17 | byteBuffer[i] = *e; 18 | maskBuffer[i] = std::byte{0xFFu}; 19 | } 20 | } 21 | bytes = _mm256_loadu_si256(reinterpret_cast<__m256i*>(&byteBuffer)); 22 | mask = _mm256_loadu_si256(reinterpret_cast<__m256i*>(&maskBuffer)); 23 | } 24 | 25 | template 26 | const_scan_result find_pattern_avx2(const std::byte* begin, const std::byte* end, const scan_context& context) { 27 | const auto signature = context.signature; 28 | const auto cmpIndex = cmpeq2 ? *context.pairIndex : 0; 29 | LIBHAT_ASSUME(cmpIndex < 32); 30 | 31 | // 256 bit vector containing first signature byte repeated 32 | const auto firstByte = _mm256_set1_epi8(static_cast(*signature[cmpIndex])); 33 | 34 | __m256i secondByte; 35 | if constexpr (cmpeq2) { 36 | secondByte = _mm256_set1_epi8(static_cast(*signature[cmpIndex + 1])); 37 | } 38 | 39 | __m256i signatureBytes, signatureMask; 40 | if constexpr (veccmp) { 41 | load_signature_256(signature, signatureBytes, signatureMask); 42 | } 43 | 44 | auto [pre, vec, post] = segment_scan<__m256i, veccmp>(begin, end, signature.size(), cmpIndex); 45 | 46 | if (!pre.empty()) { 47 | const auto result = find_pattern_single(pre.data(), pre.data() + pre.size(), context); 48 | if (result.has_result()) { 49 | return result; 50 | } 51 | } 52 | 53 | for (auto& it : vec) { 54 | const auto cmp = _mm256_cmpeq_epi8(firstByte, _mm256_loadu_si256(&it)); 55 | auto mask = static_cast(_mm256_movemask_epi8(cmp)); 56 | 57 | if constexpr (alignment != scan_alignment::X1) { 58 | mask &= create_alignment_mask(); 59 | if (!mask) continue; 60 | } else if constexpr (cmpeq2) { 61 | const auto cmp2 = _mm256_cmpeq_epi8(secondByte, _mm256_loadu_si256(&it)); 62 | auto mask2 = static_cast(_mm256_movemask_epi8(cmp2)); 63 | // avoid loading unaligned memory by letting a match of the first signature byte in the last 64 | // position imply that the second byte also matched 65 | mask &= (mask2 >> 1) | (0b1u << 31); 66 | } 67 | 68 | while (mask) { 69 | const auto offset = _tzcnt_u32(mask); 70 | const auto i = reinterpret_cast(&it) + offset - cmpIndex; 71 | if constexpr (veccmp) { 72 | const auto data = _mm256_loadu_si256(reinterpret_cast(i)); 73 | const auto cmpToSig = _mm256_cmpeq_epi8(signatureBytes, data); 74 | const auto matched = _mm256_testc_si256(cmpToSig, signatureMask); 75 | if (matched) LIBHAT_UNLIKELY { 76 | return i; 77 | } 78 | } else { 79 | auto match = std::equal(signature.begin(), signature.end(), i, [](auto opt, auto byte) { 80 | return !opt.has_value() || *opt == byte; 81 | }); 82 | if (match) LIBHAT_UNLIKELY { 83 | return i; 84 | } 85 | } 86 | mask = _blsr_u32(mask); 87 | } 88 | } 89 | 90 | if (!post.empty()) { 91 | return find_pattern_single(post.data(), post.data() + post.size(), context); 92 | } 93 | return {}; 94 | } 95 | 96 | template<> 97 | scan_function_t resolve_scanner(scan_context& context) { 98 | context.apply_hints({.vectorSize = 32}); 99 | 100 | const auto alignment = context.alignment; 101 | const auto signature = context.signature; 102 | const bool veccmp = signature.size() <= 32; 103 | 104 | if (alignment == scan_alignment::X1) { 105 | const bool cmpeq2 = context.pairIndex.has_value(); 106 | if (cmpeq2 && veccmp) { 107 | return &find_pattern_avx2; 108 | } else if (cmpeq2) { 109 | return &find_pattern_avx2; 110 | } else if (veccmp) { 111 | return &find_pattern_avx2; 112 | } else { 113 | return &find_pattern_avx2; 114 | } 115 | } else if (alignment == scan_alignment::X16) { 116 | if (veccmp) { 117 | return &find_pattern_avx2; 118 | } else { 119 | return &find_pattern_avx2; 120 | } 121 | } 122 | LIBHAT_UNREACHABLE(); 123 | } 124 | } 125 | #endif 126 | -------------------------------------------------------------------------------- /src/arch/x86/AVX512.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #if defined(LIBHAT_X86_64) && !defined(LIBHAT_DISABLE_AVX512) 4 | 5 | #include 6 | 7 | #include 8 | 9 | namespace hat::detail { 10 | 11 | inline void load_signature_512(const signature_view signature, __m512i& bytes, uint64_t& mask) { 12 | std::byte byteBuffer[64]{}; // The remaining signature bytes 13 | uint64_t maskBuffer{}; // A bitmask for the signature bytes we care about 14 | for (size_t i = 0; i < signature.size(); i++) { 15 | auto e = signature[i]; 16 | if (e.has_value()) { 17 | byteBuffer[i] = *e; 18 | maskBuffer |= (1ull << i); 19 | } 20 | } 21 | bytes = _mm512_loadu_si512(&byteBuffer); 22 | mask = maskBuffer; 23 | } 24 | 25 | template 26 | const_scan_result find_pattern_avx512(const std::byte* begin, const std::byte* end, const scan_context& context) { 27 | const auto signature = context.signature; 28 | const auto cmpIndex = cmpeq2 ? *context.pairIndex : 0; 29 | LIBHAT_ASSUME(cmpIndex < 64); 30 | 31 | // 512 bit vector containing first signature byte repeated 32 | const auto firstByte = _mm512_set1_epi8(static_cast(*signature[cmpIndex])); 33 | 34 | __m512i secondByte; 35 | if constexpr (cmpeq2) { 36 | secondByte = _mm512_set1_epi8(static_cast(*signature[cmpIndex + 1])); 37 | } 38 | 39 | __m512i signatureBytes; 40 | uint64_t signatureMask; 41 | if constexpr (veccmp) { 42 | load_signature_512(signature, signatureBytes, signatureMask); 43 | } 44 | 45 | auto [pre, vec, post] = segment_scan<__m512i, veccmp>(begin, end, signature.size(), cmpIndex); 46 | 47 | if (!pre.empty()) { 48 | const auto result = find_pattern_single(pre.data(), pre.data() + pre.size(), context); 49 | if (result.has_result()) { 50 | return result; 51 | } 52 | } 53 | 54 | for (auto& it : vec) { 55 | auto mask = _mm512_cmpeq_epi8_mask(firstByte, _mm512_loadu_si512(&it)); 56 | 57 | if constexpr (alignment != scan_alignment::X1) { 58 | mask &= create_alignment_mask(); 59 | if (!mask) continue; 60 | } else if constexpr (cmpeq2) { 61 | const auto mask2 = _mm512_cmpeq_epi8_mask(secondByte, _mm512_loadu_si512(&it)); 62 | mask &= (mask2 >> 1) | (0b1ull << 63); 63 | } 64 | 65 | while (mask) { 66 | const auto offset = _tzcnt_u64(mask); 67 | const auto i = reinterpret_cast(&it) + offset - cmpIndex; 68 | if constexpr (veccmp) { 69 | const auto data = _mm512_loadu_si512(i); 70 | const auto invalid = _mm512_mask_cmpneq_epi8_mask(signatureMask, signatureBytes, data); 71 | if (!invalid) LIBHAT_UNLIKELY { 72 | return i; 73 | } 74 | } else { 75 | auto match = std::equal(signature.begin(), signature.end(), i, [](auto opt, auto byte) { 76 | return !opt.has_value() || *opt == byte; 77 | }); 78 | if (match) LIBHAT_UNLIKELY { 79 | return i; 80 | } 81 | } 82 | mask = _blsr_u64(mask); 83 | } 84 | } 85 | 86 | if (!post.empty()) { 87 | return find_pattern_single(post.data(), post.data() + post.size(), context); 88 | } 89 | return {}; 90 | } 91 | 92 | template<> 93 | scan_function_t resolve_scanner(scan_context& context) { 94 | context.apply_hints({.vectorSize = 64}); 95 | 96 | const auto alignment = context.alignment; 97 | const auto signature = context.signature; 98 | const bool veccmp = signature.size() <= 64; 99 | 100 | if (alignment == scan_alignment::X1) { 101 | const bool cmpeq2 = context.pairIndex.has_value(); 102 | if (cmpeq2 && veccmp) { 103 | return &find_pattern_avx512; 104 | } else if (cmpeq2) { 105 | return &find_pattern_avx512; 106 | } else if (veccmp) { 107 | return &find_pattern_avx512; 108 | } else { 109 | return &find_pattern_avx512; 110 | } 111 | } else if (alignment == scan_alignment::X16) { 112 | if (veccmp) { 113 | return &find_pattern_avx512; 114 | } else { 115 | return &find_pattern_avx512; 116 | } 117 | } 118 | LIBHAT_UNREACHABLE(); 119 | } 120 | } 121 | #endif 122 | -------------------------------------------------------------------------------- /src/arch/x86/Frequency.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace hat::detail::x86_64 { 4 | 5 | static constexpr auto p(uint8_t a, uint8_t b) { 6 | return std::pair{std::byte{a}, std::byte{b}}; 7 | } 8 | 9 | // Top 100 byte pair occurrences on 1 byte alignment 10 | // Accounts for ~39.7% of all pairs 11 | static constexpr inline std::array pairs_x1{ 12 | p(0x00, 0x00), p(0x48, 0x8B), p(0xCC, 0xCC), p(0x48, 0x8D), p(0x48, 0x89), 13 | p(0x00, 0x48), p(0x48, 0x83), p(0x44, 0x24), p(0x01, 0x00), p(0x49, 0x8B), 14 | p(0x48, 0x85), p(0x4C, 0x24), p(0xFF, 0xFF), p(0x0F, 0x11), p(0x4C, 0x8B), 15 | p(0x08, 0x48), p(0x24, 0x20), p(0x5C, 0x24), p(0x01, 0x48), p(0xFF, 0x48), 16 | p(0x4C, 0x89), p(0x4C, 0x8D), p(0xCC, 0x48), p(0xFF, 0x15), p(0x10, 0x48), 17 | p(0x24, 0x30), p(0x03, 0x48), p(0x89, 0x44), p(0x00, 0xE8), p(0x90, 0x48), 18 | p(0x8D, 0x05), p(0x83, 0xC4), p(0xC3, 0xCC), p(0x20, 0x48), p(0x0F, 0x57), 19 | p(0x30, 0x48), p(0x02, 0x00), p(0xF3, 0x0F), p(0x00, 0x0F), p(0x54, 0x24), 20 | p(0x85, 0xC9), p(0xC0, 0x0F), p(0x48, 0xC7), p(0x48, 0x81), p(0x85, 0xC0), 21 | p(0x74, 0x24), p(0x02, 0x48), p(0x89, 0x5C), p(0x0F, 0x10), p(0x83, 0xEC), 22 | p(0xC9, 0x74), p(0x8D, 0x4D), p(0x24, 0x40), p(0x57, 0xC0), p(0x24, 0x28), 23 | p(0x8D, 0x4C), p(0x24, 0x38), p(0x00, 0x4C), p(0x8B, 0xCB), p(0x38, 0x48), 24 | p(0x48, 0x3B), p(0xF8, 0x48), p(0x8D, 0x0D), p(0xC0, 0x48), p(0x04, 0x48), 25 | p(0x0F, 0x84), p(0x03, 0x00), p(0x00, 0x49), p(0xC3, 0x48), p(0x8B, 0xCF), 26 | p(0xC0, 0x74), p(0x89, 0x45), p(0x57, 0x48), p(0x40, 0x48), p(0x48, 0x33), 27 | p(0x24, 0x48), p(0x24, 0x50), p(0x0F, 0xB6), p(0x8D, 0x15), p(0x18, 0x48), 28 | p(0x28, 0x48), p(0x0F, 0x7F), p(0x7C, 0x24), p(0x8D, 0x54), p(0x8B, 0x40), 29 | p(0x8B, 0xC8), p(0x8B, 0x01), p(0x8D, 0x8D), p(0xC1, 0x48), p(0x8B, 0x5C), 30 | p(0xFE, 0x48), p(0x89, 0x74), p(0xC7, 0x44), p(0x66, 0x0F), p(0x83, 0xF8), 31 | p(0xCB, 0xE8), p(0x24, 0x60), p(0xCC, 0xE8), p(0xC4, 0x20), p(0x8B, 0x4D), 32 | }; 33 | } 34 | -------------------------------------------------------------------------------- /src/arch/x86/SSE.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #if (defined(LIBHAT_X86) || defined(LIBHAT_X86_64)) && !defined(LIBHAT_DISABLE_SSE) 4 | 5 | #include 6 | 7 | #include 8 | 9 | #ifdef _MSC_VER 10 | #include 11 | 12 | namespace hat::detail { 13 | inline unsigned long bsf(unsigned long num) noexcept { 14 | unsigned long offset; 15 | _BitScanForward(&offset, num); 16 | return offset; 17 | } 18 | } 19 | 20 | #define LIBHAT_BSF32(num) hat::detail::bsf(num) 21 | #else 22 | #define LIBHAT_BSF32(num) __builtin_ctz(num) 23 | #endif 24 | 25 | namespace hat::detail { 26 | 27 | inline void load_signature_128(const signature_view signature, __m128i& bytes, __m128i& mask) { 28 | std::byte byteBuffer[16]{}; // The remaining signature bytes 29 | std::byte maskBuffer[16]{}; // A bitmask for the signature bytes we care about 30 | for (size_t i = 0; i < signature.size(); i++) { 31 | auto e = signature[i]; 32 | if (e.has_value()) { 33 | byteBuffer[i] = *e; 34 | maskBuffer[i] = std::byte{0xFFu}; 35 | } 36 | } 37 | bytes = _mm_loadu_si128(reinterpret_cast<__m128i*>(&byteBuffer)); 38 | mask = _mm_loadu_si128(reinterpret_cast<__m128i*>(&maskBuffer)); 39 | } 40 | 41 | template 42 | const_scan_result find_pattern_sse(const std::byte* begin, const std::byte* end, const scan_context& context) { 43 | const auto signature = context.signature; 44 | const auto cmpIndex = cmpeq2 ? *context.pairIndex : 0; 45 | LIBHAT_ASSUME(cmpIndex < 16); 46 | 47 | // 128 bit vector containing first signature byte repeated 48 | const auto firstByte = _mm_set1_epi8(static_cast(*signature[cmpIndex])); 49 | 50 | __m128i secondByte; 51 | if constexpr (cmpeq2) { 52 | secondByte = _mm_set1_epi8(static_cast(*signature[cmpIndex + 1])); 53 | } 54 | 55 | __m128i signatureBytes, signatureMask; 56 | if constexpr (veccmp) { 57 | load_signature_128(signature, signatureBytes, signatureMask); 58 | } 59 | 60 | auto [pre, vec, post] = segment_scan<__m128i, veccmp>(begin, end, signature.size(), cmpIndex); 61 | 62 | if (!pre.empty()) { 63 | const auto result = find_pattern_single(pre.data(), pre.data() + pre.size(), context); 64 | if (result.has_result()) { 65 | return result; 66 | } 67 | } 68 | 69 | for (auto& it : vec) { 70 | const auto cmp = _mm_cmpeq_epi8(firstByte, _mm_loadu_si128(&it)); 71 | auto mask = static_cast(_mm_movemask_epi8(cmp)); 72 | 73 | if constexpr (alignment != scan_alignment::X1) { 74 | mask &= create_alignment_mask(); 75 | if (!mask) continue; 76 | } else if constexpr (cmpeq2) { 77 | const auto cmp2 = _mm_cmpeq_epi8(secondByte, _mm_loadu_si128(&it)); 78 | auto mask2 = static_cast(_mm_movemask_epi8(cmp2)); 79 | mask &= (mask2 >> 1) | (0b1u << 15); 80 | } 81 | 82 | while (mask) { 83 | const auto offset = LIBHAT_BSF32(mask); 84 | const auto i = reinterpret_cast(&it) + offset - cmpIndex; 85 | if constexpr (veccmp) { 86 | const auto data = _mm_loadu_si128(reinterpret_cast(i)); 87 | const auto cmpToSig = _mm_cmpeq_epi8(signatureBytes, data); 88 | const auto matched = _mm_testc_si128(cmpToSig, signatureMask); 89 | if (matched) LIBHAT_UNLIKELY { 90 | return i; 91 | } 92 | } else { 93 | auto match = std::equal(signature.begin(), signature.end(), i, [](auto opt, auto byte) { 94 | return !opt.has_value() || *opt == byte; 95 | }); 96 | if (match) LIBHAT_UNLIKELY { 97 | return i; 98 | } 99 | } 100 | mask &= (mask - 1); 101 | } 102 | } 103 | 104 | if (!post.empty()) { 105 | return find_pattern_single(post.data(), post.data() + post.size(), context); 106 | } 107 | return {}; 108 | } 109 | 110 | template<> 111 | scan_function_t resolve_scanner(scan_context& context) { 112 | context.apply_hints({.vectorSize = 16}); 113 | 114 | const auto alignment = context.alignment; 115 | const auto signature = context.signature; 116 | const bool veccmp = signature.size() <= 16; 117 | 118 | if (alignment == scan_alignment::X1) { 119 | const bool cmpeq2 = context.pairIndex.has_value(); 120 | if (cmpeq2 && veccmp) { 121 | return &find_pattern_sse; 122 | } else if (cmpeq2) { 123 | return &find_pattern_sse; 124 | } else if (veccmp) { 125 | return &find_pattern_sse; 126 | } else { 127 | return &find_pattern_sse; 128 | } 129 | } else if (alignment == scan_alignment::X16) { 130 | if (veccmp) { 131 | return &find_pattern_sse; 132 | } else { 133 | return &find_pattern_sse; 134 | } 135 | } 136 | LIBHAT_UNREACHABLE(); 137 | } 138 | } 139 | #endif 140 | -------------------------------------------------------------------------------- /src/arch/x86/System.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #if defined(LIBHAT_X86) || defined(LIBHAT_X86_64) 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #ifdef LIBHAT_WINDOWS 14 | #include 15 | #endif 16 | 17 | #ifdef LIBHAT_UNIX 18 | #include 19 | #endif 20 | 21 | #ifndef _XCR_XFEATURE_ENABLED_MASK 22 | #define _XCR_XFEATURE_ENABLED_MASK 0 23 | #endif 24 | 25 | namespace hat { 26 | 27 | static constexpr int CPU_BASIC_INFO = 0; 28 | static constexpr int CPU_EXTENDED_INFO = static_cast(0x80000000); 29 | static constexpr int CPU_BRAND_STRING = static_cast(0x80000004); 30 | 31 | // don't re-use the __cpuid name here because linux macro'd it 32 | #ifdef LIBHAT_WINDOWS 33 | static void cpuid_impl(int* info, int id) { 34 | __cpuid(info, id); 35 | } 36 | static void cpuidex_impl(int* info, int id, int subId) { 37 | __cpuidex(info, id, subId); 38 | } 39 | #endif 40 | #ifdef LIBHAT_UNIX 41 | static void cpuid_impl(int* info, int id) { 42 | __cpuid(id, info[0], info[1], info[2], info[3]); 43 | } 44 | static void cpuidex_impl(int* info, int id, int subId) { 45 | __cpuid_count(id, subId, info[0], info[1], info[2], info[3]); 46 | } 47 | #endif 48 | 49 | system_info_x86::system_info_x86() { 50 | std::array info{}; 51 | std::vector> data{}; 52 | std::vector> extData{}; 53 | 54 | // Gather info 55 | hat::cpuid_impl(info.data(), CPU_BASIC_INFO); 56 | auto nIds = info[0]; 57 | 58 | char vendor[0xC + 1]{}; 59 | memcpy(vendor, &info[1], sizeof(int)); 60 | memcpy(vendor + 4, &info[3], sizeof(int)); 61 | memcpy(vendor + 8, &info[2], sizeof(int)); 62 | 63 | for (int i = CPU_BASIC_INFO; i <= nIds; i++) { 64 | hat::cpuidex_impl(info.data(), i, 0); 65 | data.push_back(info); 66 | } 67 | // Gather extended info 68 | hat::cpuid_impl(info.data(), CPU_EXTENDED_INFO); 69 | int nExtIds = info[0]; 70 | for (int i = CPU_EXTENDED_INFO; i <= nExtIds; i++) { 71 | hat::cpuidex_impl(info.data(), i, 0); 72 | extData.push_back(info); 73 | } 74 | 75 | // Read relevant info 76 | std::bitset<32> f_1_ECX_{}; 77 | std::bitset<32> f_1_EDX_{}; 78 | std::bitset<32> f_7_EBX_{}; 79 | if (nIds >= 1) { 80 | f_1_ECX_ = (uint32_t) data[1][2]; 81 | f_1_EDX_ = (uint32_t) data[1][3]; 82 | } 83 | if (nIds >= 7) { 84 | f_7_EBX_ = (uint32_t) data[7][1]; 85 | } 86 | 87 | // Read extended info 88 | char brand[0x40 + 1]{}; 89 | if (nExtIds >= CPU_BRAND_STRING) { 90 | memcpy(brand, extData[2].data(), sizeof(info)); 91 | memcpy(brand + 16, extData[3].data(), sizeof(info)); 92 | memcpy(brand + 32, extData[4].data(), sizeof(info)); 93 | } 94 | 95 | // Check OS capabilities 96 | bool avxsupport = false; 97 | bool avx512support = false; 98 | bool xsave = f_1_ECX_[26]; 99 | bool osxsave = f_1_ECX_[27]; 100 | if (xsave && osxsave) { 101 | // https://cdrdv2-public.intel.com/671190/253668-sdm-vol-3a.pdf (Page 2-20) 102 | const std::bitset<64> xcr = _xgetbv(_XCR_XFEATURE_ENABLED_MASK); 103 | avxsupport = xcr[1] && xcr[2]; // xmm and ymm 104 | avx512support = avxsupport && xcr[5] && xcr[6] && xcr[7]; // opmask and zmm 105 | } 106 | 107 | this->cpu_vendor = vendor; 108 | this->cpu_brand = brand; 109 | this->extensions = { 110 | .sse = f_1_EDX_[25], 111 | .sse2 = f_1_EDX_[26], 112 | .sse3 = f_1_ECX_[0], 113 | .ssse3 = f_1_ECX_[9], 114 | .sse41 = f_1_ECX_[19], 115 | .sse42 = f_1_ECX_[20], 116 | .avx = f_1_ECX_[28] && avxsupport, 117 | .avx2 = f_7_EBX_[5] && avxsupport, 118 | .avx512f = f_7_EBX_[16] && avx512support, 119 | .avx512bw = f_7_EBX_[30] && avx512support, 120 | .popcnt = f_1_ECX_[23], 121 | .bmi = f_7_EBX_[3], 122 | }; 123 | } 124 | } 125 | #endif 126 | -------------------------------------------------------------------------------- /src/c/libhat.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | static signature_t* allocate_signature(const hat::signature_view signature) { 7 | const auto bytes = std::as_bytes(signature); 8 | auto* mem = malloc(sizeof(signature_t) + bytes.size()); 9 | auto* sig = static_cast(mem); 10 | sig->data = static_cast(mem) + sizeof(signature_t); 11 | sig->count = signature.size(); 12 | std::memcpy(sig->data, bytes.data(), bytes.size()); 13 | return sig; 14 | } 15 | 16 | extern "C" { 17 | 18 | LIBHAT_API libhat_status_t libhat_parse_signature(const char* signatureStr, signature_t** signatureOut) { 19 | auto result = hat::parse_signature(signatureStr); 20 | if (!result.has_value()) { 21 | *signatureOut = nullptr; 22 | switch (result.error()) { 23 | using enum hat::signature_parse_error; 24 | case missing_byte: return libhat_err_sig_nobyte; 25 | case parse_error: return libhat_err_sig_invalid; 26 | case empty_signature: return libhat_err_sig_empty; 27 | } 28 | return libhat_err_unknown; 29 | } 30 | *signatureOut = allocate_signature(result.value()); 31 | return libhat_success; 32 | } 33 | 34 | LIBHAT_API libhat_status_t libhat_create_signature( 35 | const char* bytes, 36 | const char* mask, 37 | const size_t size, 38 | signature_t** signatureOut 39 | ) { 40 | hat::signature signature{}; 41 | signature.reserve(size); 42 | for (size_t i{}; i < size; i++) { 43 | if (static_cast(mask[i])) { 44 | signature.emplace_back(static_cast(bytes[i])); 45 | } else { 46 | signature.emplace_back(std::nullopt); 47 | } 48 | } 49 | *signatureOut = allocate_signature(signature); 50 | return libhat_success; 51 | } 52 | 53 | LIBHAT_API const void* libhat_find_pattern( 54 | const signature_t* signature, 55 | const void* buffer, 56 | const size_t size, 57 | const scan_alignment align 58 | ) { 59 | const hat::signature_view view{ 60 | static_cast(signature->data), 61 | signature->count 62 | }; 63 | 64 | const auto find_pattern = [=](const hat::scan_alignment alignment) { 65 | const auto begin = static_cast(buffer); 66 | const auto end = static_cast(buffer) + size; 67 | const auto result = hat::find_pattern(begin, end, view, alignment); 68 | return result.has_result() ? result.get() : nullptr; 69 | }; 70 | 71 | switch (align) { 72 | case scan_alignment_x1: 73 | return find_pattern(hat::scan_alignment::X1); 74 | case scan_alignment_x16: 75 | return find_pattern(hat::scan_alignment::X16); 76 | } 77 | exit(EXIT_FAILURE); 78 | } 79 | 80 | LIBHAT_API const void* libhat_find_pattern_mod( 81 | const signature_t* signature, 82 | const void* module, 83 | const char* section, 84 | const scan_alignment align 85 | ) { 86 | const hat::signature_view view{ 87 | static_cast(signature->data), 88 | signature->count 89 | }; 90 | 91 | const auto find_pattern = [=]() -> const std::byte* { 92 | const auto mod = hat::process::module_at(const_cast(module)); 93 | if (!mod.has_value()) { 94 | return nullptr; 95 | } 96 | const auto result = hat::find_pattern(view, section, mod.value()); 97 | return result.has_result() ? result.get() : nullptr; 98 | }; 99 | 100 | switch (align) { 101 | case scan_alignment_x1: 102 | return find_pattern.operator()(); 103 | case scan_alignment_x16: 104 | return find_pattern.operator()(); 105 | } 106 | exit(EXIT_FAILURE); 107 | } 108 | 109 | LIBHAT_API const void* libhat_get_module(const char* name) { 110 | if (name) { 111 | if (const auto mod = hat::process::get_module(name); mod.has_value()) { 112 | return reinterpret_cast(mod.value().address()); 113 | } else { 114 | return nullptr; 115 | } 116 | } 117 | return reinterpret_cast(hat::process::get_process_module().address()); 118 | } 119 | 120 | LIBHAT_API void libhat_free(void* mem) { 121 | free(mem); 122 | } 123 | 124 | } // extern "C" 125 | -------------------------------------------------------------------------------- /src/os/linux/Common.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | 13 | static void iter_mapped_regions(std::predicate auto&& callback) { 14 | std::ifstream f("/proc/self/maps"); 15 | std::string s; 16 | while (std::getline(f, s)) { 17 | const char* it = s.data(); 18 | const char* end = s.data() + s.size(); 19 | std::from_chars_result res{}; 20 | 21 | uintptr_t pageBegin; 22 | res = std::from_chars(it, end, pageBegin, 16); 23 | if (res.ec != std::errc{} || res.ptr == end) { 24 | continue; 25 | } 26 | it = res.ptr + 1; // +1 to skip the hyphen 27 | 28 | uintptr_t pageEnd; 29 | res = std::from_chars(it, end, pageEnd, 16); 30 | if (res.ec != std::errc{} || res.ptr == end) { 31 | continue; 32 | } 33 | it = res.ptr + 1; // +1 to skip the space 34 | 35 | std::string_view remaining{it, end}; 36 | if (remaining.size() >= 3) { 37 | uint32_t prot = 0; 38 | if (remaining[0] == 'r') prot |= PROT_READ; 39 | if (remaining[1] == 'w') prot |= PROT_WRITE; 40 | if (remaining[2] == 'x') prot |= PROT_EXEC; 41 | if (!callback(pageBegin, pageEnd, prot)) { 42 | return; 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/os/linux/MemoryProtector.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #ifdef LIBHAT_LINUX 3 | 4 | #include 5 | #include 6 | #include "../../Utils.hpp" 7 | 8 | #include "Common.hpp" 9 | 10 | #include 11 | 12 | namespace hat { 13 | 14 | static std::optional get_page_prot(const uintptr_t address) { 15 | std::optional result; 16 | iter_mapped_regions([&](const uintptr_t begin, const uintptr_t end, uint32_t prot) { 17 | if (address >= begin && address < end) { 18 | result = prot; 19 | return false; 20 | } 21 | return true; 22 | }); 23 | return result; 24 | } 25 | 26 | static int to_system_prot(const protection flags) { 27 | int prot = 0; 28 | if (static_cast(flags & protection::Read)) prot |= PROT_READ; 29 | if (static_cast(flags & protection::Write)) prot |= PROT_WRITE; 30 | if (static_cast(flags & protection::Execute)) prot |= PROT_EXEC; 31 | return prot; 32 | } 33 | 34 | memory_protector::memory_protector(const uintptr_t address, const size_t size, const protection flags) : address(address), size(size) { 35 | const auto pageSize = hat::get_system().page_size; 36 | 37 | const auto oldProt = get_page_prot(address); 38 | if (!oldProt) { 39 | return; // Failure indicated via is_set() 40 | } 41 | 42 | this->oldProtection = *oldProt; 43 | this->set = 0 == mprotect( 44 | reinterpret_cast(detail::fast_align_down(address, pageSize)), 45 | static_cast(detail::fast_align_up(size, pageSize)), 46 | to_system_prot(flags) 47 | ); 48 | } 49 | 50 | void memory_protector::restore() { 51 | const auto pageSize = hat::get_system().page_size; 52 | mprotect( 53 | reinterpret_cast(detail::fast_align_down(address, pageSize)), 54 | static_cast(detail::fast_align_up(size, pageSize)), 55 | static_cast(this->oldProtection) 56 | ); 57 | } 58 | } 59 | #endif 60 | -------------------------------------------------------------------------------- /src/os/linux/Process.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #ifdef LIBHAT_LINUX 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | #include "Common.hpp" 12 | 13 | namespace hat::process { 14 | 15 | hat::process::module get_process_module() { 16 | const auto module = get_module({}); 17 | if (!module) { 18 | std::abort(); 19 | } 20 | return *module; 21 | } 22 | 23 | void module::for_each_segment(const std::function, hat::protection)>& callback) const { 24 | auto phdrCallback = [&](const dl_phdr_info& info) { 25 | const auto addr = std::bit_cast(info.dlpi_addr); 26 | if (addr != this->address()) { 27 | return 0; 28 | } 29 | 30 | for (size_t i = 0; i < info.dlpi_phnum; i++) { 31 | auto& header = info.dlpi_phdr[i]; 32 | if (header.p_type != PT_LOAD) { 33 | continue; 34 | } 35 | 36 | const std::span data{ 37 | reinterpret_cast(this->address() + header.p_vaddr), 38 | header.p_memsz 39 | }; 40 | 41 | hat::protection prot{}; 42 | if (header.p_flags & PF_R) prot |= hat::protection::Read; 43 | if (header.p_flags & PF_W) prot |= hat::protection::Write; 44 | if (header.p_flags & PF_X) prot |= hat::protection::Execute; 45 | 46 | if (!callback(data, prot)) { 47 | break; 48 | } 49 | } 50 | 51 | return 1; 52 | }; 53 | 54 | dl_iterate_phdr( 55 | [](dl_phdr_info* info, size_t, void* data) { 56 | return (*static_cast(data))(*info); 57 | }, 58 | &phdrCallback); 59 | } 60 | 61 | std::optional get_module(const std::string_view name) { 62 | using Handle = std::unique_ptr; 65 | 66 | std::unique_ptr buffer; 67 | 68 | if (!name.empty()) { 69 | buffer = std::make_unique(name.size() + 1); 70 | std::ranges::copy(name, buffer.get()); 71 | } 72 | 73 | const Handle handle{dlopen(buffer.get(), RTLD_LAZY | RTLD_NOLOAD)}; 74 | if (!handle) { 75 | return {}; 76 | } 77 | 78 | std::optional module{}; 79 | auto callback = [&](const dl_phdr_info& info) { 80 | const Handle h{dlopen(info.dlpi_name, RTLD_LAZY | RTLD_NOLOAD)}; 81 | if (h == handle) { 82 | module = hat::process::module{std::bit_cast(info.dlpi_addr)}; 83 | return 1; 84 | } 85 | return 0; 86 | }; 87 | 88 | dl_iterate_phdr( 89 | [](dl_phdr_info* info, size_t, void* data) { 90 | return (*static_cast(data))(*info); 91 | }, 92 | &callback); 93 | 94 | return module; 95 | } 96 | 97 | static bool regionHasFlags(const std::span region, const uint32_t flags) { 98 | auto addr = std::bit_cast(region.data()); 99 | const auto end = addr + region.size(); 100 | 101 | iter_mapped_regions([&](const uintptr_t b, const uintptr_t e, const uint32_t prot) { 102 | if (addr >= b && addr < e) { 103 | if (!(prot & flags)) { 104 | return false; 105 | } 106 | addr = e; 107 | } else if (addr >= e) { 108 | return false; 109 | } 110 | return addr < end; 111 | }); 112 | 113 | return addr >= end; 114 | } 115 | 116 | bool is_readable(const std::span region) { 117 | return regionHasFlags(region, PROT_READ); 118 | } 119 | 120 | bool is_writable(const std::span region) { 121 | return regionHasFlags(region, PROT_WRITE); 122 | } 123 | 124 | bool is_executable(const std::span region) { 125 | return regionHasFlags(region, PROT_EXEC); 126 | } 127 | } 128 | 129 | #endif 130 | -------------------------------------------------------------------------------- /src/os/unix/System.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #ifdef LIBHAT_UNIX 3 | 4 | #include 5 | #include 6 | 7 | namespace hat { 8 | 9 | system_info::system_info() { 10 | this->page_size = static_cast(sysconf(_SC_PAGESIZE)); 11 | } 12 | } 13 | 14 | #endif 15 | -------------------------------------------------------------------------------- /src/os/win32/MemoryProtector.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #ifdef LIBHAT_WINDOWS 3 | 4 | #include 5 | 6 | #ifndef NOMINMAX 7 | #define NOMINMAX 8 | #endif 9 | #ifndef WIN32_LEAN_AND_MEAN 10 | #define WIN32_LEAN_AND_MEAN 11 | #endif 12 | #include 13 | 14 | namespace hat { 15 | 16 | static DWORD to_system_prot(const protection flags) { 17 | const bool r = static_cast(flags & protection::Read); 18 | const bool w = static_cast(flags & protection::Write); 19 | const bool x = static_cast(flags & protection::Execute); 20 | 21 | if (x && w) return PAGE_EXECUTE_READWRITE; 22 | if (x && r) return PAGE_EXECUTE_READ; 23 | if (x) return PAGE_EXECUTE; 24 | if (w) return PAGE_READWRITE; 25 | if (r) return PAGE_READONLY; 26 | return PAGE_NOACCESS; 27 | } 28 | 29 | memory_protector::memory_protector(const uintptr_t address, const size_t size, const protection flags) : address(address), size(size) { 30 | this->set = 0 != VirtualProtect( 31 | reinterpret_cast(this->address), 32 | this->size, 33 | to_system_prot(flags), 34 | reinterpret_cast(&this->oldProtection) 35 | ); 36 | } 37 | 38 | void memory_protector::restore() { 39 | VirtualProtect(reinterpret_cast(this->address), this->size, this->oldProtection, reinterpret_cast(&this->oldProtection)); 40 | } 41 | } 42 | #endif 43 | -------------------------------------------------------------------------------- /src/os/win32/Process.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #ifdef LIBHAT_WINDOWS 3 | 4 | #include 5 | 6 | #ifndef NOMINMAX 7 | #define NOMINMAX 8 | #endif 9 | #ifndef WIN32_LEAN_AND_MEAN 10 | #define WIN32_LEAN_AND_MEAN 11 | #endif 12 | #include 13 | 14 | #include 15 | #include 16 | #include 17 | 18 | namespace hat::process { 19 | 20 | static bool isValidModule(const void* mod, const std::optional size) { 21 | if (size && *size < sizeof(IMAGE_DOS_HEADER)) { 22 | return false; 23 | } 24 | 25 | const auto dosHeader = static_cast(mod); 26 | if (dosHeader->e_magic != IMAGE_DOS_SIGNATURE) { 27 | return false; 28 | } 29 | 30 | if (size && *size < static_cast(dosHeader->e_lfanew) + sizeof(IMAGE_NT_HEADERS)) { 31 | return false; 32 | } 33 | 34 | const auto ntHeaders = reinterpret_cast(static_cast(mod) + dosHeader->e_lfanew); 35 | if (ntHeaders->Signature != IMAGE_NT_SIGNATURE) { 36 | return false; 37 | } 38 | 39 | return true; 40 | } 41 | 42 | static const IMAGE_NT_HEADERS& getNTHeaders(const hat::process::module mod) { 43 | const auto* scanBytes = reinterpret_cast(mod.address()); 44 | const auto* dosHeader = reinterpret_cast(mod.address()); 45 | return *reinterpret_cast(scanBytes + dosHeader->e_lfanew); 46 | } 47 | 48 | static bool regionHasFlags(const std::span region, const DWORD flags) { 49 | for (auto* addr = region.data(); addr < region.data() + region.size();) { 50 | MEMORY_BASIC_INFORMATION mbi{}; 51 | if (!VirtualQuery(addr, &mbi, sizeof(mbi))) { 52 | return false; 53 | } 54 | if (mbi.State != MEM_COMMIT) { 55 | return false; 56 | } 57 | if (!(mbi.Protect & flags)) { 58 | return false; 59 | } 60 | addr = static_cast(mbi.BaseAddress) + mbi.RegionSize; 61 | } 62 | return true; 63 | } 64 | 65 | bool is_readable(const std::span region) { 66 | constexpr DWORD flags = PAGE_EXECUTE_READ 67 | | PAGE_EXECUTE_READWRITE 68 | | PAGE_EXECUTE_WRITECOPY 69 | | PAGE_READONLY 70 | | PAGE_READWRITE 71 | | PAGE_WRITECOPY; 72 | return regionHasFlags(region, flags); 73 | } 74 | 75 | bool is_writable(const std::span region) { 76 | constexpr DWORD flags = PAGE_EXECUTE_READWRITE 77 | | PAGE_EXECUTE_WRITECOPY 78 | | PAGE_READWRITE 79 | | PAGE_WRITECOPY; 80 | return regionHasFlags(region, flags); 81 | } 82 | 83 | bool is_executable(const std::span region) { 84 | constexpr DWORD flags = PAGE_EXECUTE 85 | | PAGE_EXECUTE_READ 86 | | PAGE_EXECUTE_READWRITE 87 | | PAGE_EXECUTE_WRITECOPY; 88 | return regionHasFlags(region, flags); 89 | } 90 | 91 | hat::process::module get_process_module() { 92 | return module{reinterpret_cast(GetModuleHandleW(nullptr))}; 93 | } 94 | 95 | std::optional get_module(const std::string_view name) { 96 | const int size = MultiByteToWideChar(CP_UTF8, 0, name.data(), static_cast(name.size()), nullptr, 0); 97 | if (!size) { 98 | return {}; 99 | } 100 | 101 | std::wstring str; 102 | str.resize(static_cast(size)); 103 | 104 | MultiByteToWideChar(CP_UTF8, 0, name.data(), static_cast(name.size()), str.data(), size); 105 | 106 | if (const auto handle = GetModuleHandleW(str.c_str()); handle) { 107 | return hat::process::module{std::bit_cast(handle)}; 108 | } 109 | return {}; 110 | } 111 | 112 | std::optional module_at(void* address, const std::optional size) { 113 | if (isValidModule(address, size)) { 114 | return hat::process::module{std::bit_cast(address)}; 115 | } 116 | return {}; 117 | } 118 | 119 | std::span hat::process::module::get_module_data() const { 120 | auto* const scanBytes = reinterpret_cast(this->address()); 121 | const size_t sizeOfImage = getNTHeaders(*this).OptionalHeader.SizeOfImage; 122 | return {scanBytes, sizeOfImage}; 123 | } 124 | 125 | std::span hat::process::module::get_section_data(const std::string_view name) const { 126 | auto* bytes = reinterpret_cast(this->address()); 127 | const auto& ntHeaders = getNTHeaders(*this); 128 | 129 | const auto* sectionHeader = IMAGE_FIRST_SECTION(&ntHeaders); 130 | for (WORD i = 0; i < ntHeaders.FileHeader.NumberOfSections; i++, sectionHeader++) { 131 | const std::string_view sectionName{ 132 | reinterpret_cast(sectionHeader->Name), 133 | strnlen_s(reinterpret_cast(sectionHeader->Name), IMAGE_SIZEOF_SHORT_NAME) 134 | }; 135 | if (sectionName == name) { 136 | return { 137 | bytes + sectionHeader->VirtualAddress, 138 | static_cast(sectionHeader->Misc.VirtualSize) 139 | }; 140 | } 141 | } 142 | return {}; 143 | } 144 | 145 | void hat::process::module::for_each_segment(const std::function, hat::protection)>& callback) const { 146 | const auto& ntHeaders = getNTHeaders(*this); 147 | 148 | const auto* sectionHeader = IMAGE_FIRST_SECTION(&ntHeaders); 149 | for (WORD i = 0; i < ntHeaders.FileHeader.NumberOfSections; i++, sectionHeader++) { 150 | const std::span data{ 151 | reinterpret_cast(this->address() + sectionHeader->VirtualAddress), 152 | sectionHeader->Misc.VirtualSize 153 | }; 154 | 155 | hat::protection prot{}; 156 | if (sectionHeader->Characteristics & IMAGE_SCN_MEM_READ) prot |= hat::protection::Read; 157 | if (sectionHeader->Characteristics & IMAGE_SCN_MEM_WRITE) prot |= hat::protection::Write; 158 | if (sectionHeader->Characteristics & IMAGE_SCN_MEM_EXECUTE) prot |= hat::protection::Execute; 159 | 160 | if (!callback(data, prot)) { 161 | break; 162 | } 163 | } 164 | } 165 | } 166 | #endif 167 | -------------------------------------------------------------------------------- /src/os/win32/Scanner.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #ifdef LIBHAT_WINDOWS 3 | 4 | #include 5 | 6 | namespace hat::experimental { 7 | 8 | template<> 9 | scan_result find_vtable(const std::string& className, hat::process::module mod) { 10 | // Tracing cross-references 11 | // Type Descriptor => Object Locator => VTable 12 | auto sig = string_to_signature(".?AV" + className + "@@"); 13 | 14 | // TODO: Have a better solution for this 15 | // 3rd character may be 'V' for classes and 'U' for structs 16 | sig[3] = {}; 17 | 18 | auto typeDesc = *find_pattern(sig, ".data", mod); 19 | if (!typeDesc) { 20 | return nullptr; 21 | } 22 | // 0x10 is the offset from the type descriptor name to the type descriptor header 23 | typeDesc -= 2 * sizeof(void*); 24 | 25 | // The actual xref refers to an offset from the base module 26 | const auto loffset = static_cast(typeDesc - reinterpret_cast(mod.address())); 27 | auto locator = object_to_signature(loffset); 28 | // FIXME: These appear to be the values just for basic classes with single inheritance. We should be using a 29 | // different method to differentiate the object locator from the base class descriptor. 30 | #ifdef LIBHAT_X86_64 31 | locator.insert(locator.begin(), { 32 | std::byte{0x01}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, // signature 33 | std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, // offset 34 | std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00} // constructor displacement offset 35 | }); 36 | #else 37 | locator.insert(locator.begin(), { 38 | std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, // signature 39 | std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, // offset 40 | std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00} // constructor displacement offset 41 | }); 42 | #endif 43 | const auto objectLocator = *find_pattern(locator, ".rdata", mod); 44 | if (!objectLocator) { 45 | return nullptr; 46 | } 47 | 48 | const auto vtable = *find_pattern(object_to_signature(objectLocator), ".data", mod); 49 | return vtable ? vtable + sizeof(void*) : nullptr; 50 | } 51 | 52 | template<> 53 | scan_result find_vtable(const std::string& className, hat::process::module mod) { 54 | // Tracing cross-references 55 | // Type Descriptor Name => Type Info => VTable 56 | const auto sig = string_to_signature(std::to_string(className.size()) + className + "\0"); 57 | const auto typeName = *find_pattern(sig, ".rdata", mod); 58 | if (!typeName) { 59 | return nullptr; 60 | } 61 | auto typeInfo = *find_pattern(object_to_signature(typeName), ".rdata", mod); 62 | if (!typeInfo) { 63 | return nullptr; 64 | } 65 | // A single pointer is the offset from the type name pointer to the start of the type info 66 | typeInfo -= sizeof(void*); 67 | 68 | const auto vtable = *find_pattern(object_to_signature(typeInfo), ".rdata", mod); 69 | return vtable ? vtable + sizeof(void*) : nullptr; 70 | } 71 | } 72 | #endif 73 | -------------------------------------------------------------------------------- /src/os/win32/System.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #ifdef LIBHAT_WINDOWS 3 | 4 | #include 5 | 6 | #ifndef NOMINMAX 7 | #define NOMINMAX 8 | #endif 9 | #ifndef WIN32_LEAN_AND_MEAN 10 | #define WIN32_LEAN_AND_MEAN 11 | #endif 12 | #include 13 | 14 | namespace hat { 15 | 16 | system_info::system_info() { 17 | SYSTEM_INFO sysInfo; 18 | GetSystemInfo(&sysInfo); 19 | this->page_size = sysInfo.dwPageSize; 20 | } 21 | } 22 | 23 | #endif 24 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | project(libhat_tests) 2 | 3 | include(FetchContent) 4 | 5 | set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) 6 | set(BENCHMARK_ENABLE_TESTING OFF CACHE BOOL "" FORCE) 7 | 8 | if(POLICY CMP0135) 9 | cmake_policy(SET CMP0135 NEW) 10 | endif() 11 | 12 | FetchContent_Declare( 13 | googletest 14 | GIT_REPOSITORY https://github.com/google/googletest.git 15 | GIT_TAG 96cd50c082d880a9aab6455dcc5817cfbf0ea45f 16 | ) 17 | FetchContent_Declare( 18 | benchmark 19 | GIT_REPOSITORY https://github.com/google/benchmark.git 20 | GIT_TAG 4682db08bc7bb7e547e0a1056e32392998f8101f 21 | ) 22 | FetchContent_MakeAvailable(googletest benchmark) 23 | 24 | if(LIBHAT_TESTING_SDE) 25 | set(LIBHAT_SETUP_SDE OFF) 26 | if(WIN32) 27 | FetchContent_Declare( 28 | intel_sde 29 | URL https://downloadmirror.intel.com/831748/sde-external-9.44.0-2024-08-22-win.tar.xz 30 | URL_HASH SHA256=ED1562840510ABEB958DFE4DFBFF5BEA9A960A0075839222E984393425E15FBF 31 | ) 32 | set(LIBHAT_SETUP_SDE ON) 33 | set(LIBHAT_SDE_EXECUTABLE sde.exe) 34 | elseif(UNIX) 35 | FetchContent_Declare( 36 | intel_sde 37 | URL https://downloadmirror.intel.com/831748/sde-external-9.44.0-2024-08-22-lin.tar.xz 38 | URL_HASH SHA256=C6BC16FC6D1855049EA22DAF214A8C00331713BC6A7B0C4D6955D6ED84148B9B 39 | ) 40 | set(LIBHAT_SETUP_SDE ON) 41 | set(LIBHAT_SDE_EXECUTABLE sde64) 42 | else() 43 | message("Intel SDE not supported by host platform, testing support may be limited" WARNING) 44 | endif() 45 | if(LIBHAT_SETUP_SDE) 46 | FetchContent_MakeAvailable(intel_sde) 47 | set(LIBHAT_TEST_COMMAND_PREFIX 48 | ${intel_sde_SOURCE_DIR}/${LIBHAT_SDE_EXECUTABLE} 49 | -gnr # Supports AVX512 emulation 50 | -- 51 | ) 52 | endif() 53 | endif() 54 | 55 | function(register_test NAME SOURCE) 56 | add_executable(${NAME} ${SOURCE}) 57 | target_link_libraries(${NAME} PRIVATE gtest gtest_main benchmark libhat) 58 | add_test(NAME ${NAME} COMMAND ${LIBHAT_TEST_COMMAND_PREFIX} $ --benchmark_counters_tabular=true) 59 | endfunction() 60 | 61 | register_test(libhat_benchmark_compare benchmark/Compare.cpp) 62 | register_test(libhat_test_scanner tests/Scanner.cpp) 63 | 64 | add_executable(libhat_hwinfo info/HardwareInfo.cpp) 65 | target_link_libraries(libhat_hwinfo PRIVATE libhat) 66 | -------------------------------------------------------------------------------- /test/benchmark/Compare.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | #include "vendor/UC1.hpp" 9 | #include "vendor/UC2.hpp" 10 | 11 | static constexpr std::string_view test_pattern = "01 02 03 04 05 06 07 08 09"; 12 | 13 | static auto gen_random_buffer(const size_t size) { 14 | std::vector buffer(size); 15 | std::default_random_engine generator(123); 16 | std::uniform_int_distribution distribution(0, 0xFFFFFFFFFFFFFFFF); 17 | for (size_t i = 0; i < buffer.size(); i += 8) { 18 | *reinterpret_cast(&buffer[i]) = distribution(generator); 19 | } 20 | return buffer; 21 | } 22 | 23 | static void BM_Throughput_Libhat(benchmark::State& state) { 24 | const size_t size = state.range(0); 25 | const auto buf = gen_random_buffer(size); 26 | const auto begin = std::to_address(buf.begin()); 27 | const auto end = std::to_address(buf.end()); 28 | 29 | const auto sig = hat::parse_signature(test_pattern).value(); 30 | for (auto _ : state) { 31 | benchmark::DoNotOptimize(hat::find_pattern(begin, end, sig)); 32 | } 33 | state.SetBytesProcessed(static_cast(state.iterations() * size)); 34 | } 35 | 36 | static void BM_Throughput_UC1(benchmark::State& state) { 37 | const size_t size = state.range(0); 38 | const auto buf = gen_random_buffer(size); 39 | const auto begin = std::to_address(buf.begin()); 40 | const auto end = std::to_address(buf.end()); 41 | 42 | const auto sig = UC1::pattern_to_byte(test_pattern); 43 | for (auto _ : state) { 44 | benchmark::DoNotOptimize(UC1::PatternScan(begin, end, sig)); 45 | } 46 | state.SetBytesProcessed(static_cast(state.iterations() * size)); 47 | } 48 | 49 | static void BM_Throughput_UC2(benchmark::State& state) { 50 | const size_t size = state.range(0); 51 | const auto buf = gen_random_buffer(size); 52 | const auto begin = std::to_address(buf.begin()); 53 | const auto end = std::to_address(buf.end()); 54 | 55 | for (auto _ : state) { 56 | benchmark::DoNotOptimize(UC2::findPattern(begin, end, test_pattern.data())); 57 | } 58 | state.SetBytesProcessed(static_cast(state.iterations() * size)); 59 | } 60 | 61 | static int64_t rangeStart = 1 << 22; // 4 MiB 62 | static int64_t rangeLimit = 1 << 28; // 256 MiB 63 | 64 | BENCHMARK(BM_Throughput_Libhat)->Threads(1)->MinWarmUpTime(1)->MinTime(2)->Range(rangeStart, rangeLimit)->UseRealTime(); 65 | BENCHMARK(BM_Throughput_UC1)->Threads(1)->MinWarmUpTime(1)->MinTime(2)->Range(rangeStart, rangeLimit)->UseRealTime(); 66 | BENCHMARK(BM_Throughput_UC2)->Threads(1)->MinWarmUpTime(1)->MinTime(2)->Range(rangeStart, rangeLimit)->UseRealTime(); 67 | 68 | BENCHMARK_MAIN(); 69 | -------------------------------------------------------------------------------- /test/benchmark/vendor/UC1.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // A slightly modified version of MarkHC's signature scanner in CSGOSimple 4 | // https://github.com/spirthack/CSGOSimple/blob/c37d4bc36efe99c621eb288fd34299c1692ee1dd/CSGOSimple/helpers/utils.cpp#L226 5 | namespace UC1 { 6 | inline std::vector pattern_to_byte(const std::string_view pattern) { 7 | auto bytes = std::vector{}; 8 | auto start = pattern.data(); 9 | auto end = start + pattern.size(); 10 | 11 | for (auto current = start; current < end; ++current) { 12 | if (*current == '?') { 13 | ++current; 14 | if (*current == '?') 15 | ++current; 16 | bytes.push_back(-1); 17 | } else { 18 | bytes.push_back(strtoul(current, const_cast(¤t), 16)); 19 | } 20 | } 21 | return bytes; 22 | } 23 | 24 | inline const std::byte* PatternScan(const std::byte* begin, const std::byte* end, std::span patternBytes) { 25 | auto sizeOfImage = static_cast(std::distance(begin, end)); 26 | auto scanBytes = std::to_address(begin); 27 | 28 | auto s = patternBytes.size(); 29 | auto d = patternBytes.data(); 30 | 31 | for (auto i = 0ul; i < sizeOfImage - s; ++i) { 32 | bool found = true; 33 | for (auto j = 0ul; j < s; ++j) { 34 | if (scanBytes[i + j] != static_cast(d[j]) && d[j] != -1) { 35 | found = false; 36 | break; 37 | } 38 | } 39 | if (found) { 40 | return &scanBytes[i]; 41 | } 42 | } 43 | return nullptr; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /test/benchmark/vendor/UC2.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define INRANGE(x,a,b) (x >= a && x <= b) 4 | #define getBits( x ) (INRANGE((x&(~0x20)),'A','F') ? ((x&(~0x20)) - 'A' + 0xa) : (INRANGE(x,'0','9') ? x - '0' : 0)) 5 | #define getByte( x ) (getBits(x[0]) << 4 | getBits(x[1])) 6 | 7 | // https://www.unknowncheats.me/forum/650040-post8.html 8 | namespace UC2 { 9 | inline const std::byte* findPattern(const std::byte* rangeStart, const std::byte* rangeEnd, const char* pattern) { 10 | using PBYTE = uint8_t*; 11 | using BYTE = uint8_t; 12 | using PWORD = uint16_t*; 13 | 14 | const char* pat = pattern; 15 | const std::byte* firstMatch = nullptr; 16 | for (auto pCur = rangeStart; pCur < rangeEnd; pCur++) { 17 | if (!*pat) return firstMatch; 18 | if (*(PBYTE)pat == '\?' || *(BYTE*)pCur == getByte(pat)) { 19 | if (!firstMatch) firstMatch = pCur; 20 | if (!pat[2]) return firstMatch; 21 | if (*(PWORD)pat == '\?\?' || *(PBYTE)pat != '\?') pat += 3; 22 | else pat += 2; //one ? 23 | } else { 24 | pat = pattern; 25 | firstMatch = 0; 26 | } 27 | } 28 | return NULL; 29 | } 30 | } 31 | 32 | #undef INRANGE 33 | #undef getBits 34 | #undef getByte 35 | -------------------------------------------------------------------------------- /test/info/HardwareInfo.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main() { 4 | const auto& system = hat::get_system(); 5 | 6 | printf("cpu_vendor: %s\n", system.cpu_vendor.c_str()); 7 | printf("cpu_brand: %s\n", system.cpu_brand.c_str()); 8 | // extensions 9 | const auto& ext = system.extensions; 10 | printf("sse: %d\n", ext.sse); 11 | printf("sse2: %d\n", ext.sse2); 12 | printf("sse3: %d\n", ext.sse3); 13 | printf("ssse3: %d\n", ext.ssse3); 14 | printf("sse41: %d\n", ext.sse41); 15 | printf("sse42: %d\n", ext.sse42); 16 | printf("avx: %d\n", ext.avx); 17 | printf("avx2: %d\n", ext.avx2); 18 | printf("avx512f: %d\n", ext.avx512f); 19 | printf("avx512bw: %d\n", ext.avx512bw); 20 | printf("popcnt: %d\n", ext.popcnt); 21 | printf("bmi: %d\n", ext.bmi); 22 | 23 | return 0; 24 | } 25 | -------------------------------------------------------------------------------- /test/tests/Scanner.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | template 6 | struct FindPatternParameters { 7 | static_assert(SignatureSize <= MaxBufferSize); 8 | static_assert(SignatureSize < 0xFF); 9 | 10 | static constexpr auto mode = Mode; 11 | static constexpr auto signature_size = SignatureSize; 12 | static constexpr auto max_buffer_size = MaxBufferSize; 13 | }; 14 | 15 | template 16 | class FindPatternTest; 17 | 18 | template 19 | concept FindPatternTestCallback = std::invocable; 23 | 24 | template 25 | class FindPatternTest> : public ::testing::Test { 26 | protected: 27 | void run_cases( 28 | const hat::scan_alignment alignment, 29 | FindPatternTestCallback auto&& callback 30 | ) { 31 | hat::fixed_signature sig{}; 32 | for (int i{}; i < SignatureSize; i++) { 33 | sig[i] = static_cast(i + 1); 34 | } 35 | hat::fixed_signature sigWildcard = sig; 36 | if constexpr (SignatureSize >= 2) { 37 | sigWildcard[1] = std::nullopt; 38 | } 39 | 40 | const auto contextA = hat::detail::scan_context::create(sig, alignment, hat::scan_hint::none); 41 | const auto contextB = hat::detail::scan_context::create(sigWildcard, alignment, hat::scan_hint::none); 42 | 43 | for (size_t size = SignatureSize; size != MaxBufferSize; size++) { 44 | for (size_t offset{}; offset != size - SignatureSize + 1; offset++) { 45 | std::vector code(size, std::byte{0x00}); 46 | auto* const begin = std::to_address(code.begin()); 47 | auto* const end = std::to_address(code.end()); 48 | 49 | ASSERT_FALSE(contextA.scan(begin, end).has_result()); 50 | ASSERT_FALSE(contextB.scan(begin, end).has_result()); 51 | 52 | std::ranges::copy(sig | std::views::transform(&hat::signature_element::value), 53 | begin + offset); 54 | 55 | callback(contextA.scan(begin, end), begin + offset); 56 | callback(contextB.scan(begin, end), begin + offset); 57 | } 58 | } 59 | } 60 | }; 61 | 62 | using FindPatternTestTypes = ::testing::Types< 63 | #if defined(LIBHAT_X86_64) || defined(LIBHAT_X86) 64 | FindPatternParameters, 65 | FindPatternParameters, 66 | FindPatternParameters, 67 | FindPatternParameters, 68 | FindPatternParameters, 69 | FindPatternParameters, 70 | 71 | FindPatternParameters, 72 | FindPatternParameters, 73 | FindPatternParameters, 74 | FindPatternParameters, 75 | FindPatternParameters, 76 | FindPatternParameters, 77 | #endif 78 | #ifdef LIBHAT_X86_64 79 | FindPatternParameters, 80 | FindPatternParameters, 81 | FindPatternParameters, 82 | FindPatternParameters, 83 | FindPatternParameters, 84 | FindPatternParameters, 85 | #endif 86 | FindPatternParameters, 87 | FindPatternParameters, 88 | FindPatternParameters, 89 | FindPatternParameters, 90 | FindPatternParameters, 91 | FindPatternParameters 92 | >; 93 | 94 | class FindPatternTestNameGenerator { 95 | public: 96 | template 97 | static std::string GetName(int) { 98 | return std::format("{}/{}/{}", getModeName(), T::signature_size, T::max_buffer_size); 99 | } 100 | 101 | private: 102 | template 103 | static consteval std::string_view getModeName() { 104 | if constexpr (Mode == hat::detail::scan_mode::Single) return "Single"; 105 | else if constexpr (Mode == hat::detail::scan_mode::SSE) return "SSE"; 106 | else if constexpr (Mode == hat::detail::scan_mode::AVX2) return "AVX2"; 107 | else if constexpr (Mode == hat::detail::scan_mode::AVX512) return "AVX512"; 108 | else static_assert(sizeof(Mode) == 0); 109 | } 110 | }; 111 | 112 | TYPED_TEST_SUITE(FindPatternTest, FindPatternTestTypes, FindPatternTestNameGenerator); 113 | 114 | TYPED_TEST(FindPatternTest, ScanX1) { 115 | this->run_cases(hat::scan_alignment::X1, [](auto result, auto expected) { 116 | ASSERT_TRUE(result.has_result()); 117 | ASSERT_EQ(result.get(), expected); 118 | }); 119 | } 120 | 121 | TYPED_TEST(FindPatternTest, ScanX16) { 122 | this->run_cases(hat::scan_alignment::X16, [](auto result, auto expected) { 123 | if (std::bit_cast(expected) % 16 == 0) { 124 | ASSERT_TRUE(result.has_result()); 125 | ASSERT_EQ(result.get(), expected); 126 | } else { 127 | ASSERT_FALSE(result.has_result()); 128 | } 129 | }); 130 | } 131 | --------------------------------------------------------------------------------