tkhquang/DetourModKit

GitHub: tkhquang/DetourModKit

DetourModKit 是一个专为 Windows 游戏模组开发设计的 C++23 工具包,旨在简化 Hook、内存扫描及配置管理等常见任务。

Stars: 1 | Forks: 0

# DetourModKit [![覆盖率报告 ≥ 80%](https://static.pigsec.cn/wp-content/uploads/repos/2026/04/89cbf5ca44233132.svg)](https://tkhquang.github.io/DetourModKit/) [![许可证: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) [功能](#features) | [构建](#building-detourmodkit-static-library-via-cmake) | [测试](#running-unit-tests) | [指南](#guides) | [集成](#using-detourmodkit-in-your-mod-project) | [示例](#code-example) DetourModKit 是一个轻量级的 C++ 工具包,旨在简化游戏 Mod 制作中的常见任务,特别是涉及内存扫描、Hook 和配置管理的 Mod。它主要考虑 MinGW 构建,但也旨在实现通用的 C++ 兼容性。 ## 功能 | 模块 | 描述 | 头文件 | |--------|-------------|--------| | AOB Scanner | 支持通配符和 RIP 解析的 SIMD 加速模式扫描 | `scanner.hpp` | | Hook Manager | 通过 SafetyHook 实现内联、中段函数和 VMT Hook | `hook_manager.hpp` | | Configuration | 支持组合键的 INI 设置 | `config.hpp` | | Logger | 带格式化字符串的同步单例日志记录器 | `logger.hpp` | | Async Logger | 带批量写入的无锁有界队列日志记录器 | `async_logger.hpp` | | Memory Utilities | 可读性检查、区域缓存和安全指针读取 | `memory.hpp` | | Event Dispatcher | 支持 RAII 订阅的类型化发布/订阅 | `event_dispatcher.hpp` | | Profiler | 支持 Chrome Tracing 导出的作用域计时(禁用时零开销) | `profiler.hpp` | | Format Utilities | 用于地址、字节和 VK 代码的 `std::format` 辅助工具;字符串修剪 | `format.hpp` | | Filesystem Utilities | 模块目录解析(宽字符串 API) | `filesystem.hpp` | | Math Utilities | 角度转换(仅头文件) | `math.hpp` | | Version Macros | 由 CMake 生成的编译时版本检查 | `version.hpp` | | Input System | 带后台轮询的热键监控(键盘/鼠标/手柄) | `input.hpp`, `input_codes.hpp` |
AOB Scanner - 在内存中查找字节码(特征码),支持通配符 - SIMD 加速模式验证: - AVX2(每次迭代 32 字节,在 Haswell+ CPU 上运行时检测) - SSE2 回退(每次迭代 16 字节),适用于长度 >= 16 字节的模式 - `|` 偏移标记,用于在更宽的模式中定位特定指令(例如,`"48 8B 88 B8 00 00 00 | 48 89 4C 24 68"` 将偏移量设置为第 7 个字节) - 第 N 次出现匹配(从 1 开始),适用于命中多个位置的模式 - RIP 相对指令解析,用于从 x86-64 代码中提取绝对地址(返回带有类型化 `RipResolveError` 的 `std::expected` 以便进行可操作的诊断) - `scan_executable_regions()` 用于扫描进程中所有已提交的可执行页面 - 适用于将代码解包到任何已加载模块之外的匿名内存中的打包或受保护二进制文件的游戏
Hook Manager - 围绕 [SafetyHook](https://github.com/cursey/safetyhook) 的 C++ 封装,用于创建和管理 Hook - **内联 Hook** 和 **中段函数 Hook** - 通过直接地址或 AOB 扫描定位目标函数 - **VMT(虚方法表)Hook** - 克隆对象的 vtable 并按索引替换单个方法槽 - 虚拟调用的每对象拦截(例如,D3D 设备方法、游戏 AI 接口) - 将单个 Hook 的 vtable 应用于多个对象 - 通过 `with_vmt_method()` 安全地基于回调访问 Hook 的方法
Configuration System - 从 INI 文件加载设置(由 [SimpleIni](https://github.com/brofield/simpleini) 提供支持) - Mod 注册配置变量;工具包处理解析和值赋值 - 通过 `register_key_combo` 支持组合键: - 格式:`modifier+trigger`(例如,`Ctrl+Shift+F3`) - 逗号分隔的独立组合(例如,`F3,Gamepad_LT+Gamepad_B`) - 命名键(`Ctrl`、`F3`、`Mouse1`、`Gamepad_A`)、十六进制 VK 代码(`0x72`)和混合格式
Logger - 灵活的单例日志记录器,用于将消息输出到日志文件 - 可配置的日志级别、时间戳和前缀 - 适用于高吞吐量场景的异步日志记录 - 格式化字符串占位符,用于简洁的日志消息 - 通过 Win32 共享访问文件句柄进行并发文件访问(日志记录处于活动状态时,外部工具可以读取日志文件) - `is_enabled(LogLevel)` 用于控制仅跟踪的昂贵工作
Async Logger - 无锁、基于有界队列的异步日志记录器,将日志生成与文件 I/O 解耦 - 生产者端延迟极低,消费者线程进行批量写入 - 可配置的溢出策略:DropNewest / DropOldest / Block / SyncFallback - 有界 Block 策略,默认超时时间为 16 毫秒(60 fps 下的一帧),以防止线程饥饿 - 针对长度 <= 512 字节的消息进行内联缓冲区优化 - 消息大小验证,对长度 > 16 MB 的消息进行截断
Memory Utilities - 用于检查内存可读性/可写性以及向内存写入字节的函数 - 可选的内存区域缓存,具有分片 SRWLOCK 并发、LRU 驱逐和惊群合并 - `is_readable_nonblocking()` - 三态(可读/不可读/未知),适用于延迟敏感线程 - `read_ptr_unsafe()` - 热路径中的安全指针读取(在 MSVC 上受 SEH 保护,在 MinGW 上通过 VirtualQuery 回退进行缓存加速) - `read_ptr_unchecked()` - 仅头文件的内联变体,具有可配置的低地址有效性保护,用于指针链遍历,无每次调用的 SEH 开销(调用者必须保证结构指针的有效性)
Event Dispatcher - 支持 RAII 订阅管理的类型化发布/订阅事件系统 - 每个 `EventDispatcher` 管理单个事件类型 - `shared_mutex` 并发:通过共享锁进行并发 `emit()`,通过独占锁进行订阅/取消订阅 - 订阅在销毁时自动取消 - 处理程序按订阅顺序调用(在取消订阅之间保持) - 线程本地重入保护检测并拒绝来自处理程序内部的订阅/取消订阅调用,防止死锁 - 组合多个调度器以实现多事件架构 - `emit_safe()` 用于容错调度(推荐用于 Hook 回调) - 当调度器在其订阅之前被销毁时是安全的(weak_ptr 保护)
Profiler - 可选的作用域计时检测,禁用时零开销 - 通过 `DMK_ENABLE_PROFILING` 在编译时控制 - 启用时,将无锁计时样本(每个作用域约 50 ns)记录到固定大小的环形缓冲区(64K 样本,约 1.5 MB)中 - 每个样本槽的奇/偶序列计数器,以便 `export_chrome_json()` 可以安全地跳过正在进行的写入而不会出现撕裂读取 - 导出为 [Chrome Tracing JSON](https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/preview) 格式,可在 `chrome://tracing` 或 [Perfetto](https://ui.perfetto.dev) 中查看 - 使用 `DMK_PROFILE_SCOPE("name")` 或 `DMK_PROFILE_FUNCTION()` 宏进行检测;通过 `Profiler::get_instance().export_to_file()` 导出
Format, Filesystem, Math, and Version Utilities - **Format** (`format.hpp`):使用 `std::format` 的内存地址、字节值、VK 代码和十六进制整数向量的内联格式化辅助工具。还包括字符串修剪工具。 - **Filesystem** (`filesystem.hpp`):模块目录解析(宽字符串 API)。 - **Math** (`math.hpp`):角度转换(仅头文件)。 - **Version** (`version.hpp`):通过 `DMK_VERSION_MAJOR`、`DMK_VERSION_MINOR`、`DMK_VERSION_PATCH`、`DMK_VERSION_STRING` 和 `DMK_VERSION_AT_LEAST(major, minor, patch)` 进行编译时版本检查。在配置时从 CMake 的 `project(VERSION)` 生成。
Input System **输入源和模式:** - 键盘、鼠标和 XInput 手柄输入,通过统一的 `InputCode` 标记类型(`InputSource` + 按钮代码) - 按下(边沿触发)和按住(电平触发)输入模式,支持修饰键组合 - 修饰键使用 AND 逻辑,独立组合之间使用 OR 逻辑 - 严格的修饰键匹配 - 只有当完全按住声明的修饰键时,绑定才会触发(按下 `Shift+V` 永远不会触发纯 `V` 绑定) - 多个独立组合可以共享单个绑定名称,以实现跨设备热键(例如,键盘 F3 或手柄 LT+B) - 手柄模拟扳机(LT/RT)和摇杆轴被视为具有可配置死区阈值的数字按钮 - 默认具有焦点感知 - 当进程不拥有前台窗口时,输入事件将被忽略 **线程和生命周期:** - 可作为 RAII `InputPoller` 构建块或通过线程安全的 `InputManager` 单例使用 - 两阶段初始化(先构造后启动)以安全地启动线程 - 带有 `stop_token` 的 `condition_variable_any`,用于响应式协作关闭 - 异常安全的回调调用 - 关闭时自动释放按住状态 - 感知加载器锁的关闭:从 `DllMain` 或 `FreeLibrary` 上下文调用时,后台线程被安全分离而不是加入 **性能:** - 基于哈希映射的 `is_binding_active()` 查询,用于无锁跨线程状态读取(例如,从 60+ fps 的渲染 Hook 中) - 每个名称多个绑定,用于多组合热键 - 通过原子标志实现无锁 `is_running()` - `input_code_to_name()` 的 O(1) 反向名称查找 **手柄和轮询:** - XInput 每个周期轮询一次;当未注册手柄绑定时完全跳过 - 当未连接控制器时,重连尝试限制为每 2 秒一次,避免在未连接的插槽上产生 `XInputGetState` 的每个周期开销 **配置集成:** - 从 INI 文件加载输入代码(命名键、十六进制 VK 代码或混合) - 命名键解析使用二分查找以实现高效查找 - `register_press` 和 `register_hold` 直接接受 `KeyComboList`,以实现配置解析的组合键的零样板绑定
## 测试 * **全面的测试套件:** 使用 GoogleTest 对所有模块进行完整的单元测试覆盖。 * **代码覆盖率:** 自动化覆盖率分析,CI 中有 80% 的最低行覆盖率门槛。 * **覆盖率工具:** 用于解析和分析覆盖率报告的内置脚本。 有关详细的覆盖率分析和测试架构,请参阅 [测试覆盖率指南](docs/tests/README.md)。 ## 指南 * [热重载开发指南](docs/hot-reload/README.md) - 通过实时重载迭代 Hook 的开发工作流 * [测试覆盖率指南](docs/tests/README.md) - 覆盖率分析、测试架构和模块级细分 ## 前置条件 * 支持 C++23 的 C++ 编译器(例如,MinGW g++ 12 或更新版本,MSVC 2022 或更新版本)。 * [CMake](https://cmake.org/) 3.25 或更新版本。 * [Ninja](https://ninja-build.org/) 构建系统(随 Visual Studio 一起提供;对于 MSYS2:`pacman -S ninja`)。 * `make`(可选,用于 Makefile 包装器 -- 例如,MinGW 环境下的 `mingw32-make`)。 * Git(用于克隆和管理子模块)。 ## 构建 DetourModKit(通过 CMake 构建静态库) 本项目使用 CMake、[CMake Presets](https://cmake.org/cmake/help/latest/manual/cmake-presets.7.html) 和 Ninja 来编排其构建。为了方便起见,提供了一个精简的 Makefile 包装。 1. **克隆仓库(包含子模块):** git clone --recursive https://github.com/tkhquang/DetourModKit.git cd DetourModKit 如果您已经克隆但未使用 `--recursive`: git submodule update --init --recursive 要将子模块更新到最新的上游版本(当未固定到特定提交时): git submodule update --init --recursive --remote 2. **构建并打包以进行分发:** 使用 Makefile 包装器(推荐) # 构建库(默认为 MinGW Release) make # 安装到 build/install/ make install # 使用不同的预设构建 make PRESET=msvc-release make install PRESET=msvc-release 直接使用 CMake 预设 # MinGW cmake --preset mingw-release cmake --build --preset mingw-release --parallel cmake --install build/mingw-release --prefix ./install_package/mingw # MSVC(从 Visual Studio 开发者命令提示符运行) cmake --preset msvc-release cmake --build --preset msvc-release --parallel cmake --install build/msvc-release --prefix ./install_package/msvc 可用预设 | 预设 | 编译器 | 构建类型 | 测试 | 备注 | | --- | --- | --- | --- | --- | | `mingw-debug` | GCC (MinGW) | Debug | ON | | | `mingw-debug-asan` | GCC (MinGW) | Debug | ON | 已启用 ASan + UBSan | | `mingw-release` | GCC (MinGW) | Release | OFF | | | `msvc-debug` | MSVC (cl) | Debug | ON | | | `msvc-release` | MSVC (cl) | Release | OFF | | 运行安装命令后,安装目录(Makefile 包装器为 `build/install/`,或者您传递给 `cmake --install` 的任何 `--prefix`)将包含: ```text / ├── include/ │ ├── DetourModKit/ <-- DetourModKit public headers │ │ ├── scanner.hpp <-- AOB scanner │ │ ├── async_logger.hpp <-- Async logging system │ │ ├── config.hpp │ │ ├── event_dispatcher.hpp <-- Typed pub/sub with RAII subscriptions │ │ ├── format.hpp <-- String & format utilities │ │ ├── math.hpp <-- Math utilities (angle conversions) │ │ ├── memory.hpp <-- Memory utilities │ │ ├── profiler.hpp <-- Scoped timing (zero-cost when disabled) │ │ ├── filesystem.hpp <-- Filesystem utilities │ │ ├── hook_manager.hpp <-- Hook management │ │ ├── input.hpp <-- Input/hotkey system │ │ ├── input_codes.hpp <-- Unified input codes (keyboard/mouse/gamepad) │ │ ├── logger.hpp <-- Synchronous logger │ │ ├── version.hpp <-- Version macros (generated by CMake) │ │ ├── win_file_stream.hpp <-- Win32 shared-access file stream │ │ └── ... │ ├── DetourModKit.hpp <-- Main DetourModKit include │ ├── DirectXMath/ <-- DirectXMath headers │ │ ├── DirectXMath.h │ │ ├── DirectXMathVector.inl │ │ └── ... │ ├── safetyhook/ <-- SafetyHook detail headers │ │ ├── common.hpp │ │ ├── inline_hook.hpp │ │ └── ... │ ├── safetyhook.hpp <-- Main SafetyHook include │ └── SimpleIni.h <-- SimpleIni header ├── lib/ │ ├── libDetourModKit.a <-- Static libraries (.a for MinGW, .lib for MSVC) │ ├── libsafetyhook.a │ ├── libZydis.a │ └── libZycore.a └── lib/cmake/DetourModKit/ <-- CMake config files ├── DetourModKitConfig.cmake ├── DetourModKitConfigVersion.cmake └── DetourModKitTargets.cmake ``` ## 运行单元测试 DetourModKit 包含一个使用 GoogleTest 的全面单元测试套件。调试预设(`mingw-debug`、`msvc-debug`)默认启用测试。 ### 使用 Makefile 包装器 ``` # 构建并运行测试(默认为 MinGW) make test # 使用 MSVC 运行测试(需要 VS Developer Command Prompt) make test_msvc # 清理所有构建目录 make clean ``` ### 使用 CMake 预设进行测试 ``` # MinGW cmake --preset mingw-debug cmake --build --preset mingw-debug --parallel ctest --preset mingw-debug # MSVC cmake --preset msvc-debug cmake --build --preset msvc-debug --parallel ctest --preset msvc-debug ``` ### 将警告视为错误 要将编译器警告视为错误(在 CI 中默认启用): ``` cmake --preset mingw-debug -DDMK_WARNINGS_AS_ERRORS=ON cmake --build --preset mingw-debug --parallel ``` ### 启用性能分析 要启用可选的性能分析检测(`DMK_PROFILE_SCOPE` / `DMK_PROFILE_FUNCTION` 宏): ``` cmake --preset mingw-debug -DDMK_ENABLE_PROFILING=ON cmake --build --preset mingw-debug --parallel ``` 当 `DMK_ENABLE_PROFILING` 为 OFF(默认)时,所有性能分析宏都扩展为 `((void)0)`,零开销。`Profiler` 类和 `ScopedProfile` 仍然会被编译到库中(因此测试始终有效),但检测用户代码的宏是空操作。 ### 启用 Sanitizer 要启用 AddressSanitizer 和 UndefinedBehaviorSanitizer(需要 GCC/Clang): ``` # 使用专用 preset cmake --preset mingw-debug-asan cmake --build --preset mingw-debug-asan --parallel # 或使用任何 debug preset 手动进行 cmake --preset mingw-debug -DDMK_ENABLE_SANITIZERS=ON cmake --build --preset mingw-debug --parallel ``` ### 启用代码覆盖率 要生成代码覆盖率报告(需要 GCC/Clang),请在配置时传递覆盖率选项: ``` cmake --preset mingw-debug -DDMK_ENABLE_COVERAGE=ON cmake --build --preset mingw-debug --parallel ``` 所有合并到 `main` 的拉取请求都会通过 CI 自动测试,并有 **80% 的最低行覆盖率** 门槛。有关详细信息,请参阅 [PR Check 工作流](.github/workflows/pr-check.yml)。最新的覆盖率报告会在每次推送到 `main` 时发布到 [GitHub Pages](https://tkhquang.github.io/DetourModKit/)。 ## 在您的 Mod 项目中使用 DetourModKit 有两种主要方法可以将 DetourModKit 集成到您的项目中: ### 方法 1:将 DetourModKit 用作子模块(推荐) 此方法非常适合活跃开发,并确保您始终拥有最新的兼容版本。 1. **添加 DetourModKit 作为子模块:** # 在您的项目根目录中 git submodule add https://github.com/tkhquang/DetourModKit.git external/DetourModKit git submodule update --init --recursive 要固定特定的发布版本: cd external/DetourModKit git checkout v2.0.0 # 或 v1.0.1, v1.0.0 等 cd ../.. git add external/DetourModKit git commit -m "pin DetourModKit to v2.0.0" 稍后要升级到更新版本: cd external/DetourModKit git fetch --tags git checkout v2.1.0 # 所需版本 cd ../.. git add external/DetourModKit git commit -m "upgrade DetourModKit to v2.1.0" 2. **配置您的 CMakeLists.txt:** cmake_minimum_required(VERSION 3.25) project(MyMod VERSION 1.0.0 LANGUAGES CXX) set(CMAKE_CXX_STANDARD 23) set(CMAKE_CXX_STANDARD_REQUIRED ON) # 将 DetourModKit 添加为子目录 if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/external/DetourModKit/CMakeLists.txt") message(STATUS "Configuring DetourModKit from: external/DetourModKit") add_subdirectory(external/DetourModKit) if(TARGET DetourModKit) set(DETOURMODKIT_TARGET DetourModKit) message(STATUS "DetourModKit target found: ${DETOURMODKIT_TARGET}") else() message(FATAL_ERROR "DetourModKit target not created by subdirectory") endif() else() message(FATAL_ERROR "DetourModKit not found at 'external/DetourModKit'. " "Please ensure the submodule is initialized: " "'git submodule update --init --recursive'") endif() # 创建您的 Mod 目标 add_library(MyMod SHARED src/main.cpp) # 链接到 DetourModKit(所有依赖项都会被传递链接)。 # user32 和 xinput1_4 通过 DetourModKit 的 INTERFACE 链接自动传播。 target_link_libraries(MyMod PRIVATE DetourModKit) # 添加您自己的 Mod 代码所需的任何额外系统库 if(WIN32) target_link_libraries(MyMod PRIVATE psapi kernel32) endif() 3. **在您的 GitHub Actions 工作流中(如果使用 CI):** - name: Checkout code uses: actions/checkout@v4 with: submodules: "recursive" # 这确保拉取 DetourModKit ### 方法 2:使用预构建的 DetourModKit 包 此方法使用预构建并已安装的 DetourModKit 版本。 1. **下载发布包:** MinGW 和 MSVC 的预构建包可在 [Releases](https://github.com/tkhquang/DetourModKit/releases) 页面上找到。下载与您的工具链和版本匹配的 zip 文件(例如,`DetourModKit_MinGW_v2.0.0.zip` 或 `DetourModKit_MSVC_v2.0.0.zip`)。 要升级,请下载较新的发布 zip 并替换 `external/DetourModKit/` 目录的内容。 2. **集成 DetourModKit:** * 将下载的 zip 解压到您的 Mod 项目中(例如,解压到 `external/DetourModKit/` 子目录中)。 * 或者,从源代码构建并运行 `cmake --install` 以生成相同的目录布局(请参阅 [构建](#building-detourmodkit-static-library-via-cmake))。 3. **配置您的 Mod 构建系统:** CMake # 在您的 Mod 的 CMakeLists.txt 中 cmake_minimum_required(VERSION 3.25) project(MyMod) set(CMAKE_CXX_STANDARD 23) # 查找 DetourModKit set(DetourModKit_DIR "external/DetourModKit/lib/cmake/DetourModKit") find_package(DetourModKit REQUIRED) # 创建您的 Mod 目标 add_library(MyMod SHARED src/main.cpp) # 链接到 DetourModKit。 # user32 和 xinput1_4 通过 DetourModKit 的 INTERFACE 链接自动传播。 target_link_libraries(MyMod PRIVATE DetourModKit::DetourModKit) # 添加您自己的 Mod 代码所需的任何额外系统库 if(WIN32) target_link_libraries(MyMod PRIVATE psapi kernel32) endif() Makefile(g++ MinGW 示例) # 在您的 Mod 的 Makefile 中 DETOURMODKIT_DIR := external/DetourModKit CXXFLAGS += -I$(DETOURMODKIT_DIR)/include LDFLAGS += -L$(DETOURMODKIT_DIR)/lib LIBS += -lDetourModKit -lsafetyhook -lZydis -lZycore # 添加系统库:-luser32 -lxinput1_4 是 DetourModKit 所必需的。 # 如果您自己的 Mod 代码使用它们,请添加 -lpsapi -lkernel32 等。 LIBS += -luser32 -lxinput1_4 -static-libgcc -static-libstdc++ # 示例链接命令: # $(CXX) $(YOUR_OBJECTS) -o YourMod.asi -shared $(LDFLAGS) $(LIBS) ## 代码示例 ``` // MyMod/src/main.cpp #include #include // Single include for all DetourModKit functionality #include // SafetyHook and SimpleIni are transitively available #include #include // Global variables for your mod's configuration struct ModConfiguration { bool enable_greeting_hook = true; std::string log_level_setting = "INFO"; DMKKeyComboList toggle_combo; DMKKeyComboList hold_scroll_combo; } g_mod_config; // Example Hook: Target function signature using OriginalGameFunction_PrintMessage_t = void (__stdcall *)(const char *message, int type); OriginalGameFunction_PrintMessage_t original_GameFunction_PrintMessage = nullptr; // Detour function void __stdcall Detour_GameFunction_PrintMessage(const char *message, int type) { auto &logger = DMKLogger::get_instance(); logger.info("Detour_GameFunction_PrintMessage CALLED! Original message: \"{}\", type: {}", message, type); if (g_mod_config.enable_greeting_hook) { logger.debug("Modifying message because greeting hook is enabled."); if (original_GameFunction_PrintMessage) { original_GameFunction_PrintMessage("Hello from DetourModKit! Hooked!", type + 100); } return; } if (original_GameFunction_PrintMessage) { original_GameFunction_PrintMessage(message, type); } } // Mod Initialization Function void InitializeMyMod() { // Configure the Logger DMKLogger::configure("MyMod", "MyMod.log", "%Y-%m-%d %H:%M:%S"); auto &logger = DMKLogger::get_instance(); // Enable async logging for high-throughput scenarios. // Optional: remove the block below if synchronous logging is preferred. DMKAsyncLoggerConfig async_config; async_config.queue_capacity = 8192; async_config.batch_size = 64; logger.enable_async_mode(async_config); // Register your configuration variables (using callback-based API) DMKConfig::register_bool("Hooks", "EnableGreetingHook", "Enable Greeting Hook", [](bool v) { g_mod_config.enable_greeting_hook = v; }, true); DMKConfig::register_string("Debug", "LogLevel", "Log Level", [](const std::string &v) { g_mod_config.log_level_setting = v; }, "INFO"); // Register hotkey bindings from INI (modifier+trigger format) // Comma separates independent combos: "F3,Gamepad_LT+Gamepad_B" (F3 OR LT+B) // Plus separates modifiers from trigger: "Ctrl+Shift+F3" (AND for modifiers, last = trigger) // Hex VK codes still work: "0x72", "0x11+0x72" // Mouse: "Mouse4", "Ctrl+Mouse1" // Gamepad: "Gamepad_A", "Gamepad_LB+Gamepad_A" DMKConfig::register_key_combo("Hotkeys", "ToggleKey", "Toggle Keys", [](const DMKKeyComboList &c) { g_mod_config.toggle_combo = c; }, "F3"); DMKConfig::register_key_combo("Hotkeys", "HoldScrollKey", "Hold Scroll Keys", [](const DMKKeyComboList &c) { g_mod_config.hold_scroll_combo = c; }, ""); // Load configuration from INI file DMKConfig::load("MyMod.ini"); // Apply LogLevel from loaded configuration logger.set_log_level(DMKLogger::string_to_log_level(g_mod_config.log_level_setting)); // Log the loaded configuration logger.info("MyMod configuration loaded and applied."); DMKConfig::log_all(); // Initialize Hooks auto &hook_manager = DMKHookManager::get_instance(); uintptr_t target_function_address = 0; // Example: AOB Scan const HMODULE game_module = GetModuleHandleA(nullptr); if (game_module) { MODULEINFO module_info{}; if (GetModuleInformation(GetCurrentProcess(), game_module, &module_info, sizeof(module_info))) { logger.debug("Scanning module at {} size {}", DMKFormat::format_address(reinterpret_cast(module_info.lpBaseOfDll)), module_info.SizeOfImage); // Replace with actual AOB pattern from your target game const std::string aob_sig_str = "48 89 ?? ?? 57"; const ptrdiff_t pattern_offset = 0; const auto pattern = DMKScanner::parse_aob(aob_sig_str); if (pattern.has_value()) { const std::byte *found_pattern = DMKScanner::find_pattern( reinterpret_cast(module_info.lpBaseOfDll), module_info.SizeOfImage, *pattern ); if (found_pattern) { target_function_address = reinterpret_cast(found_pattern) + pattern_offset; logger.info("Pattern found at: {}, target address: {}", DMKFormat::format_address(reinterpret_cast(found_pattern)), DMKFormat::format_address(target_function_address)); } else { logger.error("AOB pattern not found in target module."); } } else { logger.error("Failed to parse AOB pattern: {}", aob_sig_str); } } else { logger.error("GetModuleInformation failed: {}", GetLastError()); } } else { logger.error("Failed to get game module handle."); } if (target_function_address != 0) { const DMKHookConfig hook_cfg; auto result = hook_manager.create_inline_hook( "GameFunction_PrintMessage_Hook", target_function_address, reinterpret_cast(Detour_GameFunction_PrintMessage), reinterpret_cast(&original_GameFunction_PrintMessage), hook_cfg ); if (result.has_value()) { logger.info("Successfully created hook: {}", result.value()); } else { logger.error("Failed to create hook: {}", DMK::Hook::error_to_string(result.error())); } } else { logger.warning("Target address is 0 or not found. Hook not created."); } // Register hotkey bindings with the InputManager (after hooks are ready). // register_press/register_hold accept a KeyComboList directly. One binding // is created per combo, all sharing the same name for OR-logic queries. auto &input_mgr = DMKInputManager::get_instance(); input_mgr.register_press("toggle_view", g_mod_config.toggle_combo, []() { DMKLogger::get_instance().info("Toggle key pressed!"); }); input_mgr.register_hold("hold_scroll", g_mod_config.hold_scroll_combo, [](bool held) { DMKLogger::get_instance().info("Hold scroll: {}", held ? "active" : "released"); }); // Start the input polling thread (focus-aware by default) input_mgr.start(); logger.info("MyMod Initialized using DetourModKit!"); } // Mod Shutdown Function void ShutdownMyMod() { DMKLogger::get_instance().info("MyMod Shutting Down..."); // Shuts down all singletons in correct dependency order: // InputManager -> HookManager -> Memory cache -> Config -> Logger DMK_Shutdown(); } // IMPORTANT: Offload initialization to a worker thread so start() runs // outside DllMain, and call DMK_Shutdown() before DLL_PROCESS_DETACH. static DWORD WINAPI InitThread(LPVOID) { InitializeMyMod(); return 0; } BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { if (ul_reason_for_call == DLL_PROCESS_ATTACH) { DisableThreadLibraryCalls(hModule); CloseHandle(CreateThread(nullptr, 0, InitThread, nullptr, 0, nullptr)); } else if (ul_reason_for_call == DLL_PROCESS_DETACH) { ShutdownMyMod(); } return TRUE; } ``` ## 配置文件示例 在您的 DLL 旁边创建一个 `MyMod.ini` 文件: ``` [Hooks] EnableGreetingHook=true [Debug] LogLevel=INFO [Hotkeys] ; Named keys (recommended) ToggleKey=F3 ; Single key HoldScrollKey=LShift ; Left Shift DebugCombo=Ctrl+Shift+D ; Ctrl AND Shift AND D (plus = AND for modifiers, last = trigger) ; Multiple independent combos (comma = OR between combos) DualInput=F3,Gamepad_LT+Gamepad_B ; F3 alone OR (hold LT + press B) MultiCombo=Ctrl+F3,Ctrl+F4 ; Ctrl+F3 OR Ctrl+F4 ; Mouse buttons AimToggle=Mouse4 ; Mouse button 4 (side button) QuickAction=Ctrl+Mouse1 ; Ctrl + Left click ; Gamepad buttons (XInput) GamepadToggle=Gamepad_A ; A button GamepadCombo=Gamepad_LB+Gamepad_A ; LB (modifier) + A (trigger) GamepadTrigger=Gamepad_LT ; Left trigger (digital, configurable deadzone) ; Hex VK codes still supported LegacyKey=0x72 ; F3 by hex code LegacyCombo=0x11+0x10+0x44 ; Ctrl+Shift+D by hex codes ``` ## 支持的输入名称 配置系统识别以下命名输入代码(不区分大小写): | 类别 | 名称 | | --- | --- | | **修饰键** | `Ctrl`, `LCtrl`, `RCtrl`, `Shift`, `LShift`, `RShift`, `Alt`, `LAlt`, `RAlt` | | **字母** | `A`–`Z` | | **数字** | `0`–`9` | | **功能键** | `F1`–`F24` | | **导航键** | `Left`, `Right`, `Up`, `Down`, `Home`, `End`, `PageUp`, `PageDown`, `Insert`, `Delete` | | **常用键** | `Space`, `Enter`, `Escape`, `Tab`, `Backspace`, `CapsLock`, `NumLock`, `ScrollLock`, `PrintScreen`, `Pause` | | **小键盘** | `Numpad0`–`Numpad9`, `NumpadAdd`, `NumpadSubtract`, `NumpadMultiply`, `NumpadDivide`, `NumpadDecimal` | | **鼠标** | `Mouse1` (左键), `Mouse2` (右键), `Mouse3` (中键), `Mouse4`, `Mouse5` | | **手柄** | `Gamepad_A`, `Gamepad_B`, `Gamepad_X`, `Gamepad_Y`, `Gamepad_LB`, `Gamepad_RB`, `Gamepad_LT`, `Gamepad_RT`, `Gamepad_Start`, `Gamepad_Back`, `Gamepad_LS`, `Gamepad_RS`, `Gamepad_DpadUp`, `Gamepad_DpadDown`, `Gamepad_DpadLeft`, `Gamepad_DpadRight` | | **手柄摇杆** | `Gamepad_LSUp`, `Gamepad_LSDown`, `Gamepad_LSLeft`, `Gamepad_LSRight`, `Gamepad_RSUp`, `Gamepad_RSDown`, `Gamepad_RSLeft`, `Gamepad_RSRight` | 带有 `0x` 前缀的十六进制 VK 代码(例如,F3 为 `0x72`)也被接受,并且默认为键盘输入。 ## 手柄兼容性 手柄支持使用 **XInput** API。以下控制器原生支持: | 控制器 | 支持 | | --- | --- | | Xbox 360 | 是(原生 XInput) | | Xbox One / Series X\|S | 是(原生 XInput) | | GameSir(XInput 模式) | 是(将控制器切换到 XInput 模式) | | PS4 DualShock 4 | 通过 [DS4Windows](https://github.com/ds4windowsapp/DS4Windows) 或 Steam Input | | PS5 DualSense | 通过 [DualSenseX](https://github.com/Paliverse/DualSenseX) 或 Steam Input | | Nintendo Switch Pro | 通过 [BetterJoy](https://github.com/Davidobot/BetterJoy) 或 Steam Input | | 通用 USB 手柄 | 仅当控制器公开 XInput 接口时 | **为什么仅支持 XInput?** DetourModKit 的输入系统专为 Mod 热键和切换而设计,而非用于替代游戏的主要输入处理。XInput 原生覆盖 Xbox 控制器,绝大多数使用非 Xbox 控制器的 PC 玩家已经使用 Steam Input 或类似的重新映射工具,将其控制器呈现为 XInput。添加 DirectInput 或 Windows.Gaming.Input 会显著增加复杂性,而在 XInput + 键盘/鼠标几乎覆盖所有真实用户的用例中,这是不必要的。 **限制:** * 最多支持 4 个控制器(XInput 硬限制,索引 0-3)。 * 模拟扳机(LT/RT)和摇杆轴被视为具有可配置死区阈值的数字按钮。 * 没有事件驱动的热插拔检测;控制器连接通过轮询检查(断开连接时,重连尝试限制为每 2 秒一次)。 * **Shift + 小键盘键:** 当按住 Shift 时,Windows将小键盘键转换为其等效的导航键(例如,`Numpad5` 变为 `VK_CLEAR` 而不是 `VK_NUMPAD5`)。这意味着像 `LShift+Numpad5` 这样的组合永远不会触发,因为 `GetAsyncKeyState` 看到的是转换后的 VK 代码,而不是原始的小键盘代码。**解决方法:** 对于小键盘组合,请使用 `Ctrl` 或 `Alt` 代替 `Shift`,或者使用非小键盘键。([更多信息](https://learn.microsoft.com/en-us/answers/questions/3935239/how-to-make-it-so-left-shift-doesnt-affect-number)) ## 使用 DetourModKit 的项目 有关实际参考和真实使用示例: * **OBR-NoCarryWeight**: [https://github.com/tkhquang/OBRTools/tree/main/NoCarryWeight](https://github.com/tkhquang/OBRTools/tree/main/NoCarryWeight) * **KCD1-TPVToggle**: [https://github.com/tkhquang/KCD1Tools/tree/main/TPVToggle](https://github.com/tkhquang/KCD1Tools/tree/main/TPVToggle) * **KCD2-TPVToggle**: [https://github.com/tkhquang/KCD2Tools/tree/main/TPVToggle](https://github.com/tkhquang/KCD2Tools/tree/main/TPVToggle) * **CrimsonDesert-EquipHide**: [https://github.com/tkhquang/CrimsonDesertTools/tree/main/CrimsonDesertEquipHide](https://github.com/tkhquang/CrimsonDesertTools/tree/main/CrimsonDesertEquipHide) ## 致谢 DetourModKit 包含了来自其他开源项目的组件。有关完整详细信息,请参阅 [DetourModKit_Acknowledgements.txt](DetourModKit_Acknowledgements.txt)。 * [SafetyHook](https://github.com/cursey/safetyhook) (Boost Software License 1.0) * [SimpleIni](https://github.com/brofield/simpleini) (MIT) * [DirectXMath](https://github.com/microsoft/DirectXMath) (MIT) * [Zydis & Zycore](https://github.com/zyantific/zydis) (MIT)
标签:AOB扫描, Bash脚本, C++, C++23, Chrome Tracing, CMake, ETW劫持, Hook, INI, Linux, MinGW, MIT协议, SIMD, SOC Prime, VMT Hook, 事件分发, 云资产清单, 内存安全, 内存搜索, 内存操作, 内联钩子, 工具库, 开发工具, 异步日志, 性能分析, 数据擦除, 文件系统, 日志记录, 游戏修改, 游戏模组, 游戏辅助, 热键, 特征码扫描, 输入系统, 逆向工程, 静态库