include/DetourModKit/hook_manager.hpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #ifndef DETOURMODKIT_HOOK_MANAGER_HPP | ||
| 2 | #define DETOURMODKIT_HOOK_MANAGER_HPP | ||
| 3 | |||
| 4 | #include <string> | ||
| 5 | #include <unordered_map> | ||
| 6 | #include <cstdint> | ||
| 7 | #include <functional> | ||
| 8 | #include <memory> | ||
| 9 | #include <shared_mutex> | ||
| 10 | #include <optional> | ||
| 11 | #include <expected> | ||
| 12 | #include <string_view> | ||
| 13 | #include <type_traits> | ||
| 14 | #include <concepts> | ||
| 15 | #include <atomic> | ||
| 16 | #include <cassert> | ||
| 17 | #include <utility> | ||
| 18 | #include <format> | ||
| 19 | |||
| 20 | #include "safetyhook.hpp" | ||
| 21 | #include "DetourModKit/logger.hpp" | ||
| 22 | #include "DetourModKit/scanner.hpp" | ||
| 23 | #include "DetourModKit/format.hpp" | ||
| 24 | |||
| 25 | namespace DetourModKit | ||
| 26 | { | ||
| 27 | namespace detail | ||
| 28 | { | ||
| 29 | /** | ||
| 30 | * @brief Transparent hash functor for heterogeneous lookup in string-keyed maps. | ||
| 31 | * @details Allows std::string_view lookups without constructing a temporary std::string. | ||
| 32 | */ | ||
| 33 | struct TransparentStringHash | ||
| 34 | { | ||
| 35 | using is_transparent = void; | ||
| 36 | 42740 | size_t operator()(std::string_view sv) const noexcept { return std::hash<std::string_view>{}(sv); } | |
| 37 | }; | ||
| 38 | } // namespace detail | ||
| 39 | |||
| 40 | /** | ||
| 41 | * @enum HookType | ||
| 42 | * @brief Enumeration of supported hook types, corresponding to SafetyHook capabilities. | ||
| 43 | */ | ||
| 44 | enum class HookType | ||
| 45 | { | ||
| 46 | Inline, | ||
| 47 | Mid, | ||
| 48 | Vmt | ||
| 49 | }; | ||
| 50 | |||
| 51 | /** | ||
| 52 | * @enum HookStatus | ||
| 53 | * @brief Represents the current operational status of a managed hook. | ||
| 54 | */ | ||
| 55 | enum class HookStatus | ||
| 56 | { | ||
| 57 | Active, | ||
| 58 | Disabled, | ||
| 59 | Enabling, | ||
| 60 | Disabling | ||
| 61 | }; | ||
| 62 | |||
| 63 | /** | ||
| 64 | * @enum HookError | ||
| 65 | * @brief Error codes for hook creation/operation failures. | ||
| 66 | */ | ||
| 67 | enum class HookError | ||
| 68 | { | ||
| 69 | AllocatorNotAvailable, | ||
| 70 | InvalidTargetAddress, | ||
| 71 | InvalidDetourFunction, | ||
| 72 | InvalidTrampolinePointer, | ||
| 73 | HookAlreadyExists, | ||
| 74 | HookNotFound, | ||
| 75 | ShutdownInProgress, | ||
| 76 | SafetyHookError, | ||
| 77 | EnableFailed, | ||
| 78 | DisableFailed, | ||
| 79 | InvalidHookState, | ||
| 80 | InvalidObject, | ||
| 81 | VmtHookNotFound, | ||
| 82 | MethodAlreadyHooked, | ||
| 83 | MethodNotFound, | ||
| 84 | TargetAlreadyHookedInProcess, | ||
| 85 | UnknownError | ||
| 86 | }; | ||
| 87 | |||
| 88 | /** | ||
| 89 | * @struct HookConfig | ||
| 90 | * @brief Configuration options used during the creation of a new hook. | ||
| 91 | */ | ||
| 92 | struct HookConfig | ||
| 93 | { | ||
| 94 | bool auto_enable = true; | ||
| 95 | |||
| 96 | /** | ||
| 97 | * @brief When true, refuse to inline-hook a target whose first bytes | ||
| 98 | * already encode a JMP outside the target's module. | ||
| 99 | * @details Default false preserves backwards-compatible behaviour: | ||
| 100 | * a warning is logged but the hook proceeds (SafetyHook | ||
| 101 | * layers trampolines on top of existing inline hooks). Set | ||
| 102 | * to true for strict mods that never want to install a | ||
| 103 | * second hook behind another mod's. | ||
| 104 | */ | ||
| 105 | bool fail_if_already_hooked = false; | ||
| 106 | }; | ||
| 107 | |||
| 108 | /** | ||
| 109 | * @class Hook | ||
| 110 | * @brief Abstract base class for managed hooks. | ||
| 111 | * @details Defines a common interface for interacting with different types of hooks | ||
| 112 | * managed by the HookManager. Implements the Template Method pattern for | ||
| 113 | * enable/disable state management. | ||
| 114 | */ | ||
| 115 | class Hook | ||
| 116 | { | ||
| 117 | public: | ||
| 118 | 96 | virtual ~Hook() = default; | |
| 119 | |||
| 120 | 30 | const std::string &get_name() const noexcept { return m_name; } | |
| 121 | 57 | HookType get_type() const noexcept { return m_type; } | |
| 122 | 12 | uintptr_t get_target_address() const noexcept { return m_target_address; } | |
| 123 | 42185 | HookStatus get_status() const noexcept { return m_status.load(std::memory_order_acquire); } | |
| 124 | |||
| 125 | /** | ||
| 126 | * @brief Enables the hook. | ||
| 127 | * @return Success if the hook was enabled or already active. On failure, the | ||
| 128 | * HookError indicates the reason (SafetyHookError, EnableFailed, InvalidHookState). | ||
| 129 | * @note Uses atomic CAS for lock-free status transitions. Thread-safe without | ||
| 130 | * requiring external synchronization. Uses an intermediate Enabling state | ||
| 131 | * to prevent other threads from observing a speculative terminal state | ||
| 132 | * while the SafetyHook enable call is in progress. | ||
| 133 | */ | ||
| 134 | 410 | [[nodiscard]] std::expected<void, HookError> enable() | |
| 135 | { | ||
| 136 |
1/2✗ Branch 3 → 4 not taken.
✓ Branch 3 → 8 taken 410 times.
|
410 | if (!is_impl_valid()) |
| 137 | ✗ | return std::unexpected(HookError::SafetyHookError); | |
| 138 | |||
| 139 | 410 | HookStatus expected = HookStatus::Disabled; | |
| 140 |
2/2✓ Branch 9 → 10 taken 329 times.
✓ Branch 9 → 18 taken 81 times.
|
410 | if (!m_status.compare_exchange_strong(expected, HookStatus::Enabling, std::memory_order_acq_rel)) |
| 141 | { | ||
| 142 |
2/2✓ Branch 10 → 11 taken 36 times.
✓ Branch 10 → 14 taken 293 times.
|
329 | if (expected == HookStatus::Active) |
| 143 | 36 | return {}; | |
| 144 | 293 | return std::unexpected(HookError::InvalidHookState); | |
| 145 | } | ||
| 146 | |||
| 147 |
2/4✓ Branch 18 → 19 taken 81 times.
✗ Branch 18 → 31 not taken.
✓ Branch 19 → 20 taken 81 times.
✗ Branch 19 → 24 not taken.
|
81 | if (do_enable()) |
| 148 | { | ||
| 149 | 81 | m_status.store(HookStatus::Active, std::memory_order_release); | |
| 150 | 81 | return {}; | |
| 151 | } | ||
| 152 | |||
| 153 | ✗ | m_status.store(HookStatus::Disabled, std::memory_order_release); | |
| 154 | ✗ | return std::unexpected(HookError::EnableFailed); | |
| 155 | } | ||
| 156 | |||
| 157 | /** | ||
| 158 | * @brief Disables the hook. | ||
| 159 | * @return Success if the hook was disabled or already disabled. On failure, the | ||
| 160 | * HookError indicates the reason (SafetyHookError, DisableFailed, InvalidHookState). | ||
| 161 | * @note Uses atomic CAS for lock-free status transitions. Thread-safe without | ||
| 162 | * requiring external synchronization. Uses an intermediate Disabling state | ||
| 163 | * to prevent other threads from observing a speculative terminal state | ||
| 164 | * while the SafetyHook disable call is in progress. | ||
| 165 | */ | ||
| 166 | 508 | [[nodiscard]] std::expected<void, HookError> disable() | |
| 167 | { | ||
| 168 |
1/2✗ Branch 3 → 4 not taken.
✓ Branch 3 → 8 taken 508 times.
|
508 | if (!is_impl_valid()) |
| 169 | ✗ | return std::unexpected(HookError::SafetyHookError); | |
| 170 | |||
| 171 | 508 | HookStatus expected = HookStatus::Active; | |
| 172 |
2/2✓ Branch 9 → 10 taken 335 times.
✓ Branch 9 → 18 taken 173 times.
|
508 | if (!m_status.compare_exchange_strong(expected, HookStatus::Disabling, std::memory_order_acq_rel)) |
| 173 | { | ||
| 174 |
2/2✓ Branch 10 → 11 taken 61 times.
✓ Branch 10 → 14 taken 274 times.
|
335 | if (expected == HookStatus::Disabled) |
| 175 | 61 | return {}; | |
| 176 | 274 | return std::unexpected(HookError::InvalidHookState); | |
| 177 | } | ||
| 178 | |||
| 179 |
2/4✓ Branch 18 → 19 taken 173 times.
✗ Branch 18 → 31 not taken.
✓ Branch 19 → 20 taken 173 times.
✗ Branch 19 → 24 not taken.
|
173 | if (do_disable()) |
| 180 | { | ||
| 181 | 173 | m_status.store(HookStatus::Disabled, std::memory_order_release); | |
| 182 | 173 | return {}; | |
| 183 | } | ||
| 184 | |||
| 185 | ✗ | m_status.store(HookStatus::Active, std::memory_order_release); | |
| 186 | ✗ | return std::unexpected(HookError::DisableFailed); | |
| 187 | } | ||
| 188 | |||
| 189 | bool is_enabled() const noexcept { return m_status.load(std::memory_order_acquire) == HookStatus::Active; } | ||
| 190 | |||
| 191 | 572 | static constexpr std::string_view status_to_string(HookStatus status) noexcept | |
| 192 | { | ||
| 193 |
5/5✓ Branch 2 → 3 taken 1 time.
✓ Branch 2 → 4 taken 1 time.
✓ Branch 2 → 5 taken 279 times.
✓ Branch 2 → 6 taken 290 times.
✓ Branch 2 → 7 taken 1 time.
|
572 | switch (status) |
| 194 | { | ||
| 195 | 1 | case HookStatus::Active: | |
| 196 | 1 | return "Active"; | |
| 197 | 1 | case HookStatus::Disabled: | |
| 198 | 1 | return "Disabled"; | |
| 199 | 279 | case HookStatus::Enabling: | |
| 200 | 279 | return "Enabling"; | |
| 201 | 290 | case HookStatus::Disabling: | |
| 202 | 290 | return "Disabling"; | |
| 203 | 1 | default: | |
| 204 | 1 | return "Unknown"; | |
| 205 | } | ||
| 206 | } | ||
| 207 | |||
| 208 | 33 | static constexpr std::string_view error_to_string(HookError error) noexcept | |
| 209 | { | ||
| 210 |
16/18✓ Branch 2 → 3 taken 3 times.
✓ Branch 2 → 4 taken 2 times.
✓ Branch 2 → 5 taken 2 times.
✓ Branch 2 → 6 taken 2 times.
✓ Branch 2 → 7 taken 2 times.
✓ Branch 2 → 8 taken 4 times.
✓ Branch 2 → 9 taken 4 times.
✓ Branch 2 → 10 taken 2 times.
✓ Branch 2 → 11 taken 1 time.
✓ Branch 2 → 12 taken 1 time.
✓ Branch 2 → 13 taken 1 time.
✓ Branch 2 → 14 taken 2 times.
✓ Branch 2 → 15 taken 2 times.
✓ Branch 2 → 16 taken 2 times.
✓ Branch 2 → 17 taken 1 time.
✗ Branch 2 → 18 not taken.
✓ Branch 2 → 19 taken 2 times.
✗ Branch 2 → 20 not taken.
|
33 | switch (error) |
| 211 | { | ||
| 212 | 3 | case HookError::AllocatorNotAvailable: | |
| 213 | 3 | return "Allocator not available"; | |
| 214 | 2 | case HookError::InvalidTargetAddress: | |
| 215 | 2 | return "Invalid target address"; | |
| 216 | 2 | case HookError::InvalidDetourFunction: | |
| 217 | 2 | return "Invalid detour function"; | |
| 218 | 2 | case HookError::InvalidTrampolinePointer: | |
| 219 | 2 | return "Invalid trampoline pointer"; | |
| 220 | 2 | case HookError::HookAlreadyExists: | |
| 221 | 2 | return "Hook already exists"; | |
| 222 | 4 | case HookError::HookNotFound: | |
| 223 | 4 | return "Hook not found"; | |
| 224 | 4 | case HookError::ShutdownInProgress: | |
| 225 | 4 | return "Shutdown in progress"; | |
| 226 | 2 | case HookError::SafetyHookError: | |
| 227 | 2 | return "SafetyHook error"; | |
| 228 | 1 | case HookError::EnableFailed: | |
| 229 | 1 | return "Hook enable failed"; | |
| 230 | 1 | case HookError::DisableFailed: | |
| 231 | 1 | return "Hook disable failed"; | |
| 232 | 1 | case HookError::InvalidHookState: | |
| 233 | 1 | return "Hook is in a transitional state"; | |
| 234 | 2 | case HookError::InvalidObject: | |
| 235 | 2 | return "Invalid object pointer"; | |
| 236 | 2 | case HookError::VmtHookNotFound: | |
| 237 | 2 | return "VMT hook not found"; | |
| 238 | 2 | case HookError::MethodAlreadyHooked: | |
| 239 | 2 | return "VMT method already hooked"; | |
| 240 | 1 | case HookError::MethodNotFound: | |
| 241 | 1 | return "VMT method hook not found"; | |
| 242 | ✗ | case HookError::TargetAlreadyHookedInProcess: | |
| 243 | ✗ | return "Target address is already inline-hooked by another module"; | |
| 244 | 2 | case HookError::UnknownError: | |
| 245 | 2 | return "Unknown error"; | |
| 246 | ✗ | default: | |
| 247 | ✗ | return "Invalid error code"; | |
| 248 | } | ||
| 249 | } | ||
| 250 | |||
| 251 | protected: | ||
| 252 | std::string m_name; | ||
| 253 | HookType m_type; | ||
| 254 | uintptr_t m_target_address; | ||
| 255 | std::atomic<HookStatus> m_status; | ||
| 256 | |||
| 257 | 96 | Hook(std::string name, HookType type, uintptr_t target_address, HookStatus initial_status) | |
| 258 | 192 | : m_name(std::move(name)), m_type(type), m_target_address(target_address), m_status(initial_status) {} | |
| 259 | |||
| 260 | virtual bool is_impl_valid() const noexcept = 0; | ||
| 261 | virtual bool do_enable() = 0; | ||
| 262 | virtual bool do_disable() = 0; | ||
| 263 | |||
| 264 | Hook(const Hook &) = delete; | ||
| 265 | Hook &operator=(const Hook &) = delete; | ||
| 266 | Hook(Hook &&) = delete; | ||
| 267 | Hook &operator=(Hook &&) = delete; | ||
| 268 | }; | ||
| 269 | |||
| 270 | /** | ||
| 271 | * @class InlineHook | ||
| 272 | * @brief Represents a managed inline hook, wrapping a SafetyHook::InlineHook object. | ||
| 273 | */ | ||
| 274 | class InlineHook : public Hook | ||
| 275 | { | ||
| 276 | public: | ||
| 277 | 74 | InlineHook(std::string name, uintptr_t target_address, | |
| 278 | safetyhook::InlineHook hook_obj, | ||
| 279 | HookStatus initial_status) | ||
| 280 | 148 | : Hook(std::move(name), HookType::Inline, target_address, initial_status), | |
| 281 | 222 | m_safetyhook_impl(std::move(hook_obj)) {} | |
| 282 | |||
| 283 | /** | ||
| 284 | * @brief Retrieves the trampoline to call the original function. | ||
| 285 | * @tparam T The function pointer type of the original function. | ||
| 286 | * @return A function pointer of type T to the original function's trampoline. | ||
| 287 | */ | ||
| 288 | template <typename T> | ||
| 289 | 2 | T get_original() const noexcept | |
| 290 | { | ||
| 291 |
1/2✓ Branch 3 → 4 taken 2 times.
✗ Branch 3 → 5 not taken.
|
2 | return m_safetyhook_impl ? m_safetyhook_impl.original<T>() : nullptr; |
| 292 | } | ||
| 293 | |||
| 294 | protected: | ||
| 295 | 889 | bool is_impl_valid() const noexcept override { return static_cast<bool>(m_safetyhook_impl); } | |
| 296 | 78 | bool do_enable() override | |
| 297 | { | ||
| 298 |
1/2✓ Branch 2 → 3 taken 78 times.
✗ Branch 2 → 6 not taken.
|
78 | auto result = m_safetyhook_impl.enable(); |
| 299 | 156 | return result.has_value(); | |
| 300 | } | ||
| 301 | 150 | bool do_disable() override | |
| 302 | { | ||
| 303 |
1/2✓ Branch 2 → 3 taken 150 times.
✗ Branch 2 → 6 not taken.
|
150 | auto result = m_safetyhook_impl.disable(); |
| 304 | 300 | return result.has_value(); | |
| 305 | } | ||
| 306 | |||
| 307 | private: | ||
| 308 | safetyhook::InlineHook m_safetyhook_impl; | ||
| 309 | }; | ||
| 310 | |||
| 311 | /** | ||
| 312 | * @class MidHook | ||
| 313 | * @brief Represents a managed mid-function hook, wrapping a SafetyHook::MidHook object. | ||
| 314 | */ | ||
| 315 | class MidHook : public Hook | ||
| 316 | { | ||
| 317 | public: | ||
| 318 | 22 | MidHook(std::string name, uintptr_t target_address, | |
| 319 | safetyhook::MidHook hook_obj, | ||
| 320 | HookStatus initial_status) | ||
| 321 | 44 | : Hook(std::move(name), HookType::Mid, target_address, initial_status), | |
| 322 | 66 | m_safetyhook_impl(std::move(hook_obj)) {} | |
| 323 | |||
| 324 | /** | ||
| 325 | * @brief Gets the destination function of this mid-hook. | ||
| 326 | * @return safetyhook::MidHookFn The function pointer to the detour. | ||
| 327 | */ | ||
| 328 | 1 | safetyhook::MidHookFn get_destination() const noexcept | |
| 329 | { | ||
| 330 |
1/2✓ Branch 3 → 4 taken 1 time.
✗ Branch 3 → 5 not taken.
|
1 | return m_safetyhook_impl ? m_safetyhook_impl.destination() : nullptr; |
| 331 | } | ||
| 332 | |||
| 333 | protected: | ||
| 334 | 29 | bool is_impl_valid() const noexcept override { return static_cast<bool>(m_safetyhook_impl); } | |
| 335 | 3 | bool do_enable() override | |
| 336 | { | ||
| 337 |
1/2✓ Branch 2 → 3 taken 3 times.
✗ Branch 2 → 6 not taken.
|
3 | auto result = m_safetyhook_impl.enable(); |
| 338 | 6 | return result.has_value(); | |
| 339 | } | ||
| 340 | 23 | bool do_disable() override | |
| 341 | { | ||
| 342 |
1/2✓ Branch 2 → 3 taken 23 times.
✗ Branch 2 → 6 not taken.
|
23 | auto result = m_safetyhook_impl.disable(); |
| 343 | 46 | return result.has_value(); | |
| 344 | } | ||
| 345 | |||
| 346 | private: | ||
| 347 | safetyhook::MidHook m_safetyhook_impl; | ||
| 348 | }; | ||
| 349 | |||
| 350 | /** | ||
| 351 | * @class VmtHookEntry | ||
| 352 | * @brief Manages a VMT hook for a single object class, wrapping SafetyHook's VmtHook. | ||
| 353 | * @details Owns the cloned vtable and tracks individual method hooks by vtable index. | ||
| 354 | * VMT hooks operate at the object level by replacing the vptr with a cloned | ||
| 355 | * vtable. Individual methods are hooked by index within the cloned table. | ||
| 356 | * Does not support enable/disable toggling (SafetyHook VmHook limitation). | ||
| 357 | */ | ||
| 358 | class VmtHookEntry | ||
| 359 | { | ||
| 360 | public: | ||
| 361 | 21 | VmtHookEntry(std::string name, safetyhook::VmtHook vmt_hook) | |
| 362 | 63 | : m_name(std::move(name)), m_vmt_hook(std::move(vmt_hook)) {} | |
| 363 | |||
| 364 | 1 | const std::string &get_name() const noexcept { return m_name; } | |
| 365 | |||
| 366 | 9 | safetyhook::VmtHook &vmt_hook() noexcept { return m_vmt_hook; } | |
| 367 | const safetyhook::VmtHook &vmt_hook() const noexcept { return m_vmt_hook; } | ||
| 368 | |||
| 369 |
1/2✓ Branch 3 → 4 taken 8 times.
✗ Branch 3 → 8 not taken.
|
8 | bool has_method_hook(size_t index) const { return m_method_hooks.find(index) != m_method_hooks.end(); } |
| 370 | |||
| 371 | 7 | safetyhook::VmHook *get_method_hook(size_t index) | |
| 372 | { | ||
| 373 |
1/2✓ Branch 2 → 3 taken 7 times.
✗ Branch 2 → 12 not taken.
|
7 | auto it = m_method_hooks.find(index); |
| 374 |
2/2✓ Branch 5 → 6 taken 6 times.
✓ Branch 5 → 8 taken 1 time.
|
7 | return it != m_method_hooks.end() ? &it->second : nullptr; |
| 375 | } | ||
| 376 | |||
| 377 | const safetyhook::VmHook *get_method_hook(size_t index) const | ||
| 378 | { | ||
| 379 | auto it = m_method_hooks.find(index); | ||
| 380 | return it != m_method_hooks.end() ? &it->second : nullptr; | ||
| 381 | } | ||
| 382 | |||
| 383 | 7 | void add_method_hook(size_t index, safetyhook::VmHook hook) | |
| 384 | { | ||
| 385 |
1/2✓ Branch 4 → 5 taken 7 times.
✗ Branch 4 → 6 not taken.
|
14 | m_method_hooks.emplace(index, std::move(hook)); |
| 386 | 7 | } | |
| 387 | |||
| 388 | 2 | bool remove_method_hook(size_t index) | |
| 389 | { | ||
| 390 | 2 | return m_method_hooks.erase(index) > 0; | |
| 391 | } | ||
| 392 | |||
| 393 | size_t method_hook_count() const noexcept { return m_method_hooks.size(); } | ||
| 394 | |||
| 395 | VmtHookEntry(const VmtHookEntry &) = delete; | ||
| 396 | VmtHookEntry &operator=(const VmtHookEntry &) = delete; | ||
| 397 | VmtHookEntry(VmtHookEntry &&) = default; | ||
| 398 | VmtHookEntry &operator=(VmtHookEntry &&) = default; | ||
| 399 | |||
| 400 | private: | ||
| 401 | std::string m_name; | ||
| 402 | safetyhook::VmtHook m_vmt_hook; | ||
| 403 | std::unordered_map<size_t, safetyhook::VmHook> m_method_hooks; | ||
| 404 | }; | ||
| 405 | |||
| 406 | namespace detail | ||
| 407 | { | ||
| 408 | /** | ||
| 409 | * @brief Container type for the inline / mid hook registry, keyed by hook name. | ||
| 410 | * @details Centralized once so every site that references this exact instantiation sees identical template arguments. | ||
| 411 | */ | ||
| 412 | using HookMap = std::unordered_map<std::string, std::unique_ptr<Hook>, TransparentStringHash, std::equal_to<>>; | ||
| 413 | |||
| 414 | /** | ||
| 415 | * @brief Container type for the VMT hook registry, keyed by hook name. | ||
| 416 | * @details Centralized once so every site that references this exact instantiation sees identical template arguments. | ||
| 417 | */ | ||
| 418 | using VmtHookMap = std::unordered_map<std::string, VmtHookEntry, TransparentStringHash, std::equal_to<>>; | ||
| 419 | } // namespace detail | ||
| 420 | |||
| 421 | /** | ||
| 422 | * @class HookManager | ||
| 423 | * @brief Manages the lifecycle of all hooks (Inline, Mid, and VMT) using SafetyHook. | ||
| 424 | * @details Provides a centralized API for creating, removing, enabling, and disabling hooks. | ||
| 425 | * Thread-safe for all public methods. Uses std::expected for explicit error handling. | ||
| 426 | * @note Lock ordering: 1. m_mutator_gate (shared or exclusive) then 2. m_hooks_mutex (shared or exclusive). | ||
| 427 | * Mutators (create_*_hook, enable, disable, remove) acquire shared m_mutator_gate first, | ||
| 428 | * then shared or exclusive m_hooks_mutex. Shutdown and remove_all_hooks acquire exclusive | ||
| 429 | * m_mutator_gate first to block new mutators, then proceed with two-phase cleanup. | ||
| 430 | */ | ||
| 431 | class HookManager | ||
| 432 | { | ||
| 433 | public: | ||
| 434 | /** | ||
| 435 | * @brief Provides access to the singleton instance of the HookManager. | ||
| 436 | * @return HookManager& Reference to the global HookManager instance. | ||
| 437 | */ | ||
| 438 | static HookManager &get_instance(); | ||
| 439 | |||
| 440 | ~HookManager() noexcept; | ||
| 441 | |||
| 442 | /** | ||
| 443 | * @brief Explicitly shuts down the HookManager, removing all hooks without logging. | ||
| 444 | * @details This method is safe to call during shutdown when Logger may be destroyed. | ||
| 445 | * It removes all hooks without attempting to log, preventing use-after-free. | ||
| 446 | * The shutdown flag is reset after hooks are cleared, allowing subsequent | ||
| 447 | * hook creation for hot-reload scenarios. The destructor becomes a no-op | ||
| 448 | * only while the flag is set during the shutdown operation itself. | ||
| 449 | */ | ||
| 450 | void shutdown(); | ||
| 451 | |||
| 452 | // Non-copyable, non-movable (mutex member) | ||
| 453 | HookManager(const HookManager &) = delete; | ||
| 454 | HookManager &operator=(const HookManager &) = delete; | ||
| 455 | HookManager(HookManager &&) = delete; | ||
| 456 | HookManager &operator=(HookManager &&) = delete; | ||
| 457 | |||
| 458 | /** | ||
| 459 | * @brief Creates an inline hook at a specific target memory address. | ||
| 460 | * @param name A unique, descriptive name for the hook. | ||
| 461 | * @param target_address The memory address of the function to hook. | ||
| 462 | * @param detour_function Pointer to the detour function. | ||
| 463 | * @param original_trampoline Output pointer to receive trampoline address. | ||
| 464 | * @param config Optional configuration settings for the hook. | ||
| 465 | * @return std::expected<std::string, HookError> The hook name if successful, error code otherwise. | ||
| 466 | */ | ||
| 467 | [[nodiscard]] std::expected<std::string, HookError> create_inline_hook( | ||
| 468 | std::string_view name, | ||
| 469 | uintptr_t target_address, | ||
| 470 | void *detour_function, | ||
| 471 | void **original_trampoline, | ||
| 472 | const HookConfig &config = HookConfig()); | ||
| 473 | |||
| 474 | /** | ||
| 475 | * @brief Creates an inline hook by finding target address via AOB scan. | ||
| 476 | * @param name A unique, descriptive name for the hook. | ||
| 477 | * @param module_base Base address of the memory module to scan. | ||
| 478 | * @param module_size Size of the memory module to scan. | ||
| 479 | * @param aob_pattern_str The AOB pattern string. | ||
| 480 | * @param aob_offset Offset to add to the found pattern's address. | ||
| 481 | * @param detour_function Pointer to the detour function. | ||
| 482 | * @param original_trampoline Output pointer to store trampoline address. | ||
| 483 | * @param config Optional configuration settings for the hook. | ||
| 484 | * @return std::expected<std::string, HookError> The hook name if successful, error code otherwise. | ||
| 485 | */ | ||
| 486 | [[nodiscard]] std::expected<std::string, HookError> create_inline_hook_aob( | ||
| 487 | std::string_view name, | ||
| 488 | uintptr_t module_base, | ||
| 489 | size_t module_size, | ||
| 490 | std::string_view aob_pattern_str, | ||
| 491 | ptrdiff_t aob_offset, | ||
| 492 | void *detour_function, | ||
| 493 | void **original_trampoline, | ||
| 494 | const HookConfig &config = HookConfig()); | ||
| 495 | |||
| 496 | /** | ||
| 497 | * @brief Creates a mid-function hook at a specific target memory address. | ||
| 498 | * @param name A unique, descriptive name for the hook. | ||
| 499 | * @param target_address The memory address within a function to hook. | ||
| 500 | * @param detour_function The function to be called when the mid-hook is executed. | ||
| 501 | * @param config Optional configuration settings for the hook. | ||
| 502 | * @return std::expected<std::string, HookError> The hook name if successful, error code otherwise. | ||
| 503 | */ | ||
| 504 | [[nodiscard]] std::expected<std::string, HookError> create_mid_hook( | ||
| 505 | std::string_view name, | ||
| 506 | uintptr_t target_address, | ||
| 507 | safetyhook::MidHookFn detour_function, | ||
| 508 | const HookConfig &config = HookConfig()); | ||
| 509 | |||
| 510 | /** | ||
| 511 | * @brief Creates a mid-function hook by finding target address via AOB scan. | ||
| 512 | * @param name A unique, descriptive name for the hook. | ||
| 513 | * @param module_base Base address of the memory module to scan. | ||
| 514 | * @param module_size Size of the memory module to scan. | ||
| 515 | * @param aob_pattern_str The AOB pattern string. | ||
| 516 | * @param aob_offset Offset to add to the found pattern's address. | ||
| 517 | * @param detour_function The mid-hook detour function. | ||
| 518 | * @param config Optional configuration settings for the hook. | ||
| 519 | * @return std::expected<std::string, HookError> The hook name if successful, error code otherwise. | ||
| 520 | */ | ||
| 521 | [[nodiscard]] std::expected<std::string, HookError> create_mid_hook_aob( | ||
| 522 | std::string_view name, | ||
| 523 | uintptr_t module_base, | ||
| 524 | size_t module_size, | ||
| 525 | std::string_view aob_pattern_str, | ||
| 526 | ptrdiff_t aob_offset, | ||
| 527 | safetyhook::MidHookFn detour_function, | ||
| 528 | const HookConfig &config = HookConfig()); | ||
| 529 | |||
| 530 | /** | ||
| 531 | * @brief Creates a VMT hook for the given object, cloning its vtable. | ||
| 532 | * @param name A unique, descriptive name for the VMT hook. | ||
| 533 | * @param object Pointer to the polymorphic object whose vptr will be replaced. | ||
| 534 | * @return std::expected<std::string, HookError> The hook name if successful, error code otherwise. | ||
| 535 | */ | ||
| 536 | [[nodiscard]] std::expected<std::string, HookError> create_vmt_hook( | ||
| 537 | std::string_view name, void *object); | ||
| 538 | |||
| 539 | /** | ||
| 540 | * @brief Hooks a specific virtual method by index in a named VMT hook. | ||
| 541 | * @tparam T The type of the destination function (function pointer or member function pointer). | ||
| 542 | * @param vmt_name The name of the VMT hook (from create_vmt_hook). | ||
| 543 | * @param method_index The zero-based vtable index of the method to hook. | ||
| 544 | * @param destination The replacement function. | ||
| 545 | * @return std::expected<size_t, HookError> The method index if successful, error code otherwise. | ||
| 546 | */ | ||
| 547 | template <typename T> | ||
| 548 | 10 | [[nodiscard]] std::expected<size_t, HookError> hook_vmt_method( | |
| 549 | std::string_view vmt_name, size_t method_index, T destination) | ||
| 550 | { | ||
| 551 |
1/2✗ Branch 3 → 4 not taken.
✓ Branch 3 → 8 taken 10 times.
|
10 | if (m_shutdown_called.load(std::memory_order_acquire)) |
| 552 | { | ||
| 553 | ✗ | m_logger.error("HookManager: Shutdown in progress. Cannot hook VMT method on '{}'.", vmt_name); | |
| 554 | ✗ | return std::unexpected(HookError::ShutdownInProgress); | |
| 555 | } | ||
| 556 | |||
| 557 |
6/68✗ Branch 7 → 8 not taken.
✗ Branch 7 → 110 not taken.
✓ Branch 8 → 9 taken 10 times.
✗ Branch 8 → 36 not taken.
✗ Branch 17 → 18 not taken.
✗ Branch 17 → 19 not taken.
✓ Branch 25 → 26 taken 2 times.
✗ Branch 25 → 130 not taken.
✗ Branch 35 → 36 not taken.
✓ Branch 35 → 37 taken 2 times.
✓ Branch 42 → 43 taken 1 time.
✗ Branch 42 → 150 not taken.
✗ Branch 52 → 53 not taken.
✓ Branch 52 → 54 taken 1 time.
✗ Branch 61 → 62 not taken.
✗ Branch 61 → 170 not taken.
✗ Branch 71 → 72 not taken.
✗ Branch 71 → 73 not taken.
✗ Branch 91 → 92 not taken.
✓ Branch 91 → 93 taken 7 times.
✗ Branch 107 → 108 not taken.
✗ Branch 107 → 109 not taken.
✗ Branch 111 → 112 not taken.
✗ Branch 111 → 115 not taken.
✗ Branch 113 → 114 not taken.
✗ Branch 113 → 115 not taken.
✗ Branch 127 → 128 not taken.
✗ Branch 127 → 129 not taken.
✗ Branch 131 → 132 not taken.
✗ Branch 131 → 135 not taken.
✗ Branch 133 → 134 not taken.
✗ Branch 133 → 135 not taken.
✗ Branch 147 → 148 not taken.
✗ Branch 147 → 149 not taken.
✗ Branch 151 → 152 not taken.
✗ Branch 151 → 155 not taken.
✗ Branch 153 → 154 not taken.
✗ Branch 153 → 155 not taken.
✗ Branch 167 → 168 not taken.
✗ Branch 167 → 169 not taken.
✗ Branch 171 → 172 not taken.
✗ Branch 171 → 175 not taken.
✗ Branch 173 → 174 not taken.
✗ Branch 173 → 175 not taken.
✗ Branch 190 → 191 not taken.
✗ Branch 190 → 192 not taken.
✗ Branch 194 → 195 not taken.
✗ Branch 194 → 198 not taken.
✗ Branch 196 → 197 not taken.
✗ Branch 196 → 198 not taken.
✗ Branch 218 → 219 not taken.
✗ Branch 218 → 220 not taken.
✗ Branch 223 → 224 not taken.
✗ Branch 223 → 269 not taken.
✗ Branch 233 → 234 not taken.
✗ Branch 233 → 235 not taken.
✗ Branch 243 → 244 not taken.
✗ Branch 243 → 245 not taken.
✗ Branch 248 → 249 not taken.
✗ Branch 248 → 252 not taken.
✗ Branch 250 → 251 not taken.
✗ Branch 250 → 252 not taken.
✗ Branch 266 → 267 not taken.
✗ Branch 266 → 268 not taken.
✗ Branch 270 → 271 not taken.
✗ Branch 270 → 274 not taken.
✗ Branch 272 → 273 not taken.
✗ Branch 272 → 274 not taken.
|
23 | auto [result, deferred_logs] = [&]() -> std::pair<std::expected<size_t, HookError>, std::vector<DeferredLogEntry>> |
| 558 | { | ||
| 559 |
1/2✓ Branch 2 → 3 taken 10 times.
✗ Branch 2 → 285 not taken.
|
10 | std::shared_lock<std::shared_mutex> mutator_gate(m_mutator_gate); |
| 560 |
1/2✓ Branch 3 → 4 taken 10 times.
✗ Branch 3 → 283 not taken.
|
10 | std::unique_lock<std::shared_mutex> lock(m_hooks_mutex); |
| 561 | |||
| 562 |
1/2✗ Branch 5 → 6 not taken.
✓ Branch 5 → 20 taken 10 times.
|
10 | if (m_shutdown_called.load(std::memory_order_acquire)) |
| 563 | { | ||
| 564 | ✗ | return {std::unexpected(HookError::ShutdownInProgress), | |
| 565 | ✗ | {{std::format("HookManager: Shutdown in progress. Cannot hook VMT method on '{}'.", vmt_name), LogLevel::Error}}}; | |
| 566 | } | ||
| 567 | |||
| 568 |
1/2✓ Branch 20 → 21 taken 10 times.
✗ Branch 20 → 281 not taken.
|
10 | auto vmt_it = m_vmt_hooks.find(vmt_name); |
| 569 |
2/2✓ Branch 23 → 24 taken 2 times.
✓ Branch 23 → 38 taken 8 times.
|
10 | if (vmt_it == m_vmt_hooks.end()) |
| 570 | { | ||
| 571 | 2 | return {std::unexpected(HookError::VmtHookNotFound), | |
| 572 |
3/6✓ Branch 28 → 29 taken 2 times.
✗ Branch 28 → 120 not taken.
✓ Branch 33 → 34 taken 2 times.
✓ Branch 33 → 35 taken 2 times.
✗ Branch 124 → 125 not taken.
✗ Branch 124 → 126 not taken.
|
10 | {{std::format("HookManager: VMT hook '{}' not found for method hook at index {}.", vmt_name, method_index), LogLevel::Error}}}; |
| 573 | } | ||
| 574 | |||
| 575 |
3/4✓ Branch 39 → 40 taken 8 times.
✗ Branch 39 → 281 not taken.
✓ Branch 40 → 41 taken 1 time.
✓ Branch 40 → 55 taken 7 times.
|
8 | if (vmt_it->second.has_method_hook(method_index)) |
| 576 | { | ||
| 577 | 1 | return {std::unexpected(HookError::MethodAlreadyHooked), | |
| 578 |
3/6✓ Branch 45 → 46 taken 1 time.
✗ Branch 45 → 140 not taken.
✓ Branch 50 → 51 taken 1 time.
✓ Branch 50 → 52 taken 1 time.
✗ Branch 144 → 145 not taken.
✗ Branch 144 → 146 not taken.
|
5 | {{std::format("HookManager: VMT '{}' method index {} is already hooked.", vmt_name, method_index), LogLevel::Error}}}; |
| 579 | } | ||
| 580 | |||
| 581 | try | ||
| 582 | { | ||
| 583 |
1/2✓ Branch 57 → 58 taken 7 times.
✗ Branch 57 → 203 not taken.
|
7 | auto hook_result = vmt_it->second.vmt_hook().hook_method(method_index, destination); |
| 584 | |||
| 585 |
1/2✗ Branch 59 → 60 not taken.
✓ Branch 59 → 74 taken 7 times.
|
7 | if (!hook_result) |
| 586 | { | ||
| 587 | ✗ | return {std::unexpected(HookError::SafetyHookError), | |
| 588 | ✗ | {{std::format("HookManager: Failed to hook VMT '{}' method index {}.", vmt_name, method_index), LogLevel::Error}}}; | |
| 589 | } | ||
| 590 | |||
| 591 |
2/4✓ Branch 75 → 76 taken 7 times.
✗ Branch 75 → 182 not taken.
✓ Branch 79 → 80 taken 7 times.
✗ Branch 79 → 180 not taken.
|
14 | vmt_it->second.add_method_hook(method_index, std::move(hook_result.value())); |
| 592 | |||
| 593 | return {method_index, | ||
| 594 |
4/8✓ Branch 81 → 82 taken 7 times.
✗ Branch 81 → 193 not taken.
✓ Branch 84 → 85 taken 7 times.
✗ Branch 84 → 183 not taken.
✓ Branch 89 → 90 taken 7 times.
✓ Branch 89 → 91 taken 7 times.
✗ Branch 187 → 188 not taken.
✗ Branch 187 → 189 not taken.
|
35 | {{std::format("HookManager: Successfully hooked VMT '{}' method index {}.", vmt_name, method_index), LogLevel::Info}}}; |
| 595 | 7 | } | |
| 596 | ✗ | catch (const std::exception &e) | |
| 597 | { | ||
| 598 | ✗ | return {std::unexpected(HookError::UnknownError), | |
| 599 | ✗ | {{std::format("HookManager: Exception hooking VMT '{}' method index {}: {}", vmt_name, method_index, e.what()), LogLevel::Error}}}; | |
| 600 | } | ||
| 601 | ✗ | catch (...) | |
| 602 | { | ||
| 603 | ✗ | return {std::unexpected(HookError::UnknownError), | |
| 604 | ✗ | {{std::format("HookManager: Unknown exception hooking VMT '{}' method index {}.", vmt_name, method_index), LogLevel::Error}}}; | |
| 605 | } | ||
| 606 | 10 | }(); | |
| 607 | |||
| 608 |
2/2✓ Branch 26 → 13 taken 10 times.
✓ Branch 26 → 27 taken 10 times.
|
30 | for (const auto &entry : deferred_logs) |
| 609 | { | ||
| 610 |
1/2✓ Branch 16 → 17 taken 10 times.
✗ Branch 16 → 37 not taken.
|
10 | m_logger.log(entry.level, entry.msg); |
| 611 | } | ||
| 612 | 10 | return result; | |
| 613 |
1/4✗ Branch 27 → 28 not taken.
✓ Branch 27 → 29 taken 10 times.
✗ Branch 38 → 39 not taken.
✗ Branch 38 → 40 not taken.
|
20 | } |
| 614 | |||
| 615 | /** | ||
| 616 | * @brief Removes an entire VMT hook, restoring the original vtable on all applied objects. | ||
| 617 | * @param vmt_name The name of the VMT hook to remove. | ||
| 618 | * @return Success if removed, or HookError::VmtHookNotFound. | ||
| 619 | */ | ||
| 620 | [[nodiscard]] std::expected<void, HookError> remove_vmt_hook(std::string_view vmt_name); | ||
| 621 | |||
| 622 | /** | ||
| 623 | * @brief Removes a single method hook from a VMT, restoring the original method. | ||
| 624 | * @param vmt_name The name of the VMT hook. | ||
| 625 | * @param method_index The vtable index of the method to unhook. | ||
| 626 | * @return Success if removed, or a HookError describing the failure. | ||
| 627 | */ | ||
| 628 | [[nodiscard]] std::expected<void, HookError> remove_vmt_method(std::string_view vmt_name, size_t method_index); | ||
| 629 | |||
| 630 | /** | ||
| 631 | * @brief Applies the cloned (hooked) vtable to an additional object. | ||
| 632 | * @param vmt_name The name of the VMT hook. | ||
| 633 | * @param object The object to apply the hooked vtable to. | ||
| 634 | * @return true if the VMT hook was found and applied, false otherwise. | ||
| 635 | */ | ||
| 636 | [[nodiscard]] bool apply_vmt_hook(std::string_view vmt_name, void *object); | ||
| 637 | |||
| 638 | /** | ||
| 639 | * @brief Removes the hooked vtable from a specific object, restoring its original vptr. | ||
| 640 | * @param vmt_name The name of the VMT hook. | ||
| 641 | * @param object The object to restore. | ||
| 642 | * @return true if the VMT hook was found and the object was restored, false otherwise. | ||
| 643 | */ | ||
| 644 | [[nodiscard]] bool remove_vmt_from_object(std::string_view vmt_name, void *object); | ||
| 645 | |||
| 646 | /** | ||
| 647 | * @brief Removes all VMT hooks, restoring original vtables on all applied objects. | ||
| 648 | */ | ||
| 649 | void remove_all_vmt_hooks(); | ||
| 650 | |||
| 651 | /** | ||
| 652 | * @brief Returns the names of all active VMT hooks. | ||
| 653 | * @return std::vector<std::string> Vector containing the names of the VMT hooks. | ||
| 654 | */ | ||
| 655 | std::vector<std::string> get_vmt_hook_names() const; | ||
| 656 | |||
| 657 | /** | ||
| 658 | * @brief Safely accesses a VmHook (method hook) within a named VMT hook. | ||
| 659 | * @details The callback is invoked while the shared_mutex is held as a reader. | ||
| 660 | * @tparam F Callable type accepting (safetyhook::VmHook&) and returning a value. | ||
| 661 | * @param vmt_name The name of the VMT hook. | ||
| 662 | * @param method_index The vtable index of the method hook. | ||
| 663 | * @param fn The callback to invoke with the VmHook reference. | ||
| 664 | * @return std::optional<R> The callback's return value, or std::nullopt if not found. | ||
| 665 | */ | ||
| 666 | template <typename F> | ||
| 667 | requires std::invocable<F, safetyhook::VmHook &> && | ||
| 668 | (!std::is_void_v<std::invoke_result_t<F, safetyhook::VmHook &>>) && | ||
| 669 | (!std::is_reference_v<std::invoke_result_t<F, safetyhook::VmHook &>>) | ||
| 670 | 3 | [[nodiscard]] auto with_vmt_method(std::string_view vmt_name, size_t method_index, F &&fn) | |
| 671 | -> std::optional<std::invoke_result_t<F, safetyhook::VmHook &>> | ||
| 672 | { | ||
| 673 |
3/6std::optional<std::invoke_result<HookManagerTest_VmtHook_WithVmtMethod_NotFound_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}, safetyhook::VmHook&>::type> DetourModKit::HookManager::with_vmt_method<HookManagerTest_VmtHook_WithVmtMethod_NotFound_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, unsigned long long, HookManagerTest_VmtHook_WithVmtMethod_NotFound_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}&&):
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 8 taken 1 time.
std::optional<std::invoke_result<HookManagerTest_VmtHook_WithVmtMethod_ValueCallback_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}, safetyhook::VmHook&>::type> DetourModKit::HookManager::with_vmt_method<HookManagerTest_VmtHook_WithVmtMethod_ValueCallback_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, unsigned long long, HookManagerTest_VmtHook_WithVmtMethod_ValueCallback_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}&&):
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 8 taken 1 time.
std::optional<std::invoke_result<HookManagerTest_VmtHook_WithVmtMethod_MethodNotFound_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}, safetyhook::VmHook&>::type> DetourModKit::HookManager::with_vmt_method<HookManagerTest_VmtHook_WithVmtMethod_MethodNotFound_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, unsigned long long, HookManagerTest_VmtHook_WithVmtMethod_MethodNotFound_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}&&):
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 8 taken 1 time.
|
3 | if (get_reentrancy_guard() > 0) |
| 674 | { | ||
| 675 | ✗ | m_logger.error("HookManager: Reentrant callback detected in with_vmt_method('{}'/{})!", vmt_name, method_index); | |
| 676 | ✗ | return std::nullopt; | |
| 677 | } | ||
| 678 |
3/6std::optional<std::invoke_result<HookManagerTest_VmtHook_WithVmtMethod_NotFound_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}, safetyhook::VmHook&>::type> DetourModKit::HookManager::with_vmt_method<HookManagerTest_VmtHook_WithVmtMethod_NotFound_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, unsigned long long, HookManagerTest_VmtHook_WithVmtMethod_NotFound_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}&&):
✓ Branch 8 → 9 taken 1 time.
✗ Branch 8 → 38 not taken.
std::optional<std::invoke_result<HookManagerTest_VmtHook_WithVmtMethod_ValueCallback_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}, safetyhook::VmHook&>::type> DetourModKit::HookManager::with_vmt_method<HookManagerTest_VmtHook_WithVmtMethod_ValueCallback_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, unsigned long long, HookManagerTest_VmtHook_WithVmtMethod_ValueCallback_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}&&):
✓ Branch 8 → 9 taken 1 time.
✗ Branch 8 → 38 not taken.
std::optional<std::invoke_result<HookManagerTest_VmtHook_WithVmtMethod_MethodNotFound_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}, safetyhook::VmHook&>::type> DetourModKit::HookManager::with_vmt_method<HookManagerTest_VmtHook_WithVmtMethod_MethodNotFound_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, unsigned long long, HookManagerTest_VmtHook_WithVmtMethod_MethodNotFound_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}&&):
✓ Branch 8 → 9 taken 1 time.
✗ Branch 8 → 38 not taken.
|
3 | std::shared_lock<std::shared_mutex> lock(m_hooks_mutex); |
| 679 | 3 | ReentrancyGuard guard(get_reentrancy_guard()); | |
| 680 |
3/6std::optional<std::invoke_result<HookManagerTest_VmtHook_WithVmtMethod_NotFound_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}, safetyhook::VmHook&>::type> DetourModKit::HookManager::with_vmt_method<HookManagerTest_VmtHook_WithVmtMethod_NotFound_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, unsigned long long, HookManagerTest_VmtHook_WithVmtMethod_NotFound_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}&&):
✓ Branch 11 → 12 taken 1 time.
✗ Branch 11 → 34 not taken.
std::optional<std::invoke_result<HookManagerTest_VmtHook_WithVmtMethod_ValueCallback_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}, safetyhook::VmHook&>::type> DetourModKit::HookManager::with_vmt_method<HookManagerTest_VmtHook_WithVmtMethod_ValueCallback_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, unsigned long long, HookManagerTest_VmtHook_WithVmtMethod_ValueCallback_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}&&):
✓ Branch 11 → 12 taken 1 time.
✗ Branch 11 → 34 not taken.
std::optional<std::invoke_result<HookManagerTest_VmtHook_WithVmtMethod_MethodNotFound_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}, safetyhook::VmHook&>::type> DetourModKit::HookManager::with_vmt_method<HookManagerTest_VmtHook_WithVmtMethod_MethodNotFound_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, unsigned long long, HookManagerTest_VmtHook_WithVmtMethod_MethodNotFound_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}&&):
✓ Branch 11 → 12 taken 1 time.
✗ Branch 11 → 34 not taken.
|
3 | auto vmt_it = m_vmt_hooks.find(vmt_name); |
| 681 |
3/6std::optional<std::invoke_result<HookManagerTest_VmtHook_WithVmtMethod_NotFound_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}, safetyhook::VmHook&>::type> DetourModKit::HookManager::with_vmt_method<HookManagerTest_VmtHook_WithVmtMethod_NotFound_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, unsigned long long, HookManagerTest_VmtHook_WithVmtMethod_NotFound_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}&&):
✗ Branch 14 → 15 not taken.
✓ Branch 14 → 24 taken 1 time.
std::optional<std::invoke_result<HookManagerTest_VmtHook_WithVmtMethod_ValueCallback_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}, safetyhook::VmHook&>::type> DetourModKit::HookManager::with_vmt_method<HookManagerTest_VmtHook_WithVmtMethod_ValueCallback_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, unsigned long long, HookManagerTest_VmtHook_WithVmtMethod_ValueCallback_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}&&):
✓ Branch 14 → 15 taken 1 time.
✗ Branch 14 → 24 not taken.
std::optional<std::invoke_result<HookManagerTest_VmtHook_WithVmtMethod_MethodNotFound_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}, safetyhook::VmHook&>::type> DetourModKit::HookManager::with_vmt_method<HookManagerTest_VmtHook_WithVmtMethod_MethodNotFound_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, unsigned long long, HookManagerTest_VmtHook_WithVmtMethod_MethodNotFound_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}&&):
✓ Branch 14 → 15 taken 1 time.
✗ Branch 14 → 24 not taken.
|
3 | if (vmt_it != m_vmt_hooks.end()) |
| 682 | { | ||
| 683 |
2/6std::optional<std::invoke_result<HookManagerTest_VmtHook_WithVmtMethod_NotFound_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}, safetyhook::VmHook&>::type> DetourModKit::HookManager::with_vmt_method<HookManagerTest_VmtHook_WithVmtMethod_NotFound_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, unsigned long long, HookManagerTest_VmtHook_WithVmtMethod_NotFound_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}&&):
✗ Branch 16 → 17 not taken.
✗ Branch 16 → 34 not taken.
std::optional<std::invoke_result<HookManagerTest_VmtHook_WithVmtMethod_ValueCallback_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}, safetyhook::VmHook&>::type> DetourModKit::HookManager::with_vmt_method<HookManagerTest_VmtHook_WithVmtMethod_ValueCallback_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, unsigned long long, HookManagerTest_VmtHook_WithVmtMethod_ValueCallback_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}&&):
✓ Branch 16 → 17 taken 1 time.
✗ Branch 16 → 34 not taken.
std::optional<std::invoke_result<HookManagerTest_VmtHook_WithVmtMethod_MethodNotFound_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}, safetyhook::VmHook&>::type> DetourModKit::HookManager::with_vmt_method<HookManagerTest_VmtHook_WithVmtMethod_MethodNotFound_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, unsigned long long, HookManagerTest_VmtHook_WithVmtMethod_MethodNotFound_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}&&):
✓ Branch 16 → 17 taken 1 time.
✗ Branch 16 → 34 not taken.
|
2 | auto *vm_hook = vmt_it->second.get_method_hook(method_index); |
| 684 |
2/6std::optional<std::invoke_result<HookManagerTest_VmtHook_WithVmtMethod_NotFound_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}, safetyhook::VmHook&>::type> DetourModKit::HookManager::with_vmt_method<HookManagerTest_VmtHook_WithVmtMethod_NotFound_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, unsigned long long, HookManagerTest_VmtHook_WithVmtMethod_NotFound_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}&&):
✗ Branch 17 → 18 not taken.
✗ Branch 17 → 24 not taken.
std::optional<std::invoke_result<HookManagerTest_VmtHook_WithVmtMethod_ValueCallback_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}, safetyhook::VmHook&>::type> DetourModKit::HookManager::with_vmt_method<HookManagerTest_VmtHook_WithVmtMethod_ValueCallback_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, unsigned long long, HookManagerTest_VmtHook_WithVmtMethod_ValueCallback_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}&&):
✓ Branch 17 → 18 taken 1 time.
✗ Branch 17 → 24 not taken.
std::optional<std::invoke_result<HookManagerTest_VmtHook_WithVmtMethod_MethodNotFound_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}, safetyhook::VmHook&>::type> DetourModKit::HookManager::with_vmt_method<HookManagerTest_VmtHook_WithVmtMethod_MethodNotFound_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, unsigned long long, HookManagerTest_VmtHook_WithVmtMethod_MethodNotFound_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}&&):
✗ Branch 17 → 18 not taken.
✓ Branch 17 → 24 taken 1 time.
|
2 | if (vm_hook) |
| 685 | { | ||
| 686 |
1/6std::optional<std::invoke_result<HookManagerTest_VmtHook_WithVmtMethod_NotFound_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}, safetyhook::VmHook&>::type> DetourModKit::HookManager::with_vmt_method<HookManagerTest_VmtHook_WithVmtMethod_NotFound_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, unsigned long long, HookManagerTest_VmtHook_WithVmtMethod_NotFound_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}&&):
✗ Branch 20 → 21 not taken.
✗ Branch 20 → 32 not taken.
std::optional<std::invoke_result<HookManagerTest_VmtHook_WithVmtMethod_ValueCallback_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}, safetyhook::VmHook&>::type> DetourModKit::HookManager::with_vmt_method<HookManagerTest_VmtHook_WithVmtMethod_ValueCallback_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, unsigned long long, HookManagerTest_VmtHook_WithVmtMethod_ValueCallback_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}&&):
✓ Branch 20 → 21 taken 1 time.
✗ Branch 20 → 32 not taken.
std::optional<std::invoke_result<HookManagerTest_VmtHook_WithVmtMethod_MethodNotFound_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}, safetyhook::VmHook&>::type> DetourModKit::HookManager::with_vmt_method<HookManagerTest_VmtHook_WithVmtMethod_MethodNotFound_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, unsigned long long, HookManagerTest_VmtHook_WithVmtMethod_MethodNotFound_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}&&):
✗ Branch 20 → 21 not taken.
✗ Branch 20 → 32 not taken.
|
1 | return std::invoke(std::forward<F>(fn), *vm_hook); |
| 687 | } | ||
| 688 | } | ||
| 689 | 2 | return std::nullopt; | |
| 690 | 3 | } | |
| 691 | |||
| 692 | /** | ||
| 693 | * @brief Safely accesses a VmHook for a void-returning callback. | ||
| 694 | * @details Same locking and reentrancy semantics as the value-returning overload. | ||
| 695 | * @param vmt_name The name of the VMT hook. | ||
| 696 | * @param method_index The vtable index of the method hook. | ||
| 697 | * @param fn The void-returning callback to invoke with the VmHook reference. | ||
| 698 | * @return true if the method hook was found and the callback was invoked, false otherwise. | ||
| 699 | */ | ||
| 700 | template <typename F> | ||
| 701 | requires std::invocable<F, safetyhook::VmHook &> && | ||
| 702 | std::is_void_v<std::invoke_result_t<F, safetyhook::VmHook &>> | ||
| 703 | 5 | [[nodiscard]] bool with_vmt_method(std::string_view vmt_name, size_t method_index, F &&fn) | |
| 704 | { | ||
| 705 |
5/10bool DetourModKit::HookManager::with_vmt_method<HookManagerTest_VmtHook_HookMethod_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, unsigned long long, HookManagerTest_VmtHook_HookMethod_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}&&):
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 6 taken 1 time.
bool DetourModKit::HookManager::with_vmt_method<HookManagerTest_VmtHook_RemoveMethod_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, unsigned long long, HookManagerTest_VmtHook_RemoveMethod_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}&&):
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 6 taken 1 time.
bool DetourModKit::HookManager::with_vmt_method<HookManagerTest_VmtHook_RemoveEntireHook_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, unsigned long long, HookManagerTest_VmtHook_RemoveEntireHook_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}&&):
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 6 taken 1 time.
bool DetourModKit::HookManager::with_vmt_method<HookManagerTest_VmtHook_ApplyToMultipleObjects_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, unsigned long long, HookManagerTest_VmtHook_ApplyToMultipleObjects_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}&&):
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 6 taken 1 time.
bool DetourModKit::HookManager::with_vmt_method<HookManagerTest_VmtHook_WithVmtMethod_VoidCallback_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, unsigned long long, HookManagerTest_VmtHook_WithVmtMethod_VoidCallback_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}&&):
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 6 taken 1 time.
|
5 | if (get_reentrancy_guard() > 0) |
| 706 | { | ||
| 707 | ✗ | m_logger.error("HookManager: Reentrant callback detected in with_vmt_method('{}'/{})!", vmt_name, method_index); | |
| 708 | ✗ | return false; | |
| 709 | } | ||
| 710 |
5/10bool DetourModKit::HookManager::with_vmt_method<HookManagerTest_VmtHook_HookMethod_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, unsigned long long, HookManagerTest_VmtHook_HookMethod_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}&&):
✓ Branch 6 → 7 taken 1 time.
✗ Branch 6 → 30 not taken.
bool DetourModKit::HookManager::with_vmt_method<HookManagerTest_VmtHook_RemoveMethod_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, unsigned long long, HookManagerTest_VmtHook_RemoveMethod_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}&&):
✓ Branch 6 → 7 taken 1 time.
✗ Branch 6 → 30 not taken.
bool DetourModKit::HookManager::with_vmt_method<HookManagerTest_VmtHook_RemoveEntireHook_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, unsigned long long, HookManagerTest_VmtHook_RemoveEntireHook_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}&&):
✓ Branch 6 → 7 taken 1 time.
✗ Branch 6 → 30 not taken.
bool DetourModKit::HookManager::with_vmt_method<HookManagerTest_VmtHook_ApplyToMultipleObjects_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, unsigned long long, HookManagerTest_VmtHook_ApplyToMultipleObjects_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}&&):
✓ Branch 6 → 7 taken 1 time.
✗ Branch 6 → 30 not taken.
bool DetourModKit::HookManager::with_vmt_method<HookManagerTest_VmtHook_WithVmtMethod_VoidCallback_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, unsigned long long, HookManagerTest_VmtHook_WithVmtMethod_VoidCallback_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}&&):
✓ Branch 6 → 7 taken 1 time.
✗ Branch 6 → 30 not taken.
|
5 | std::shared_lock<std::shared_mutex> lock(m_hooks_mutex); |
| 711 | 5 | ReentrancyGuard guard(get_reentrancy_guard()); | |
| 712 |
5/10bool DetourModKit::HookManager::with_vmt_method<HookManagerTest_VmtHook_HookMethod_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, unsigned long long, HookManagerTest_VmtHook_HookMethod_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}&&):
✓ Branch 9 → 10 taken 1 time.
✗ Branch 9 → 26 not taken.
bool DetourModKit::HookManager::with_vmt_method<HookManagerTest_VmtHook_RemoveMethod_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, unsigned long long, HookManagerTest_VmtHook_RemoveMethod_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}&&):
✓ Branch 9 → 10 taken 1 time.
✗ Branch 9 → 26 not taken.
bool DetourModKit::HookManager::with_vmt_method<HookManagerTest_VmtHook_RemoveEntireHook_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, unsigned long long, HookManagerTest_VmtHook_RemoveEntireHook_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}&&):
✓ Branch 9 → 10 taken 1 time.
✗ Branch 9 → 26 not taken.
bool DetourModKit::HookManager::with_vmt_method<HookManagerTest_VmtHook_ApplyToMultipleObjects_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, unsigned long long, HookManagerTest_VmtHook_ApplyToMultipleObjects_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}&&):
✓ Branch 9 → 10 taken 1 time.
✗ Branch 9 → 26 not taken.
bool DetourModKit::HookManager::with_vmt_method<HookManagerTest_VmtHook_WithVmtMethod_VoidCallback_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, unsigned long long, HookManagerTest_VmtHook_WithVmtMethod_VoidCallback_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}&&):
✓ Branch 9 → 10 taken 1 time.
✗ Branch 9 → 26 not taken.
|
5 | auto vmt_it = m_vmt_hooks.find(vmt_name); |
| 713 |
5/10bool DetourModKit::HookManager::with_vmt_method<HookManagerTest_VmtHook_HookMethod_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, unsigned long long, HookManagerTest_VmtHook_HookMethod_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}&&):
✓ Branch 12 → 13 taken 1 time.
✗ Branch 12 → 20 not taken.
bool DetourModKit::HookManager::with_vmt_method<HookManagerTest_VmtHook_RemoveMethod_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, unsigned long long, HookManagerTest_VmtHook_RemoveMethod_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}&&):
✓ Branch 12 → 13 taken 1 time.
✗ Branch 12 → 20 not taken.
bool DetourModKit::HookManager::with_vmt_method<HookManagerTest_VmtHook_RemoveEntireHook_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, unsigned long long, HookManagerTest_VmtHook_RemoveEntireHook_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}&&):
✓ Branch 12 → 13 taken 1 time.
✗ Branch 12 → 20 not taken.
bool DetourModKit::HookManager::with_vmt_method<HookManagerTest_VmtHook_ApplyToMultipleObjects_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, unsigned long long, HookManagerTest_VmtHook_ApplyToMultipleObjects_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}&&):
✓ Branch 12 → 13 taken 1 time.
✗ Branch 12 → 20 not taken.
bool DetourModKit::HookManager::with_vmt_method<HookManagerTest_VmtHook_WithVmtMethod_VoidCallback_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, unsigned long long, HookManagerTest_VmtHook_WithVmtMethod_VoidCallback_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}&&):
✓ Branch 12 → 13 taken 1 time.
✗ Branch 12 → 20 not taken.
|
5 | if (vmt_it != m_vmt_hooks.end()) |
| 714 | { | ||
| 715 |
5/10bool DetourModKit::HookManager::with_vmt_method<HookManagerTest_VmtHook_HookMethod_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, unsigned long long, HookManagerTest_VmtHook_HookMethod_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}&&):
✓ Branch 14 → 15 taken 1 time.
✗ Branch 14 → 26 not taken.
bool DetourModKit::HookManager::with_vmt_method<HookManagerTest_VmtHook_RemoveMethod_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, unsigned long long, HookManagerTest_VmtHook_RemoveMethod_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}&&):
✓ Branch 14 → 15 taken 1 time.
✗ Branch 14 → 26 not taken.
bool DetourModKit::HookManager::with_vmt_method<HookManagerTest_VmtHook_RemoveEntireHook_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, unsigned long long, HookManagerTest_VmtHook_RemoveEntireHook_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}&&):
✓ Branch 14 → 15 taken 1 time.
✗ Branch 14 → 26 not taken.
bool DetourModKit::HookManager::with_vmt_method<HookManagerTest_VmtHook_ApplyToMultipleObjects_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, unsigned long long, HookManagerTest_VmtHook_ApplyToMultipleObjects_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}&&):
✓ Branch 14 → 15 taken 1 time.
✗ Branch 14 → 26 not taken.
bool DetourModKit::HookManager::with_vmt_method<HookManagerTest_VmtHook_WithVmtMethod_VoidCallback_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, unsigned long long, HookManagerTest_VmtHook_WithVmtMethod_VoidCallback_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}&&):
✓ Branch 14 → 15 taken 1 time.
✗ Branch 14 → 26 not taken.
|
5 | auto *vm_hook = vmt_it->second.get_method_hook(method_index); |
| 716 |
5/10bool DetourModKit::HookManager::with_vmt_method<HookManagerTest_VmtHook_HookMethod_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, unsigned long long, HookManagerTest_VmtHook_HookMethod_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}&&):
✓ Branch 15 → 16 taken 1 time.
✗ Branch 15 → 20 not taken.
bool DetourModKit::HookManager::with_vmt_method<HookManagerTest_VmtHook_RemoveMethod_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, unsigned long long, HookManagerTest_VmtHook_RemoveMethod_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}&&):
✓ Branch 15 → 16 taken 1 time.
✗ Branch 15 → 20 not taken.
bool DetourModKit::HookManager::with_vmt_method<HookManagerTest_VmtHook_RemoveEntireHook_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, unsigned long long, HookManagerTest_VmtHook_RemoveEntireHook_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}&&):
✓ Branch 15 → 16 taken 1 time.
✗ Branch 15 → 20 not taken.
bool DetourModKit::HookManager::with_vmt_method<HookManagerTest_VmtHook_ApplyToMultipleObjects_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, unsigned long long, HookManagerTest_VmtHook_ApplyToMultipleObjects_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}&&):
✓ Branch 15 → 16 taken 1 time.
✗ Branch 15 → 20 not taken.
bool DetourModKit::HookManager::with_vmt_method<HookManagerTest_VmtHook_WithVmtMethod_VoidCallback_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, unsigned long long, HookManagerTest_VmtHook_WithVmtMethod_VoidCallback_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}&&):
✓ Branch 15 → 16 taken 1 time.
✗ Branch 15 → 20 not taken.
|
5 | if (vm_hook) |
| 717 | { | ||
| 718 |
5/10bool DetourModKit::HookManager::with_vmt_method<HookManagerTest_VmtHook_HookMethod_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, unsigned long long, HookManagerTest_VmtHook_HookMethod_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}&&):
✓ Branch 18 → 19 taken 1 time.
✗ Branch 18 → 26 not taken.
bool DetourModKit::HookManager::with_vmt_method<HookManagerTest_VmtHook_RemoveMethod_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, unsigned long long, HookManagerTest_VmtHook_RemoveMethod_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}&&):
✓ Branch 18 → 19 taken 1 time.
✗ Branch 18 → 26 not taken.
bool DetourModKit::HookManager::with_vmt_method<HookManagerTest_VmtHook_RemoveEntireHook_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, unsigned long long, HookManagerTest_VmtHook_RemoveEntireHook_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}&&):
✓ Branch 18 → 19 taken 1 time.
✗ Branch 18 → 26 not taken.
bool DetourModKit::HookManager::with_vmt_method<HookManagerTest_VmtHook_ApplyToMultipleObjects_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, unsigned long long, HookManagerTest_VmtHook_ApplyToMultipleObjects_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}&&):
✓ Branch 18 → 19 taken 1 time.
✗ Branch 18 → 26 not taken.
bool DetourModKit::HookManager::with_vmt_method<HookManagerTest_VmtHook_WithVmtMethod_VoidCallback_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, unsigned long long, HookManagerTest_VmtHook_WithVmtMethod_VoidCallback_Test::TestBody()::{lambda(safetyhook::VmHook&)#1}&&):
✓ Branch 18 → 19 taken 1 time.
✗ Branch 18 → 26 not taken.
|
5 | std::invoke(std::forward<F>(fn), *vm_hook); |
| 719 | 5 | return true; | |
| 720 | } | ||
| 721 | } | ||
| 722 | ✗ | return false; | |
| 723 | 5 | } | |
| 724 | |||
| 725 | /** | ||
| 726 | * @brief Reports whether @p target_address already carries an inline hook | ||
| 727 | * installed by this HookManager instance. | ||
| 728 | * @details Walks the local hook registry under a shared lock. Local-only | ||
| 729 | * by design: hooks installed by other statically-linked DMK | ||
| 730 | * consumers in the same process are not visible. | ||
| 731 | * | ||
| 732 | * Use this to short-circuit a redundant create_inline_hook call | ||
| 733 | * without parsing the prologue bytes. To detect inline hooks | ||
| 734 | * installed by code outside this HookManager (for example a | ||
| 735 | * third-party JMP rel32 written into the prologue) pass | ||
| 736 | * HookConfig::fail_if_already_hooked when creating the hook. | ||
| 737 | * @param target_address Function address to query. | ||
| 738 | * @return true if a managed inline hook already targets this address. | ||
| 739 | */ | ||
| 740 | [[nodiscard]] bool is_target_already_hooked(uintptr_t target_address) const noexcept; | ||
| 741 | |||
| 742 | /** | ||
| 743 | * @brief Removes a hook identified by its name. | ||
| 744 | * @param hook_id The name of the hook to remove. | ||
| 745 | * @return Success if removed, or HookError::HookNotFound. | ||
| 746 | */ | ||
| 747 | [[nodiscard]] std::expected<void, HookError> remove_hook(std::string_view hook_id); | ||
| 748 | |||
| 749 | /** | ||
| 750 | * @brief Removes all hooks currently managed by this HookManager instance. | ||
| 751 | * @details Uses two-phase removal: disables all hooks under a shared lock | ||
| 752 | * first so that in-flight trampoline callers can drain, then clears | ||
| 753 | * the maps under an exclusive lock. This prevents deadlock when a | ||
| 754 | * hooked thread is blocked on m_hooks_mutex via with_inline_hook(). | ||
| 755 | * | ||
| 756 | * After clearing, resets the internal shutdown flag to false, | ||
| 757 | * allowing subsequent create_*_hook() calls to succeed for | ||
| 758 | * hot-reload workflows. | ||
| 759 | */ | ||
| 760 | void remove_all_hooks(); | ||
| 761 | |||
| 762 | /** | ||
| 763 | * @brief Enables a previously disabled hook. | ||
| 764 | * @details Idempotent: enabling an already-active hook returns success. | ||
| 765 | * Returns HookError::InvalidHookState only when the hook is in | ||
| 766 | * a transitional state (Enabling or Disabling). Other HookError | ||
| 767 | * values indicate lookup or SafetyHook failures. | ||
| 768 | * @param hook_id The name of the hook to enable. | ||
| 769 | * @return Success if the hook is now active (or was already active), | ||
| 770 | * or a HookError describing the failure. | ||
| 771 | */ | ||
| 772 | [[nodiscard]] std::expected<void, HookError> enable_hook(std::string_view hook_id); | ||
| 773 | |||
| 774 | /** | ||
| 775 | * @brief Disables an active hook temporarily without removing it. | ||
| 776 | * @details Idempotent: disabling an already-disabled hook returns success. | ||
| 777 | * Returns HookError::InvalidHookState only when the hook is in | ||
| 778 | * a transitional state (Enabling or Disabling). Other HookError | ||
| 779 | * values indicate lookup or SafetyHook failures. | ||
| 780 | * @param hook_id The name of the hook to disable. | ||
| 781 | * @return Success if the hook is now disabled (or was already disabled), | ||
| 782 | * or a HookError describing the failure. | ||
| 783 | */ | ||
| 784 | [[nodiscard]] std::expected<void, HookError> disable_hook(std::string_view hook_id); | ||
| 785 | |||
| 786 | /** | ||
| 787 | * @brief Retrieves the current status of a hook. | ||
| 788 | * @param hook_id The name of the hook. | ||
| 789 | * @return std::optional<HookStatus> The current status, or std::nullopt if not found. | ||
| 790 | */ | ||
| 791 | [[nodiscard]] std::optional<HookStatus> get_hook_status(std::string_view hook_id) const; | ||
| 792 | |||
| 793 | /** | ||
| 794 | * @brief Gets a summary of hook counts categorized by their status. | ||
| 795 | * @return std::unordered_map<HookStatus, size_t> Map of statuses to counts. | ||
| 796 | */ | ||
| 797 | std::unordered_map<HookStatus, size_t> get_hook_counts() const; | ||
| 798 | |||
| 799 | /** | ||
| 800 | * @brief Retrieves a list of hook names. | ||
| 801 | * @param status_filter Optional status filter for returned hooks. | ||
| 802 | * @return std::vector<std::string> Vector containing the names of the hooks. | ||
| 803 | */ | ||
| 804 | 50 | std::vector<std::string> get_hook_ids(std::optional<HookStatus> status_filter = std::nullopt) const; | |
| 805 | |||
| 806 | /** | ||
| 807 | * @brief Safely accesses an InlineHook by its ID while holding the internal lock. | ||
| 808 | * @details The callback is invoked with a reference to the InlineHook while the | ||
| 809 | * shared_mutex is held as a reader, preventing concurrent removal. | ||
| 810 | * @warning DANGER: Any callback holding m_hooks_mutex must NOT call methods that | ||
| 811 | * acquire a unique_lock (remove_hook, enable_hook, disable_hook, create_*_hook) | ||
| 812 | * because those calls will deadlock. Perform such mutations outside the callback | ||
| 813 | * or use an asynchronous/posted operation that does not hold m_hooks_mutex. | ||
| 814 | * @tparam F Callable type accepting (InlineHook&) and returning a value. | ||
| 815 | * @param hook_id The name of the inline hook. | ||
| 816 | * @param fn The callback to invoke with the hook reference. | ||
| 817 | * @return std::optional<R> The callback's return value, or std::nullopt if hook not found. | ||
| 818 | */ | ||
| 819 | template <typename F> | ||
| 820 | requires std::invocable<F, InlineHook &> && | ||
| 821 | (!std::is_void_v<std::invoke_result_t<F, InlineHook &>>) && | ||
| 822 | (!std::is_reference_v<std::invoke_result_t<F, InlineHook &>>) | ||
| 823 | 8 | [[nodiscard]] auto with_inline_hook(std::string_view hook_id, F &&fn) | |
| 824 | -> std::optional<std::invoke_result_t<F, InlineHook &>> | ||
| 825 | { | ||
| 826 |
8/16std::optional<std::invoke_result<HookManagerTest_WithInlineHook_NotFound_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}, DetourModKit::InlineHook&>::type> DetourModKit::HookManager::with_inline_hook<HookManagerTest_WithInlineHook_NotFound_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_WithInlineHook_NotFound_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}&&):
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 8 taken 1 time.
std::optional<std::invoke_result<HookManagerTest_WithInlineHook_WrongType_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}, DetourModKit::InlineHook&>::type> DetourModKit::HookManager::with_inline_hook<HookManagerTest_WithInlineHook_WrongType_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_WithInlineHook_WrongType_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}&&):
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 8 taken 1 time.
std::optional<std::invoke_result<HookManagerTest_RealInlineHook_WithCallback_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}, DetourModKit::InlineHook&>::type> DetourModKit::HookManager::with_inline_hook<HookManagerTest_RealInlineHook_WithCallback_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_RealInlineHook_WithCallback_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}&&):
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 8 taken 1 time.
std::optional<std::invoke_result<HookManagerTest_WithInlineHook_SuccessCallback_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}, DetourModKit::InlineHook&>::type> DetourModKit::HookManager::with_inline_hook<HookManagerTest_WithInlineHook_SuccessCallback_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_WithInlineHook_SuccessCallback_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}&&):
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 6 taken 1 time.
std::optional<std::invoke_result<HookManagerTest_InlineHook_GetOriginal_Noexcept_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}, DetourModKit::InlineHook&>::type> DetourModKit::HookManager::with_inline_hook<HookManagerTest_InlineHook_GetOriginal_Noexcept_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_InlineHook_GetOriginal_Noexcept_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}&&):
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 8 taken 1 time.
std::optional<std::invoke_result<HookManagerTest_WithInlineHook_DirectEnableDisable_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}, DetourModKit::InlineHook&>::type> DetourModKit::HookManager::with_inline_hook<HookManagerTest_WithInlineHook_DirectEnableDisable_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_WithInlineHook_DirectEnableDisable_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}&&):
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 8 taken 1 time.
std::optional<std::invoke_result<HookManagerTest_WithInlineHook_CallbackExecutesSuccessfully_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}, DetourModKit::InlineHook&>::type> DetourModKit::HookManager::with_inline_hook<HookManagerTest_WithInlineHook_CallbackExecutesSuccessfully_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_WithInlineHook_CallbackExecutesSuccessfully_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}&&):
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 8 taken 1 time.
std::optional<std::invoke_result<HookManagerTest_WithInlineHook_ReturnsNulloptForNonExistentHook_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}, DetourModKit::InlineHook&>::type> DetourModKit::HookManager::with_inline_hook<HookManagerTest_WithInlineHook_ReturnsNulloptForNonExistentHook_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_WithInlineHook_ReturnsNulloptForNonExistentHook_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}&&):
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 8 taken 1 time.
|
8 | if (get_reentrancy_guard() > 0) |
| 827 | { | ||
| 828 | ✗ | m_logger.error("HookManager: Reentrant callback detected in with_inline_hook('{}')! Callback holding m_hooks_mutex must not call HookManager methods that acquire a unique_lock (remove_hook, enable_hook, disable_hook, create_*_hook). Perform mutations outside the callback or use an asynchronous operation.", hook_id); | |
| 829 | ✗ | return std::nullopt; | |
| 830 | } | ||
| 831 |
8/16std::optional<std::invoke_result<HookManagerTest_WithInlineHook_NotFound_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}, DetourModKit::InlineHook&>::type> DetourModKit::HookManager::with_inline_hook<HookManagerTest_WithInlineHook_NotFound_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_WithInlineHook_NotFound_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}&&):
✓ Branch 8 → 9 taken 1 time.
✗ Branch 8 → 44 not taken.
std::optional<std::invoke_result<HookManagerTest_WithInlineHook_WrongType_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}, DetourModKit::InlineHook&>::type> DetourModKit::HookManager::with_inline_hook<HookManagerTest_WithInlineHook_WrongType_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_WithInlineHook_WrongType_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}&&):
✓ Branch 8 → 9 taken 1 time.
✗ Branch 8 → 44 not taken.
std::optional<std::invoke_result<HookManagerTest_RealInlineHook_WithCallback_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}, DetourModKit::InlineHook&>::type> DetourModKit::HookManager::with_inline_hook<HookManagerTest_RealInlineHook_WithCallback_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_RealInlineHook_WithCallback_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}&&):
✓ Branch 8 → 9 taken 1 time.
✗ Branch 8 → 44 not taken.
std::optional<std::invoke_result<HookManagerTest_WithInlineHook_SuccessCallback_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}, DetourModKit::InlineHook&>::type> DetourModKit::HookManager::with_inline_hook<HookManagerTest_WithInlineHook_SuccessCallback_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_WithInlineHook_SuccessCallback_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}&&):
✓ Branch 6 → 7 taken 1 time.
✗ Branch 6 → 39 not taken.
std::optional<std::invoke_result<HookManagerTest_InlineHook_GetOriginal_Noexcept_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}, DetourModKit::InlineHook&>::type> DetourModKit::HookManager::with_inline_hook<HookManagerTest_InlineHook_GetOriginal_Noexcept_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_InlineHook_GetOriginal_Noexcept_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}&&):
✓ Branch 8 → 9 taken 1 time.
✗ Branch 8 → 44 not taken.
std::optional<std::invoke_result<HookManagerTest_WithInlineHook_DirectEnableDisable_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}, DetourModKit::InlineHook&>::type> DetourModKit::HookManager::with_inline_hook<HookManagerTest_WithInlineHook_DirectEnableDisable_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_WithInlineHook_DirectEnableDisable_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}&&):
✓ Branch 8 → 9 taken 1 time.
✗ Branch 8 → 44 not taken.
std::optional<std::invoke_result<HookManagerTest_WithInlineHook_CallbackExecutesSuccessfully_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}, DetourModKit::InlineHook&>::type> DetourModKit::HookManager::with_inline_hook<HookManagerTest_WithInlineHook_CallbackExecutesSuccessfully_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_WithInlineHook_CallbackExecutesSuccessfully_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}&&):
✓ Branch 8 → 9 taken 1 time.
✗ Branch 8 → 44 not taken.
std::optional<std::invoke_result<HookManagerTest_WithInlineHook_ReturnsNulloptForNonExistentHook_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}, DetourModKit::InlineHook&>::type> DetourModKit::HookManager::with_inline_hook<HookManagerTest_WithInlineHook_ReturnsNulloptForNonExistentHook_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_WithInlineHook_ReturnsNulloptForNonExistentHook_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}&&):
✓ Branch 8 → 9 taken 1 time.
✗ Branch 8 → 44 not taken.
|
8 | std::shared_lock<std::shared_mutex> lock(m_hooks_mutex); |
| 832 | 8 | ReentrancyGuard guard(get_reentrancy_guard()); | |
| 833 |
8/16std::optional<std::invoke_result<HookManagerTest_WithInlineHook_NotFound_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}, DetourModKit::InlineHook&>::type> DetourModKit::HookManager::with_inline_hook<HookManagerTest_WithInlineHook_NotFound_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_WithInlineHook_NotFound_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}&&):
✓ Branch 11 → 12 taken 1 time.
✗ Branch 11 → 40 not taken.
std::optional<std::invoke_result<HookManagerTest_WithInlineHook_WrongType_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}, DetourModKit::InlineHook&>::type> DetourModKit::HookManager::with_inline_hook<HookManagerTest_WithInlineHook_WrongType_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_WithInlineHook_WrongType_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}&&):
✓ Branch 11 → 12 taken 1 time.
✗ Branch 11 → 40 not taken.
std::optional<std::invoke_result<HookManagerTest_RealInlineHook_WithCallback_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}, DetourModKit::InlineHook&>::type> DetourModKit::HookManager::with_inline_hook<HookManagerTest_RealInlineHook_WithCallback_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_RealInlineHook_WithCallback_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}&&):
✓ Branch 11 → 12 taken 1 time.
✗ Branch 11 → 40 not taken.
std::optional<std::invoke_result<HookManagerTest_WithInlineHook_SuccessCallback_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}, DetourModKit::InlineHook&>::type> DetourModKit::HookManager::with_inline_hook<HookManagerTest_WithInlineHook_SuccessCallback_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_WithInlineHook_SuccessCallback_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}&&):
✓ Branch 9 → 10 taken 1 time.
✗ Branch 9 → 35 not taken.
std::optional<std::invoke_result<HookManagerTest_InlineHook_GetOriginal_Noexcept_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}, DetourModKit::InlineHook&>::type> DetourModKit::HookManager::with_inline_hook<HookManagerTest_InlineHook_GetOriginal_Noexcept_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_InlineHook_GetOriginal_Noexcept_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}&&):
✓ Branch 11 → 12 taken 1 time.
✗ Branch 11 → 40 not taken.
std::optional<std::invoke_result<HookManagerTest_WithInlineHook_DirectEnableDisable_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}, DetourModKit::InlineHook&>::type> DetourModKit::HookManager::with_inline_hook<HookManagerTest_WithInlineHook_DirectEnableDisable_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_WithInlineHook_DirectEnableDisable_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}&&):
✓ Branch 11 → 12 taken 1 time.
✗ Branch 11 → 40 not taken.
std::optional<std::invoke_result<HookManagerTest_WithInlineHook_CallbackExecutesSuccessfully_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}, DetourModKit::InlineHook&>::type> DetourModKit::HookManager::with_inline_hook<HookManagerTest_WithInlineHook_CallbackExecutesSuccessfully_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_WithInlineHook_CallbackExecutesSuccessfully_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}&&):
✓ Branch 11 → 12 taken 1 time.
✗ Branch 11 → 40 not taken.
std::optional<std::invoke_result<HookManagerTest_WithInlineHook_ReturnsNulloptForNonExistentHook_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}, DetourModKit::InlineHook&>::type> DetourModKit::HookManager::with_inline_hook<HookManagerTest_WithInlineHook_ReturnsNulloptForNonExistentHook_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_WithInlineHook_ReturnsNulloptForNonExistentHook_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}&&):
✓ Branch 11 → 12 taken 1 time.
✗ Branch 11 → 40 not taken.
|
8 | auto it = m_hooks.find(hook_id); |
| 834 |
22/48std::optional<std::invoke_result<HookManagerTest_WithInlineHook_NotFound_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}, DetourModKit::InlineHook&>::type> DetourModKit::HookManager::with_inline_hook<HookManagerTest_WithInlineHook_NotFound_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_WithInlineHook_NotFound_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}&&):
✗ Branch 14 → 15 not taken.
✓ Branch 14 → 20 taken 1 time.
✗ Branch 18 → 19 not taken.
✗ Branch 18 → 20 not taken.
✗ Branch 21 → 22 not taken.
✓ Branch 21 → 30 taken 1 time.
std::optional<std::invoke_result<HookManagerTest_WithInlineHook_WrongType_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}, DetourModKit::InlineHook&>::type> DetourModKit::HookManager::with_inline_hook<HookManagerTest_WithInlineHook_WrongType_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_WithInlineHook_WrongType_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}&&):
✓ Branch 14 → 15 taken 1 time.
✗ Branch 14 → 20 not taken.
✗ Branch 18 → 19 not taken.
✓ Branch 18 → 20 taken 1 time.
✗ Branch 21 → 22 not taken.
✓ Branch 21 → 30 taken 1 time.
std::optional<std::invoke_result<HookManagerTest_RealInlineHook_WithCallback_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}, DetourModKit::InlineHook&>::type> DetourModKit::HookManager::with_inline_hook<HookManagerTest_RealInlineHook_WithCallback_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_RealInlineHook_WithCallback_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}&&):
✓ Branch 14 → 15 taken 1 time.
✗ Branch 14 → 20 not taken.
✓ Branch 18 → 19 taken 1 time.
✗ Branch 18 → 20 not taken.
✓ Branch 21 → 22 taken 1 time.
✗ Branch 21 → 30 not taken.
std::optional<std::invoke_result<HookManagerTest_WithInlineHook_SuccessCallback_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}, DetourModKit::InlineHook&>::type> DetourModKit::HookManager::with_inline_hook<HookManagerTest_WithInlineHook_SuccessCallback_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_WithInlineHook_SuccessCallback_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}&&):
✓ Branch 12 → 13 taken 1 time.
✗ Branch 12 → 18 not taken.
✓ Branch 16 → 17 taken 1 time.
✗ Branch 16 → 18 not taken.
✓ Branch 19 → 20 taken 1 time.
✗ Branch 19 → 28 not taken.
std::optional<std::invoke_result<HookManagerTest_InlineHook_GetOriginal_Noexcept_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}, DetourModKit::InlineHook&>::type> DetourModKit::HookManager::with_inline_hook<HookManagerTest_InlineHook_GetOriginal_Noexcept_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_InlineHook_GetOriginal_Noexcept_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}&&):
✓ Branch 14 → 15 taken 1 time.
✗ Branch 14 → 20 not taken.
✓ Branch 18 → 19 taken 1 time.
✗ Branch 18 → 20 not taken.
✓ Branch 21 → 22 taken 1 time.
✗ Branch 21 → 30 not taken.
std::optional<std::invoke_result<HookManagerTest_WithInlineHook_DirectEnableDisable_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}, DetourModKit::InlineHook&>::type> DetourModKit::HookManager::with_inline_hook<HookManagerTest_WithInlineHook_DirectEnableDisable_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_WithInlineHook_DirectEnableDisable_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}&&):
✓ Branch 14 → 15 taken 1 time.
✗ Branch 14 → 20 not taken.
✓ Branch 18 → 19 taken 1 time.
✗ Branch 18 → 20 not taken.
✓ Branch 21 → 22 taken 1 time.
✗ Branch 21 → 30 not taken.
std::optional<std::invoke_result<HookManagerTest_WithInlineHook_CallbackExecutesSuccessfully_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}, DetourModKit::InlineHook&>::type> DetourModKit::HookManager::with_inline_hook<HookManagerTest_WithInlineHook_CallbackExecutesSuccessfully_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_WithInlineHook_CallbackExecutesSuccessfully_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}&&):
✓ Branch 14 → 15 taken 1 time.
✗ Branch 14 → 20 not taken.
✓ Branch 18 → 19 taken 1 time.
✗ Branch 18 → 20 not taken.
✓ Branch 21 → 22 taken 1 time.
✗ Branch 21 → 30 not taken.
std::optional<std::invoke_result<HookManagerTest_WithInlineHook_ReturnsNulloptForNonExistentHook_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}, DetourModKit::InlineHook&>::type> DetourModKit::HookManager::with_inline_hook<HookManagerTest_WithInlineHook_ReturnsNulloptForNonExistentHook_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_WithInlineHook_ReturnsNulloptForNonExistentHook_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}&&):
✗ Branch 14 → 15 not taken.
✓ Branch 14 → 20 taken 1 time.
✗ Branch 18 → 19 not taken.
✗ Branch 18 → 20 not taken.
✗ Branch 21 → 22 not taken.
✓ Branch 21 → 30 taken 1 time.
|
8 | if (it != m_hooks.end() && it->second->get_type() == HookType::Inline) |
| 835 | { | ||
| 836 |
5/16std::optional<std::invoke_result<HookManagerTest_WithInlineHook_NotFound_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}, DetourModKit::InlineHook&>::type> DetourModKit::HookManager::with_inline_hook<HookManagerTest_WithInlineHook_NotFound_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_WithInlineHook_NotFound_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}&&):
✗ Branch 26 → 27 not taken.
✗ Branch 26 → 38 not taken.
std::optional<std::invoke_result<HookManagerTest_WithInlineHook_WrongType_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}, DetourModKit::InlineHook&>::type> DetourModKit::HookManager::with_inline_hook<HookManagerTest_WithInlineHook_WrongType_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_WithInlineHook_WrongType_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}&&):
✗ Branch 26 → 27 not taken.
✗ Branch 26 → 38 not taken.
std::optional<std::invoke_result<HookManagerTest_RealInlineHook_WithCallback_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}, DetourModKit::InlineHook&>::type> DetourModKit::HookManager::with_inline_hook<HookManagerTest_RealInlineHook_WithCallback_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_RealInlineHook_WithCallback_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}&&):
✓ Branch 26 → 27 taken 1 time.
✗ Branch 26 → 38 not taken.
std::optional<std::invoke_result<HookManagerTest_WithInlineHook_SuccessCallback_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}, DetourModKit::InlineHook&>::type> DetourModKit::HookManager::with_inline_hook<HookManagerTest_WithInlineHook_SuccessCallback_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_WithInlineHook_SuccessCallback_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}&&):
✓ Branch 24 → 25 taken 1 time.
✗ Branch 24 → 34 not taken.
std::optional<std::invoke_result<HookManagerTest_InlineHook_GetOriginal_Noexcept_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}, DetourModKit::InlineHook&>::type> DetourModKit::HookManager::with_inline_hook<HookManagerTest_InlineHook_GetOriginal_Noexcept_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_InlineHook_GetOriginal_Noexcept_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}&&):
✓ Branch 26 → 27 taken 1 time.
✗ Branch 26 → 38 not taken.
std::optional<std::invoke_result<HookManagerTest_WithInlineHook_DirectEnableDisable_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}, DetourModKit::InlineHook&>::type> DetourModKit::HookManager::with_inline_hook<HookManagerTest_WithInlineHook_DirectEnableDisable_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_WithInlineHook_DirectEnableDisable_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}&&):
✓ Branch 26 → 27 taken 1 time.
✗ Branch 26 → 38 not taken.
std::optional<std::invoke_result<HookManagerTest_WithInlineHook_CallbackExecutesSuccessfully_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}, DetourModKit::InlineHook&>::type> DetourModKit::HookManager::with_inline_hook<HookManagerTest_WithInlineHook_CallbackExecutesSuccessfully_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_WithInlineHook_CallbackExecutesSuccessfully_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}&&):
✓ Branch 26 → 27 taken 1 time.
✗ Branch 26 → 38 not taken.
std::optional<std::invoke_result<HookManagerTest_WithInlineHook_ReturnsNulloptForNonExistentHook_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}, DetourModKit::InlineHook&>::type> DetourModKit::HookManager::with_inline_hook<HookManagerTest_WithInlineHook_ReturnsNulloptForNonExistentHook_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_WithInlineHook_ReturnsNulloptForNonExistentHook_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}&&):
✗ Branch 26 → 27 not taken.
✗ Branch 26 → 38 not taken.
|
10 | return std::invoke(std::forward<F>(fn), static_cast<InlineHook &>(*it->second)); |
| 837 | } | ||
| 838 | 3 | return std::nullopt; | |
| 839 | 8 | } | |
| 840 | |||
| 841 | /** | ||
| 842 | * @brief Safely accesses an InlineHook by its ID for a void-returning callback. | ||
| 843 | * @details Same locking and reentrancy semantics as the value-returning overload. | ||
| 844 | * @param hook_id The name of the inline hook. | ||
| 845 | * @param fn The void-returning callback to invoke with the hook reference. | ||
| 846 | * @return true if the hook was found and the callback was invoked, false otherwise. | ||
| 847 | */ | ||
| 848 | template <typename F> | ||
| 849 | requires std::invocable<F, InlineHook &> && | ||
| 850 | std::is_void_v<std::invoke_result_t<F, InlineHook &>> | ||
| 851 | 3 | [[nodiscard]] bool with_inline_hook(std::string_view hook_id, F &&fn) | |
| 852 | { | ||
| 853 |
3/6bool DetourModKit::HookManager::with_inline_hook<HookManagerTest_WithInlineHook_VoidCallback_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_WithInlineHook_VoidCallback_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}&&):
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 6 taken 1 time.
bool DetourModKit::HookManager::with_inline_hook<HookManagerTest_WithInlineHook_VoidCallback_NotFound_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_WithInlineHook_VoidCallback_NotFound_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}&&):
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 6 taken 1 time.
bool DetourModKit::HookManager::with_inline_hook<HookManagerTest_LateShutdown_DrainsReadersBeforeClearingMaps_Test::TestBody()::{lambda()#1}::operator()() const::{lambda(DetourModKit::InlineHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_LateShutdown_DrainsReadersBeforeClearingMaps_Test::TestBody()::{lambda()#1}::operator()() const::{lambda(DetourModKit::InlineHook&)#1}&&):
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 6 taken 1 time.
|
3 | if (get_reentrancy_guard() > 0) |
| 854 | { | ||
| 855 | ✗ | m_logger.error("HookManager: Reentrant callback detected in with_inline_hook('{}')! Callback holding m_hooks_mutex must not call HookManager methods that acquire a unique_lock (remove_hook, enable_hook, disable_hook, create_*_hook). Perform mutations outside the callback or use an asynchronous operation.", hook_id); | |
| 856 | ✗ | return false; | |
| 857 | } | ||
| 858 |
3/6bool DetourModKit::HookManager::with_inline_hook<HookManagerTest_WithInlineHook_VoidCallback_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_WithInlineHook_VoidCallback_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}&&):
✓ Branch 6 → 7 taken 1 time.
✗ Branch 6 → 36 not taken.
bool DetourModKit::HookManager::with_inline_hook<HookManagerTest_WithInlineHook_VoidCallback_NotFound_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_WithInlineHook_VoidCallback_NotFound_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}&&):
✓ Branch 6 → 7 taken 1 time.
✗ Branch 6 → 36 not taken.
bool DetourModKit::HookManager::with_inline_hook<HookManagerTest_LateShutdown_DrainsReadersBeforeClearingMaps_Test::TestBody()::{lambda()#1}::operator()() const::{lambda(DetourModKit::InlineHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_LateShutdown_DrainsReadersBeforeClearingMaps_Test::TestBody()::{lambda()#1}::operator()() const::{lambda(DetourModKit::InlineHook&)#1}&&):
✓ Branch 6 → 7 taken 1 time.
✗ Branch 6 → 36 not taken.
|
3 | std::shared_lock<std::shared_mutex> lock(m_hooks_mutex); |
| 859 | 3 | ReentrancyGuard guard(get_reentrancy_guard()); | |
| 860 |
3/6bool DetourModKit::HookManager::with_inline_hook<HookManagerTest_WithInlineHook_VoidCallback_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_WithInlineHook_VoidCallback_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}&&):
✓ Branch 9 → 10 taken 1 time.
✗ Branch 9 → 32 not taken.
bool DetourModKit::HookManager::with_inline_hook<HookManagerTest_WithInlineHook_VoidCallback_NotFound_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_WithInlineHook_VoidCallback_NotFound_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}&&):
✓ Branch 9 → 10 taken 1 time.
✗ Branch 9 → 32 not taken.
bool DetourModKit::HookManager::with_inline_hook<HookManagerTest_LateShutdown_DrainsReadersBeforeClearingMaps_Test::TestBody()::{lambda()#1}::operator()() const::{lambda(DetourModKit::InlineHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_LateShutdown_DrainsReadersBeforeClearingMaps_Test::TestBody()::{lambda()#1}::operator()() const::{lambda(DetourModKit::InlineHook&)#1}&&):
✓ Branch 9 → 10 taken 1 time.
✗ Branch 9 → 32 not taken.
|
3 | auto it = m_hooks.find(hook_id); |
| 861 |
8/18bool DetourModKit::HookManager::with_inline_hook<HookManagerTest_WithInlineHook_VoidCallback_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_WithInlineHook_VoidCallback_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}&&):
✓ Branch 12 → 13 taken 1 time.
✗ Branch 12 → 18 not taken.
✓ Branch 16 → 17 taken 1 time.
✗ Branch 16 → 18 not taken.
✓ Branch 19 → 20 taken 1 time.
✗ Branch 19 → 26 not taken.
bool DetourModKit::HookManager::with_inline_hook<HookManagerTest_WithInlineHook_VoidCallback_NotFound_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_WithInlineHook_VoidCallback_NotFound_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}&&):
✗ Branch 12 → 13 not taken.
✓ Branch 12 → 18 taken 1 time.
✗ Branch 16 → 17 not taken.
✗ Branch 16 → 18 not taken.
✗ Branch 19 → 20 not taken.
✓ Branch 19 → 26 taken 1 time.
bool DetourModKit::HookManager::with_inline_hook<HookManagerTest_LateShutdown_DrainsReadersBeforeClearingMaps_Test::TestBody()::{lambda()#1}::operator()() const::{lambda(DetourModKit::InlineHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_LateShutdown_DrainsReadersBeforeClearingMaps_Test::TestBody()::{lambda()#1}::operator()() const::{lambda(DetourModKit::InlineHook&)#1}&&):
✓ Branch 12 → 13 taken 1 time.
✗ Branch 12 → 18 not taken.
✓ Branch 16 → 17 taken 1 time.
✗ Branch 16 → 18 not taken.
✓ Branch 19 → 20 taken 1 time.
✗ Branch 19 → 26 not taken.
|
3 | if (it != m_hooks.end() && it->second->get_type() == HookType::Inline) |
| 862 | { | ||
| 863 |
2/6bool DetourModKit::HookManager::with_inline_hook<HookManagerTest_WithInlineHook_VoidCallback_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_WithInlineHook_VoidCallback_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}&&):
✓ Branch 24 → 25 taken 1 time.
✗ Branch 24 → 32 not taken.
bool DetourModKit::HookManager::with_inline_hook<HookManagerTest_WithInlineHook_VoidCallback_NotFound_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_WithInlineHook_VoidCallback_NotFound_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}&&):
✗ Branch 24 → 25 not taken.
✗ Branch 24 → 32 not taken.
bool DetourModKit::HookManager::with_inline_hook<HookManagerTest_LateShutdown_DrainsReadersBeforeClearingMaps_Test::TestBody()::{lambda()#1}::operator()() const::{lambda(DetourModKit::InlineHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_LateShutdown_DrainsReadersBeforeClearingMaps_Test::TestBody()::{lambda()#1}::operator()() const::{lambda(DetourModKit::InlineHook&)#1}&&):
✓ Branch 24 → 25 taken 1 time.
✗ Branch 24 → 32 not taken.
|
4 | std::invoke(std::forward<F>(fn), static_cast<InlineHook &>(*it->second)); |
| 864 | 2 | return true; | |
| 865 | } | ||
| 866 | 1 | return false; | |
| 867 | 3 | } | |
| 868 | |||
| 869 | /** | ||
| 870 | * @brief Try-safe access to an InlineHook by its ID using a non-blocking lock. | ||
| 871 | * @details Provides a non-blocking alternative to with_inline_hook(). The callback | ||
| 872 | * is invoked only if the lock is immediately acquired via std::try_to_lock. | ||
| 873 | * Note: try_to_lock only avoids blocking on initial acquisition - it does NOT | ||
| 874 | * make callbacks safe to re-enter HookManager methods that also acquire the | ||
| 875 | * same non-recursive mutex (e.g., enable_hook, disable_hook). If a callback | ||
| 876 | * needs to call those methods, it must release the lock first or perform those | ||
| 877 | * calls asynchronously to avoid deadlock. See with_inline_hook for the blocking | ||
| 878 | * analogue. | ||
| 879 | * @param hook_id The name of the inline hook. | ||
| 880 | * @param fn The callback to invoke with the hook reference. | ||
| 881 | * @return std::optional<R> The callback's return value. Returns std::nullopt if either | ||
| 882 | * the lock could not be acquired or the hook was not found. | ||
| 883 | */ | ||
| 884 | template <typename F> | ||
| 885 | requires std::invocable<F, InlineHook &> && | ||
| 886 | (!std::is_void_v<std::invoke_result_t<F, InlineHook &>>) && | ||
| 887 | (!std::is_reference_v<std::invoke_result_t<F, InlineHook &>>) | ||
| 888 | 3 | [[nodiscard]] auto try_with_inline_hook(std::string_view hook_id, F &&fn) | |
| 889 | -> std::optional<std::invoke_result_t<F, InlineHook &>> | ||
| 890 | { | ||
| 891 |
3/6std::optional<std::invoke_result<HookManagerTest_TryWithInlineHook_Success_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}, DetourModKit::InlineHook&>::type> DetourModKit::HookManager::try_with_inline_hook<HookManagerTest_TryWithInlineHook_Success_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_TryWithInlineHook_Success_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}&&):
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 6 taken 1 time.
std::optional<std::invoke_result<HookManagerTest_TryWithInlineHook_NotFound_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}, DetourModKit::InlineHook&>::type> DetourModKit::HookManager::try_with_inline_hook<HookManagerTest_TryWithInlineHook_NotFound_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_TryWithInlineHook_NotFound_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}&&):
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 8 taken 1 time.
std::optional<std::invoke_result<HookManagerTest_TryWithInlineHook_CallbackExecutesSuccessfully_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}, DetourModKit::InlineHook&>::type> DetourModKit::HookManager::try_with_inline_hook<HookManagerTest_TryWithInlineHook_CallbackExecutesSuccessfully_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_TryWithInlineHook_CallbackExecutesSuccessfully_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}&&):
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 8 taken 1 time.
|
3 | if (get_reentrancy_guard() > 0) |
| 892 | { | ||
| 893 | ✗ | m_logger.error("HookManager: Reentrant callback detected in try_with_inline_hook('{}')! Callback holding m_hooks_mutex must not call HookManager methods that acquire a unique_lock (remove_hook, enable_hook, disable_hook, create_*_hook). Perform mutations outside the callback or use an asynchronous operation.", hook_id); | |
| 894 | ✗ | return std::nullopt; | |
| 895 | } | ||
| 896 |
3/6std::optional<std::invoke_result<HookManagerTest_TryWithInlineHook_Success_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}, DetourModKit::InlineHook&>::type> DetourModKit::HookManager::try_with_inline_hook<HookManagerTest_TryWithInlineHook_Success_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_TryWithInlineHook_Success_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}&&):
✓ Branch 6 → 7 taken 1 time.
✗ Branch 6 → 42 not taken.
std::optional<std::invoke_result<HookManagerTest_TryWithInlineHook_NotFound_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}, DetourModKit::InlineHook&>::type> DetourModKit::HookManager::try_with_inline_hook<HookManagerTest_TryWithInlineHook_NotFound_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_TryWithInlineHook_NotFound_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}&&):
✓ Branch 8 → 9 taken 1 time.
✗ Branch 8 → 49 not taken.
std::optional<std::invoke_result<HookManagerTest_TryWithInlineHook_CallbackExecutesSuccessfully_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}, DetourModKit::InlineHook&>::type> DetourModKit::HookManager::try_with_inline_hook<HookManagerTest_TryWithInlineHook_CallbackExecutesSuccessfully_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_TryWithInlineHook_CallbackExecutesSuccessfully_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}&&):
✓ Branch 8 → 9 taken 1 time.
✗ Branch 8 → 49 not taken.
|
3 | std::shared_lock<std::shared_mutex> lock(m_hooks_mutex, std::try_to_lock); |
| 897 |
3/6std::optional<std::invoke_result<HookManagerTest_TryWithInlineHook_Success_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}, DetourModKit::InlineHook&>::type> DetourModKit::HookManager::try_with_inline_hook<HookManagerTest_TryWithInlineHook_Success_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_TryWithInlineHook_Success_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}&&):
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 10 taken 1 time.
std::optional<std::invoke_result<HookManagerTest_TryWithInlineHook_NotFound_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}, DetourModKit::InlineHook&>::type> DetourModKit::HookManager::try_with_inline_hook<HookManagerTest_TryWithInlineHook_NotFound_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_TryWithInlineHook_NotFound_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}&&):
✗ Branch 10 → 11 not taken.
✓ Branch 10 → 14 taken 1 time.
std::optional<std::invoke_result<HookManagerTest_TryWithInlineHook_CallbackExecutesSuccessfully_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}, DetourModKit::InlineHook&>::type> DetourModKit::HookManager::try_with_inline_hook<HookManagerTest_TryWithInlineHook_CallbackExecutesSuccessfully_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_TryWithInlineHook_CallbackExecutesSuccessfully_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}&&):
✗ Branch 10 → 11 not taken.
✓ Branch 10 → 14 taken 1 time.
|
3 | if (!lock.owns_lock()) |
| 898 | { | ||
| 899 | ✗ | return std::nullopt; | |
| 900 | } | ||
| 901 | 3 | ReentrancyGuard guard(get_reentrancy_guard()); | |
| 902 |
3/6std::optional<std::invoke_result<HookManagerTest_TryWithInlineHook_Success_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}, DetourModKit::InlineHook&>::type> DetourModKit::HookManager::try_with_inline_hook<HookManagerTest_TryWithInlineHook_Success_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_TryWithInlineHook_Success_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}&&):
✓ Branch 12 → 13 taken 1 time.
✗ Branch 12 → 38 not taken.
std::optional<std::invoke_result<HookManagerTest_TryWithInlineHook_NotFound_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}, DetourModKit::InlineHook&>::type> DetourModKit::HookManager::try_with_inline_hook<HookManagerTest_TryWithInlineHook_NotFound_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_TryWithInlineHook_NotFound_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}&&):
✓ Branch 16 → 17 taken 1 time.
✗ Branch 16 → 45 not taken.
std::optional<std::invoke_result<HookManagerTest_TryWithInlineHook_CallbackExecutesSuccessfully_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}, DetourModKit::InlineHook&>::type> DetourModKit::HookManager::try_with_inline_hook<HookManagerTest_TryWithInlineHook_CallbackExecutesSuccessfully_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_TryWithInlineHook_CallbackExecutesSuccessfully_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}&&):
✓ Branch 16 → 17 taken 1 time.
✗ Branch 16 → 45 not taken.
|
3 | auto it = m_hooks.find(hook_id); |
| 903 |
8/18std::optional<std::invoke_result<HookManagerTest_TryWithInlineHook_Success_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}, DetourModKit::InlineHook&>::type> DetourModKit::HookManager::try_with_inline_hook<HookManagerTest_TryWithInlineHook_Success_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_TryWithInlineHook_Success_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}&&):
✓ Branch 15 → 16 taken 1 time.
✗ Branch 15 → 21 not taken.
✓ Branch 19 → 20 taken 1 time.
✗ Branch 19 → 21 not taken.
✓ Branch 22 → 23 taken 1 time.
✗ Branch 22 → 31 not taken.
std::optional<std::invoke_result<HookManagerTest_TryWithInlineHook_NotFound_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}, DetourModKit::InlineHook&>::type> DetourModKit::HookManager::try_with_inline_hook<HookManagerTest_TryWithInlineHook_NotFound_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_TryWithInlineHook_NotFound_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}&&):
✗ Branch 19 → 20 not taken.
✓ Branch 19 → 25 taken 1 time.
✗ Branch 23 → 24 not taken.
✗ Branch 23 → 25 not taken.
✗ Branch 26 → 27 not taken.
✓ Branch 26 → 35 taken 1 time.
std::optional<std::invoke_result<HookManagerTest_TryWithInlineHook_CallbackExecutesSuccessfully_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}, DetourModKit::InlineHook&>::type> DetourModKit::HookManager::try_with_inline_hook<HookManagerTest_TryWithInlineHook_CallbackExecutesSuccessfully_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_TryWithInlineHook_CallbackExecutesSuccessfully_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}&&):
✓ Branch 19 → 20 taken 1 time.
✗ Branch 19 → 25 not taken.
✓ Branch 23 → 24 taken 1 time.
✗ Branch 23 → 25 not taken.
✓ Branch 26 → 27 taken 1 time.
✗ Branch 26 → 35 not taken.
|
3 | if (it != m_hooks.end() && it->second->get_type() == HookType::Inline) |
| 904 | { | ||
| 905 |
2/6std::optional<std::invoke_result<HookManagerTest_TryWithInlineHook_Success_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}, DetourModKit::InlineHook&>::type> DetourModKit::HookManager::try_with_inline_hook<HookManagerTest_TryWithInlineHook_Success_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_TryWithInlineHook_Success_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}&&):
✓ Branch 27 → 28 taken 1 time.
✗ Branch 27 → 37 not taken.
std::optional<std::invoke_result<HookManagerTest_TryWithInlineHook_NotFound_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}, DetourModKit::InlineHook&>::type> DetourModKit::HookManager::try_with_inline_hook<HookManagerTest_TryWithInlineHook_NotFound_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_TryWithInlineHook_NotFound_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}&&):
✗ Branch 31 → 32 not taken.
✗ Branch 31 → 43 not taken.
std::optional<std::invoke_result<HookManagerTest_TryWithInlineHook_CallbackExecutesSuccessfully_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}, DetourModKit::InlineHook&>::type> DetourModKit::HookManager::try_with_inline_hook<HookManagerTest_TryWithInlineHook_CallbackExecutesSuccessfully_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_TryWithInlineHook_CallbackExecutesSuccessfully_Test::TestBody()::{lambda(DetourModKit::InlineHook&)#1}&&):
✓ Branch 31 → 32 taken 1 time.
✗ Branch 31 → 43 not taken.
|
4 | return std::invoke(std::forward<F>(fn), static_cast<InlineHook &>(*it->second)); |
| 906 | } | ||
| 907 | 1 | return std::nullopt; | |
| 908 | 3 | } | |
| 909 | |||
| 910 | /** | ||
| 911 | * @brief Safely accesses a MidHook by its ID while holding the internal lock. | ||
| 912 | * @details The callback is invoked with a reference to the MidHook while the | ||
| 913 | * shared_mutex is held as a reader, preventing concurrent removal. | ||
| 914 | * @warning DANGER: Any callback holding m_hooks_mutex must NOT call methods that | ||
| 915 | * acquire a unique_lock (remove_hook, enable_hook, disable_hook, create_*_hook) | ||
| 916 | * because those calls will deadlock. Perform such mutations outside the callback | ||
| 917 | * or use an asynchronous/posted operation that does not hold m_hooks_mutex. | ||
| 918 | * @tparam F Callable type accepting (MidHook&) and returning a value. | ||
| 919 | * @param hook_id The name of the mid hook. | ||
| 920 | * @param fn The callback to invoke with the hook reference. | ||
| 921 | * @return std::optional<R> The callback's return value, or std::nullopt if hook not found. | ||
| 922 | */ | ||
| 923 | template <typename F> | ||
| 924 | requires std::invocable<F, MidHook &> && | ||
| 925 | (!std::is_void_v<std::invoke_result_t<F, MidHook &>>) && | ||
| 926 | (!std::is_reference_v<std::invoke_result_t<F, MidHook &>>) | ||
| 927 | 7 | [[nodiscard]] auto with_mid_hook(std::string_view hook_id, F &&fn) | |
| 928 | -> std::optional<std::invoke_result_t<F, MidHook &>> | ||
| 929 | { | ||
| 930 |
7/14std::optional<std::invoke_result<HookManagerTest_WithMidHook_NotFound_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}, DetourModKit::MidHook&>::type> DetourModKit::HookManager::with_mid_hook<HookManagerTest_WithMidHook_NotFound_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_WithMidHook_NotFound_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}&&):
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 8 taken 1 time.
std::optional<std::invoke_result<HookManagerTest_WithMidHook_WrongType_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}, DetourModKit::MidHook&>::type> DetourModKit::HookManager::with_mid_hook<HookManagerTest_WithMidHook_WrongType_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_WithMidHook_WrongType_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}&&):
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 8 taken 1 time.
std::optional<std::invoke_result<HookManagerTest_RealMidHook_WithCallback_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}, DetourModKit::MidHook&>::type> DetourModKit::HookManager::with_mid_hook<HookManagerTest_RealMidHook_WithCallback_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_RealMidHook_WithCallback_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}&&):
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 8 taken 1 time.
std::optional<std::invoke_result<HookManagerTest_WithMidHook_SuccessCallback_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}, DetourModKit::MidHook&>::type> DetourModKit::HookManager::with_mid_hook<HookManagerTest_WithMidHook_SuccessCallback_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_WithMidHook_SuccessCallback_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}&&):
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 6 taken 1 time.
std::optional<std::invoke_result<HookManagerTest_MidHook_GetDestination_Noexcept_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}, DetourModKit::MidHook&>::type> DetourModKit::HookManager::with_mid_hook<HookManagerTest_MidHook_GetDestination_Noexcept_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_MidHook_GetDestination_Noexcept_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}&&):
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 8 taken 1 time.
std::optional<std::invoke_result<HookManagerTest_WithMidHook_DirectEnableDisable_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}, DetourModKit::MidHook&>::type> DetourModKit::HookManager::with_mid_hook<HookManagerTest_WithMidHook_DirectEnableDisable_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_WithMidHook_DirectEnableDisable_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}&&):
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 8 taken 1 time.
std::optional<std::invoke_result<HookManagerTest_WithMidHook_CallbackExecutesSuccessfully_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}, DetourModKit::MidHook&>::type> DetourModKit::HookManager::with_mid_hook<HookManagerTest_WithMidHook_CallbackExecutesSuccessfully_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_WithMidHook_CallbackExecutesSuccessfully_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}&&):
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 8 taken 1 time.
|
7 | if (get_reentrancy_guard() > 0) |
| 931 | { | ||
| 932 | ✗ | m_logger.error("HookManager: Reentrant callback detected in with_mid_hook('{}')! Callback holding m_hooks_mutex must not call HookManager methods that acquire a unique_lock (remove_hook, enable_hook, disable_hook, create_*_hook). Perform mutations outside the callback or use an asynchronous operation.", hook_id); | |
| 933 | ✗ | return std::nullopt; | |
| 934 | } | ||
| 935 |
7/14std::optional<std::invoke_result<HookManagerTest_WithMidHook_NotFound_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}, DetourModKit::MidHook&>::type> DetourModKit::HookManager::with_mid_hook<HookManagerTest_WithMidHook_NotFound_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_WithMidHook_NotFound_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}&&):
✓ Branch 8 → 9 taken 1 time.
✗ Branch 8 → 44 not taken.
std::optional<std::invoke_result<HookManagerTest_WithMidHook_WrongType_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}, DetourModKit::MidHook&>::type> DetourModKit::HookManager::with_mid_hook<HookManagerTest_WithMidHook_WrongType_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_WithMidHook_WrongType_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}&&):
✓ Branch 8 → 9 taken 1 time.
✗ Branch 8 → 44 not taken.
std::optional<std::invoke_result<HookManagerTest_RealMidHook_WithCallback_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}, DetourModKit::MidHook&>::type> DetourModKit::HookManager::with_mid_hook<HookManagerTest_RealMidHook_WithCallback_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_RealMidHook_WithCallback_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}&&):
✓ Branch 8 → 9 taken 1 time.
✗ Branch 8 → 44 not taken.
std::optional<std::invoke_result<HookManagerTest_WithMidHook_SuccessCallback_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}, DetourModKit::MidHook&>::type> DetourModKit::HookManager::with_mid_hook<HookManagerTest_WithMidHook_SuccessCallback_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_WithMidHook_SuccessCallback_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}&&):
✓ Branch 6 → 7 taken 1 time.
✗ Branch 6 → 39 not taken.
std::optional<std::invoke_result<HookManagerTest_MidHook_GetDestination_Noexcept_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}, DetourModKit::MidHook&>::type> DetourModKit::HookManager::with_mid_hook<HookManagerTest_MidHook_GetDestination_Noexcept_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_MidHook_GetDestination_Noexcept_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}&&):
✓ Branch 8 → 9 taken 1 time.
✗ Branch 8 → 44 not taken.
std::optional<std::invoke_result<HookManagerTest_WithMidHook_DirectEnableDisable_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}, DetourModKit::MidHook&>::type> DetourModKit::HookManager::with_mid_hook<HookManagerTest_WithMidHook_DirectEnableDisable_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_WithMidHook_DirectEnableDisable_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}&&):
✓ Branch 8 → 9 taken 1 time.
✗ Branch 8 → 44 not taken.
std::optional<std::invoke_result<HookManagerTest_WithMidHook_CallbackExecutesSuccessfully_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}, DetourModKit::MidHook&>::type> DetourModKit::HookManager::with_mid_hook<HookManagerTest_WithMidHook_CallbackExecutesSuccessfully_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_WithMidHook_CallbackExecutesSuccessfully_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}&&):
✓ Branch 8 → 9 taken 1 time.
✗ Branch 8 → 44 not taken.
|
7 | std::shared_lock<std::shared_mutex> lock(m_hooks_mutex); |
| 936 | 7 | ReentrancyGuard guard(get_reentrancy_guard()); | |
| 937 |
7/14std::optional<std::invoke_result<HookManagerTest_WithMidHook_NotFound_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}, DetourModKit::MidHook&>::type> DetourModKit::HookManager::with_mid_hook<HookManagerTest_WithMidHook_NotFound_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_WithMidHook_NotFound_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}&&):
✓ Branch 11 → 12 taken 1 time.
✗ Branch 11 → 40 not taken.
std::optional<std::invoke_result<HookManagerTest_WithMidHook_WrongType_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}, DetourModKit::MidHook&>::type> DetourModKit::HookManager::with_mid_hook<HookManagerTest_WithMidHook_WrongType_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_WithMidHook_WrongType_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}&&):
✓ Branch 11 → 12 taken 1 time.
✗ Branch 11 → 40 not taken.
std::optional<std::invoke_result<HookManagerTest_RealMidHook_WithCallback_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}, DetourModKit::MidHook&>::type> DetourModKit::HookManager::with_mid_hook<HookManagerTest_RealMidHook_WithCallback_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_RealMidHook_WithCallback_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}&&):
✓ Branch 11 → 12 taken 1 time.
✗ Branch 11 → 40 not taken.
std::optional<std::invoke_result<HookManagerTest_WithMidHook_SuccessCallback_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}, DetourModKit::MidHook&>::type> DetourModKit::HookManager::with_mid_hook<HookManagerTest_WithMidHook_SuccessCallback_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_WithMidHook_SuccessCallback_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}&&):
✓ Branch 9 → 10 taken 1 time.
✗ Branch 9 → 35 not taken.
std::optional<std::invoke_result<HookManagerTest_MidHook_GetDestination_Noexcept_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}, DetourModKit::MidHook&>::type> DetourModKit::HookManager::with_mid_hook<HookManagerTest_MidHook_GetDestination_Noexcept_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_MidHook_GetDestination_Noexcept_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}&&):
✓ Branch 11 → 12 taken 1 time.
✗ Branch 11 → 40 not taken.
std::optional<std::invoke_result<HookManagerTest_WithMidHook_DirectEnableDisable_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}, DetourModKit::MidHook&>::type> DetourModKit::HookManager::with_mid_hook<HookManagerTest_WithMidHook_DirectEnableDisable_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_WithMidHook_DirectEnableDisable_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}&&):
✓ Branch 11 → 12 taken 1 time.
✗ Branch 11 → 40 not taken.
std::optional<std::invoke_result<HookManagerTest_WithMidHook_CallbackExecutesSuccessfully_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}, DetourModKit::MidHook&>::type> DetourModKit::HookManager::with_mid_hook<HookManagerTest_WithMidHook_CallbackExecutesSuccessfully_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_WithMidHook_CallbackExecutesSuccessfully_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}&&):
✓ Branch 11 → 12 taken 1 time.
✗ Branch 11 → 40 not taken.
|
7 | auto it = m_hooks.find(hook_id); |
| 938 |
20/42std::optional<std::invoke_result<HookManagerTest_WithMidHook_NotFound_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}, DetourModKit::MidHook&>::type> DetourModKit::HookManager::with_mid_hook<HookManagerTest_WithMidHook_NotFound_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_WithMidHook_NotFound_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}&&):
✗ Branch 14 → 15 not taken.
✓ Branch 14 → 20 taken 1 time.
✗ Branch 18 → 19 not taken.
✗ Branch 18 → 20 not taken.
✗ Branch 21 → 22 not taken.
✓ Branch 21 → 30 taken 1 time.
std::optional<std::invoke_result<HookManagerTest_WithMidHook_WrongType_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}, DetourModKit::MidHook&>::type> DetourModKit::HookManager::with_mid_hook<HookManagerTest_WithMidHook_WrongType_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_WithMidHook_WrongType_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}&&):
✓ Branch 14 → 15 taken 1 time.
✗ Branch 14 → 20 not taken.
✗ Branch 18 → 19 not taken.
✓ Branch 18 → 20 taken 1 time.
✗ Branch 21 → 22 not taken.
✓ Branch 21 → 30 taken 1 time.
std::optional<std::invoke_result<HookManagerTest_RealMidHook_WithCallback_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}, DetourModKit::MidHook&>::type> DetourModKit::HookManager::with_mid_hook<HookManagerTest_RealMidHook_WithCallback_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_RealMidHook_WithCallback_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}&&):
✓ Branch 14 → 15 taken 1 time.
✗ Branch 14 → 20 not taken.
✓ Branch 18 → 19 taken 1 time.
✗ Branch 18 → 20 not taken.
✓ Branch 21 → 22 taken 1 time.
✗ Branch 21 → 30 not taken.
std::optional<std::invoke_result<HookManagerTest_WithMidHook_SuccessCallback_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}, DetourModKit::MidHook&>::type> DetourModKit::HookManager::with_mid_hook<HookManagerTest_WithMidHook_SuccessCallback_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_WithMidHook_SuccessCallback_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}&&):
✓ Branch 12 → 13 taken 1 time.
✗ Branch 12 → 18 not taken.
✓ Branch 16 → 17 taken 1 time.
✗ Branch 16 → 18 not taken.
✓ Branch 19 → 20 taken 1 time.
✗ Branch 19 → 28 not taken.
std::optional<std::invoke_result<HookManagerTest_MidHook_GetDestination_Noexcept_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}, DetourModKit::MidHook&>::type> DetourModKit::HookManager::with_mid_hook<HookManagerTest_MidHook_GetDestination_Noexcept_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_MidHook_GetDestination_Noexcept_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}&&):
✓ Branch 14 → 15 taken 1 time.
✗ Branch 14 → 20 not taken.
✓ Branch 18 → 19 taken 1 time.
✗ Branch 18 → 20 not taken.
✓ Branch 21 → 22 taken 1 time.
✗ Branch 21 → 30 not taken.
std::optional<std::invoke_result<HookManagerTest_WithMidHook_DirectEnableDisable_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}, DetourModKit::MidHook&>::type> DetourModKit::HookManager::with_mid_hook<HookManagerTest_WithMidHook_DirectEnableDisable_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_WithMidHook_DirectEnableDisable_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}&&):
✓ Branch 14 → 15 taken 1 time.
✗ Branch 14 → 20 not taken.
✓ Branch 18 → 19 taken 1 time.
✗ Branch 18 → 20 not taken.
✓ Branch 21 → 22 taken 1 time.
✗ Branch 21 → 30 not taken.
std::optional<std::invoke_result<HookManagerTest_WithMidHook_CallbackExecutesSuccessfully_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}, DetourModKit::MidHook&>::type> DetourModKit::HookManager::with_mid_hook<HookManagerTest_WithMidHook_CallbackExecutesSuccessfully_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_WithMidHook_CallbackExecutesSuccessfully_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}&&):
✓ Branch 14 → 15 taken 1 time.
✗ Branch 14 → 20 not taken.
✓ Branch 18 → 19 taken 1 time.
✗ Branch 18 → 20 not taken.
✓ Branch 21 → 22 taken 1 time.
✗ Branch 21 → 30 not taken.
|
7 | if (it != m_hooks.end() && it->second->get_type() == HookType::Mid) |
| 939 | { | ||
| 940 |
5/14std::optional<std::invoke_result<HookManagerTest_WithMidHook_NotFound_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}, DetourModKit::MidHook&>::type> DetourModKit::HookManager::with_mid_hook<HookManagerTest_WithMidHook_NotFound_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_WithMidHook_NotFound_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}&&):
✗ Branch 26 → 27 not taken.
✗ Branch 26 → 38 not taken.
std::optional<std::invoke_result<HookManagerTest_WithMidHook_WrongType_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}, DetourModKit::MidHook&>::type> DetourModKit::HookManager::with_mid_hook<HookManagerTest_WithMidHook_WrongType_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_WithMidHook_WrongType_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}&&):
✗ Branch 26 → 27 not taken.
✗ Branch 26 → 38 not taken.
std::optional<std::invoke_result<HookManagerTest_RealMidHook_WithCallback_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}, DetourModKit::MidHook&>::type> DetourModKit::HookManager::with_mid_hook<HookManagerTest_RealMidHook_WithCallback_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_RealMidHook_WithCallback_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}&&):
✓ Branch 26 → 27 taken 1 time.
✗ Branch 26 → 38 not taken.
std::optional<std::invoke_result<HookManagerTest_WithMidHook_SuccessCallback_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}, DetourModKit::MidHook&>::type> DetourModKit::HookManager::with_mid_hook<HookManagerTest_WithMidHook_SuccessCallback_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_WithMidHook_SuccessCallback_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}&&):
✓ Branch 24 → 25 taken 1 time.
✗ Branch 24 → 34 not taken.
std::optional<std::invoke_result<HookManagerTest_MidHook_GetDestination_Noexcept_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}, DetourModKit::MidHook&>::type> DetourModKit::HookManager::with_mid_hook<HookManagerTest_MidHook_GetDestination_Noexcept_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_MidHook_GetDestination_Noexcept_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}&&):
✓ Branch 26 → 27 taken 1 time.
✗ Branch 26 → 38 not taken.
std::optional<std::invoke_result<HookManagerTest_WithMidHook_DirectEnableDisable_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}, DetourModKit::MidHook&>::type> DetourModKit::HookManager::with_mid_hook<HookManagerTest_WithMidHook_DirectEnableDisable_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_WithMidHook_DirectEnableDisable_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}&&):
✓ Branch 26 → 27 taken 1 time.
✗ Branch 26 → 38 not taken.
std::optional<std::invoke_result<HookManagerTest_WithMidHook_CallbackExecutesSuccessfully_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}, DetourModKit::MidHook&>::type> DetourModKit::HookManager::with_mid_hook<HookManagerTest_WithMidHook_CallbackExecutesSuccessfully_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_WithMidHook_CallbackExecutesSuccessfully_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}&&):
✓ Branch 26 → 27 taken 1 time.
✗ Branch 26 → 38 not taken.
|
10 | return std::invoke(std::forward<F>(fn), static_cast<MidHook &>(*it->second)); |
| 941 | } | ||
| 942 | 2 | return std::nullopt; | |
| 943 | 7 | } | |
| 944 | |||
| 945 | /** | ||
| 946 | * @brief Safely accesses a MidHook by its ID for a void-returning callback. | ||
| 947 | * @details Same locking and reentrancy semantics as the value-returning overload. | ||
| 948 | * @param hook_id The name of the mid hook. | ||
| 949 | * @param fn The void-returning callback to invoke with the hook reference. | ||
| 950 | * @return true if the hook was found and the callback was invoked, false otherwise. | ||
| 951 | */ | ||
| 952 | template <typename F> | ||
| 953 | requires std::invocable<F, MidHook &> && | ||
| 954 | std::is_void_v<std::invoke_result_t<F, MidHook &>> | ||
| 955 | 2 | [[nodiscard]] bool with_mid_hook(std::string_view hook_id, F &&fn) | |
| 956 | { | ||
| 957 |
2/4bool DetourModKit::HookManager::with_mid_hook<HookManagerTest_WithMidHook_VoidCallback_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_WithMidHook_VoidCallback_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}&&):
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 6 taken 1 time.
bool DetourModKit::HookManager::with_mid_hook<HookManagerTest_WithMidHook_VoidCallback_NotFound_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_WithMidHook_VoidCallback_NotFound_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}&&):
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 6 taken 1 time.
|
2 | if (get_reentrancy_guard() > 0) |
| 958 | { | ||
| 959 | ✗ | m_logger.error("HookManager: Reentrant callback detected in with_mid_hook('{}')! Callback holding m_hooks_mutex must not call HookManager methods that acquire a unique_lock (remove_hook, enable_hook, disable_hook, create_*_hook). Perform mutations outside the callback or use an asynchronous operation.", hook_id); | |
| 960 | ✗ | return false; | |
| 961 | } | ||
| 962 |
2/4bool DetourModKit::HookManager::with_mid_hook<HookManagerTest_WithMidHook_VoidCallback_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_WithMidHook_VoidCallback_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}&&):
✓ Branch 6 → 7 taken 1 time.
✗ Branch 6 → 36 not taken.
bool DetourModKit::HookManager::with_mid_hook<HookManagerTest_WithMidHook_VoidCallback_NotFound_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_WithMidHook_VoidCallback_NotFound_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}&&):
✓ Branch 6 → 7 taken 1 time.
✗ Branch 6 → 36 not taken.
|
2 | std::shared_lock<std::shared_mutex> lock(m_hooks_mutex); |
| 963 | 2 | ReentrancyGuard guard(get_reentrancy_guard()); | |
| 964 |
2/4bool DetourModKit::HookManager::with_mid_hook<HookManagerTest_WithMidHook_VoidCallback_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_WithMidHook_VoidCallback_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}&&):
✓ Branch 9 → 10 taken 1 time.
✗ Branch 9 → 32 not taken.
bool DetourModKit::HookManager::with_mid_hook<HookManagerTest_WithMidHook_VoidCallback_NotFound_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_WithMidHook_VoidCallback_NotFound_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}&&):
✓ Branch 9 → 10 taken 1 time.
✗ Branch 9 → 32 not taken.
|
2 | auto it = m_hooks.find(hook_id); |
| 965 |
5/12bool DetourModKit::HookManager::with_mid_hook<HookManagerTest_WithMidHook_VoidCallback_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_WithMidHook_VoidCallback_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}&&):
✓ Branch 12 → 13 taken 1 time.
✗ Branch 12 → 18 not taken.
✓ Branch 16 → 17 taken 1 time.
✗ Branch 16 → 18 not taken.
✓ Branch 19 → 20 taken 1 time.
✗ Branch 19 → 26 not taken.
bool DetourModKit::HookManager::with_mid_hook<HookManagerTest_WithMidHook_VoidCallback_NotFound_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_WithMidHook_VoidCallback_NotFound_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}&&):
✗ Branch 12 → 13 not taken.
✓ Branch 12 → 18 taken 1 time.
✗ Branch 16 → 17 not taken.
✗ Branch 16 → 18 not taken.
✗ Branch 19 → 20 not taken.
✓ Branch 19 → 26 taken 1 time.
|
2 | if (it != m_hooks.end() && it->second->get_type() == HookType::Mid) |
| 966 | { | ||
| 967 |
1/4bool DetourModKit::HookManager::with_mid_hook<HookManagerTest_WithMidHook_VoidCallback_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_WithMidHook_VoidCallback_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}&&):
✓ Branch 24 → 25 taken 1 time.
✗ Branch 24 → 32 not taken.
bool DetourModKit::HookManager::with_mid_hook<HookManagerTest_WithMidHook_VoidCallback_NotFound_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_WithMidHook_VoidCallback_NotFound_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}&&):
✗ Branch 24 → 25 not taken.
✗ Branch 24 → 32 not taken.
|
2 | std::invoke(std::forward<F>(fn), static_cast<MidHook &>(*it->second)); |
| 968 | 1 | return true; | |
| 969 | } | ||
| 970 | 1 | return false; | |
| 971 | 2 | } | |
| 972 | |||
| 973 | /** | ||
| 974 | * @brief Try-safe access to a MidHook by its ID using a non-blocking lock. | ||
| 975 | * @details Provides a non-blocking alternative to with_mid_hook(). The callback | ||
| 976 | * is invoked only if the lock is immediately acquired via std::try_to_lock. | ||
| 977 | * Note: try_to_lock only avoids blocking on initial acquisition - it does NOT | ||
| 978 | * make callbacks safe to re-enter HookManager methods that also acquire the | ||
| 979 | * same non-recursive mutex (e.g., enable_hook, disable_hook). If a callback | ||
| 980 | * needs to call those methods, it must release the lock first or perform those | ||
| 981 | * calls asynchronously to avoid deadlock. See with_mid_hook for the blocking | ||
| 982 | * analogue. | ||
| 983 | * @param hook_id The name of the mid hook. | ||
| 984 | * @param fn The callback to invoke with the hook reference. | ||
| 985 | * @return std::optional<R> The callback's return value. Returns std::nullopt if either | ||
| 986 | * the lock could not be acquired or the hook was not found. | ||
| 987 | */ | ||
| 988 | template <typename F> | ||
| 989 | requires std::invocable<F, MidHook &> && | ||
| 990 | (!std::is_void_v<std::invoke_result_t<F, MidHook &>>) && | ||
| 991 | (!std::is_reference_v<std::invoke_result_t<F, MidHook &>>) | ||
| 992 | 3 | [[nodiscard]] auto try_with_mid_hook(std::string_view hook_id, F &&fn) | |
| 993 | -> std::optional<std::invoke_result_t<F, MidHook &>> | ||
| 994 | { | ||
| 995 |
3/6std::optional<std::invoke_result<HookManagerTest_TryWithMidHook_Success_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}, DetourModKit::MidHook&>::type> DetourModKit::HookManager::try_with_mid_hook<HookManagerTest_TryWithMidHook_Success_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_TryWithMidHook_Success_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}&&):
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 6 taken 1 time.
std::optional<std::invoke_result<HookManagerTest_TryWithMidHook_NotFound_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}, DetourModKit::MidHook&>::type> DetourModKit::HookManager::try_with_mid_hook<HookManagerTest_TryWithMidHook_NotFound_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_TryWithMidHook_NotFound_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}&&):
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 8 taken 1 time.
std::optional<std::invoke_result<HookManagerTest_TryWithMidHook_CallbackExecutesSuccessfully_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}, DetourModKit::MidHook&>::type> DetourModKit::HookManager::try_with_mid_hook<HookManagerTest_TryWithMidHook_CallbackExecutesSuccessfully_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_TryWithMidHook_CallbackExecutesSuccessfully_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}&&):
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 8 taken 1 time.
|
3 | if (get_reentrancy_guard() > 0) |
| 996 | { | ||
| 997 | ✗ | m_logger.error("HookManager: Reentrant callback detected in try_with_mid_hook('{}')! Callback holding m_hooks_mutex must not call HookManager methods that acquire a unique_lock (remove_hook, enable_hook, disable_hook, create_*_hook). Perform mutations outside the callback or use an asynchronous operation.", hook_id); | |
| 998 | ✗ | return std::nullopt; | |
| 999 | } | ||
| 1000 |
3/6std::optional<std::invoke_result<HookManagerTest_TryWithMidHook_Success_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}, DetourModKit::MidHook&>::type> DetourModKit::HookManager::try_with_mid_hook<HookManagerTest_TryWithMidHook_Success_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_TryWithMidHook_Success_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}&&):
✓ Branch 6 → 7 taken 1 time.
✗ Branch 6 → 42 not taken.
std::optional<std::invoke_result<HookManagerTest_TryWithMidHook_NotFound_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}, DetourModKit::MidHook&>::type> DetourModKit::HookManager::try_with_mid_hook<HookManagerTest_TryWithMidHook_NotFound_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_TryWithMidHook_NotFound_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}&&):
✓ Branch 8 → 9 taken 1 time.
✗ Branch 8 → 49 not taken.
std::optional<std::invoke_result<HookManagerTest_TryWithMidHook_CallbackExecutesSuccessfully_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}, DetourModKit::MidHook&>::type> DetourModKit::HookManager::try_with_mid_hook<HookManagerTest_TryWithMidHook_CallbackExecutesSuccessfully_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_TryWithMidHook_CallbackExecutesSuccessfully_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}&&):
✓ Branch 8 → 9 taken 1 time.
✗ Branch 8 → 49 not taken.
|
3 | std::shared_lock<std::shared_mutex> lock(m_hooks_mutex, std::try_to_lock); |
| 1001 |
3/6std::optional<std::invoke_result<HookManagerTest_TryWithMidHook_Success_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}, DetourModKit::MidHook&>::type> DetourModKit::HookManager::try_with_mid_hook<HookManagerTest_TryWithMidHook_Success_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_TryWithMidHook_Success_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}&&):
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 10 taken 1 time.
std::optional<std::invoke_result<HookManagerTest_TryWithMidHook_NotFound_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}, DetourModKit::MidHook&>::type> DetourModKit::HookManager::try_with_mid_hook<HookManagerTest_TryWithMidHook_NotFound_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_TryWithMidHook_NotFound_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}&&):
✗ Branch 10 → 11 not taken.
✓ Branch 10 → 14 taken 1 time.
std::optional<std::invoke_result<HookManagerTest_TryWithMidHook_CallbackExecutesSuccessfully_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}, DetourModKit::MidHook&>::type> DetourModKit::HookManager::try_with_mid_hook<HookManagerTest_TryWithMidHook_CallbackExecutesSuccessfully_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_TryWithMidHook_CallbackExecutesSuccessfully_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}&&):
✗ Branch 10 → 11 not taken.
✓ Branch 10 → 14 taken 1 time.
|
3 | if (!lock.owns_lock()) |
| 1002 | { | ||
| 1003 | ✗ | return std::nullopt; | |
| 1004 | } | ||
| 1005 | 3 | ReentrancyGuard guard(get_reentrancy_guard()); | |
| 1006 |
3/6std::optional<std::invoke_result<HookManagerTest_TryWithMidHook_Success_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}, DetourModKit::MidHook&>::type> DetourModKit::HookManager::try_with_mid_hook<HookManagerTest_TryWithMidHook_Success_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_TryWithMidHook_Success_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}&&):
✓ Branch 12 → 13 taken 1 time.
✗ Branch 12 → 38 not taken.
std::optional<std::invoke_result<HookManagerTest_TryWithMidHook_NotFound_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}, DetourModKit::MidHook&>::type> DetourModKit::HookManager::try_with_mid_hook<HookManagerTest_TryWithMidHook_NotFound_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_TryWithMidHook_NotFound_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}&&):
✓ Branch 16 → 17 taken 1 time.
✗ Branch 16 → 45 not taken.
std::optional<std::invoke_result<HookManagerTest_TryWithMidHook_CallbackExecutesSuccessfully_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}, DetourModKit::MidHook&>::type> DetourModKit::HookManager::try_with_mid_hook<HookManagerTest_TryWithMidHook_CallbackExecutesSuccessfully_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_TryWithMidHook_CallbackExecutesSuccessfully_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}&&):
✓ Branch 16 → 17 taken 1 time.
✗ Branch 16 → 45 not taken.
|
3 | auto it = m_hooks.find(hook_id); |
| 1007 |
8/18std::optional<std::invoke_result<HookManagerTest_TryWithMidHook_Success_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}, DetourModKit::MidHook&>::type> DetourModKit::HookManager::try_with_mid_hook<HookManagerTest_TryWithMidHook_Success_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_TryWithMidHook_Success_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}&&):
✓ Branch 15 → 16 taken 1 time.
✗ Branch 15 → 21 not taken.
✓ Branch 19 → 20 taken 1 time.
✗ Branch 19 → 21 not taken.
✓ Branch 22 → 23 taken 1 time.
✗ Branch 22 → 31 not taken.
std::optional<std::invoke_result<HookManagerTest_TryWithMidHook_NotFound_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}, DetourModKit::MidHook&>::type> DetourModKit::HookManager::try_with_mid_hook<HookManagerTest_TryWithMidHook_NotFound_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_TryWithMidHook_NotFound_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}&&):
✗ Branch 19 → 20 not taken.
✓ Branch 19 → 25 taken 1 time.
✗ Branch 23 → 24 not taken.
✗ Branch 23 → 25 not taken.
✗ Branch 26 → 27 not taken.
✓ Branch 26 → 35 taken 1 time.
std::optional<std::invoke_result<HookManagerTest_TryWithMidHook_CallbackExecutesSuccessfully_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}, DetourModKit::MidHook&>::type> DetourModKit::HookManager::try_with_mid_hook<HookManagerTest_TryWithMidHook_CallbackExecutesSuccessfully_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_TryWithMidHook_CallbackExecutesSuccessfully_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}&&):
✓ Branch 19 → 20 taken 1 time.
✗ Branch 19 → 25 not taken.
✓ Branch 23 → 24 taken 1 time.
✗ Branch 23 → 25 not taken.
✓ Branch 26 → 27 taken 1 time.
✗ Branch 26 → 35 not taken.
|
3 | if (it != m_hooks.end() && it->second->get_type() == HookType::Mid) |
| 1008 | { | ||
| 1009 |
2/6std::optional<std::invoke_result<HookManagerTest_TryWithMidHook_Success_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}, DetourModKit::MidHook&>::type> DetourModKit::HookManager::try_with_mid_hook<HookManagerTest_TryWithMidHook_Success_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_TryWithMidHook_Success_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}&&):
✓ Branch 27 → 28 taken 1 time.
✗ Branch 27 → 37 not taken.
std::optional<std::invoke_result<HookManagerTest_TryWithMidHook_NotFound_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}, DetourModKit::MidHook&>::type> DetourModKit::HookManager::try_with_mid_hook<HookManagerTest_TryWithMidHook_NotFound_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_TryWithMidHook_NotFound_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}&&):
✗ Branch 31 → 32 not taken.
✗ Branch 31 → 43 not taken.
std::optional<std::invoke_result<HookManagerTest_TryWithMidHook_CallbackExecutesSuccessfully_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}, DetourModKit::MidHook&>::type> DetourModKit::HookManager::try_with_mid_hook<HookManagerTest_TryWithMidHook_CallbackExecutesSuccessfully_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}>(std::basic_string_view<char, std::char_traits<char> >, HookManagerTest_TryWithMidHook_CallbackExecutesSuccessfully_Test::TestBody()::{lambda(DetourModKit::MidHook&)#1}&&):
✓ Branch 31 → 32 taken 1 time.
✗ Branch 31 → 43 not taken.
|
4 | return std::invoke(std::forward<F>(fn), static_cast<MidHook &>(*it->second)); |
| 1010 | } | ||
| 1011 | 1 | return std::nullopt; | |
| 1012 | 3 | } | |
| 1013 | |||
| 1014 | private: | ||
| 1015 | /** @brief Internal log entry used to defer logging outside held locks. */ | ||
| 1016 | struct DeferredLogEntry | ||
| 1017 | { | ||
| 1018 | std::string msg; | ||
| 1019 | LogLevel level; | ||
| 1020 | }; | ||
| 1021 | explicit HookManager(Logger &logger = Logger::get_instance()); | ||
| 1022 | |||
| 1023 | mutable std::shared_mutex m_hooks_mutex; | ||
| 1024 | detail::HookMap m_hooks; | ||
| 1025 | detail::VmtHookMap m_vmt_hooks; | ||
| 1026 | Logger &m_logger; | ||
| 1027 | std::shared_ptr<safetyhook::Allocator> m_allocator; | ||
| 1028 | std::atomic<bool> m_shutdown_called{false}; | ||
| 1029 | |||
| 1030 | /** @brief Gate that mutators (create_*_hook, enable_hook, disable_hook, remove_hook) | ||
| 1031 | * acquire shared on entry, allowing shutdown/remove_all_hooks to acquire exclusive | ||
| 1032 | * to block new work. Teardown serialization uses compare_exchange_strong on | ||
| 1033 | * m_shutdown_called rather than a separate mutex. | ||
| 1034 | */ | ||
| 1035 | mutable std::shared_mutex m_mutator_gate; | ||
| 1036 | |||
| 1037 | 68 | [[nodiscard]] int &get_reentrancy_guard() noexcept | |
| 1038 | { | ||
| 1039 | thread_local int reentrancy_counter{0}; | ||
| 1040 | 68 | return reentrancy_counter; | |
| 1041 | } | ||
| 1042 | |||
| 1043 | struct ReentrancyGuard | ||
| 1044 | { | ||
| 1045 | int &counter; | ||
| 1046 | 34 | explicit ReentrancyGuard(int &cnt) noexcept : counter(cnt) { ++counter; } | |
| 1047 | 34 | ~ReentrancyGuard() noexcept { --counter; } | |
| 1048 | ReentrancyGuard(const ReentrancyGuard &) = delete; | ||
| 1049 | ReentrancyGuard &operator=(const ReentrancyGuard &) = delete; | ||
| 1050 | ReentrancyGuard(ReentrancyGuard &&) = delete; | ||
| 1051 | ReentrancyGuard &operator=(ReentrancyGuard &&) = delete; | ||
| 1052 | }; | ||
| 1053 | |||
| 1054 | std::string error_to_string(const safetyhook::InlineHook::Error &err) const; | ||
| 1055 | std::string error_to_string(const safetyhook::MidHook::Error &err) const; | ||
| 1056 | |||
| 1057 | 99 | bool hook_id_exists_locked(std::string_view hook_id) const | |
| 1058 | { | ||
| 1059 |
1/2✓ Branch 3 → 4 taken 99 times.
✗ Branch 3 → 8 not taken.
|
99 | return m_hooks.find(hook_id) != m_hooks.end(); |
| 1060 | } | ||
| 1061 | |||
| 1062 | 22 | bool vmt_hook_exists_locked(std::string_view name) const | |
| 1063 | { | ||
| 1064 |
1/2✓ Branch 3 → 4 taken 22 times.
✗ Branch 3 → 8 not taken.
|
22 | return m_vmt_hooks.find(name) != m_vmt_hooks.end(); |
| 1065 | } | ||
| 1066 | }; | ||
| 1067 | |||
| 1068 | /** | ||
| 1069 | * @brief Convenience wrapper that installs an inline hook by direct address. | ||
| 1070 | * @details Forwards every argument to HookManager::create_inline_hook. | ||
| 1071 | * Returns the registered hook name on success, std::nullopt on | ||
| 1072 | * failure. Diagnostic logging on failure is delegated to the | ||
| 1073 | * underlying create_inline_hook call, which already formats a | ||
| 1074 | * richly-detailed Error line for every failure code; this | ||
| 1075 | * wrapper does not emit a duplicate. | ||
| 1076 | */ | ||
| 1077 | 3 | [[nodiscard]] inline std::optional<std::string> try_install_inline( | |
| 1078 | std::string_view name, | ||
| 1079 | uintptr_t target_address, | ||
| 1080 | void *detour_function, | ||
| 1081 | void **original_trampoline, | ||
| 1082 | const HookConfig &config = HookConfig()) | ||
| 1083 | { | ||
| 1084 |
1/2✓ Branch 2 → 3 taken 3 times.
✗ Branch 2 → 15 not taken.
|
3 | auto result = HookManager::get_instance().create_inline_hook( |
| 1085 |
1/2✓ Branch 3 → 4 taken 3 times.
✗ Branch 3 → 15 not taken.
|
3 | name, target_address, detour_function, original_trampoline, config); |
| 1086 |
2/2✓ Branch 5 → 6 taken 1 time.
✓ Branch 5 → 9 taken 2 times.
|
3 | if (result) |
| 1087 | { | ||
| 1088 |
1/2✓ Branch 7 → 8 taken 1 time.
✗ Branch 7 → 13 not taken.
|
1 | return *result; |
| 1089 | } | ||
| 1090 | 2 | return std::nullopt; | |
| 1091 | 3 | } | |
| 1092 | |||
| 1093 | /** | ||
| 1094 | * @brief Convenience wrapper that installs an inline hook by AOB scan. | ||
| 1095 | * @details Diagnostic logging on failure is delegated to the underlying | ||
| 1096 | * create_inline_hook_aob call (pattern-resolution failures and | ||
| 1097 | * create_inline_hook failures both emit their own Error line), | ||
| 1098 | * so this wrapper does not emit a duplicate. | ||
| 1099 | */ | ||
| 1100 | 1 | [[nodiscard]] inline std::optional<std::string> try_install_inline_aob( | |
| 1101 | std::string_view name, | ||
| 1102 | uintptr_t module_base, | ||
| 1103 | size_t module_size, | ||
| 1104 | std::string_view aob_pattern, | ||
| 1105 | std::ptrdiff_t aob_offset, | ||
| 1106 | void *detour_function, | ||
| 1107 | void **original_trampoline, | ||
| 1108 | const HookConfig &config = HookConfig()) | ||
| 1109 | { | ||
| 1110 |
1/2✓ Branch 2 → 3 taken 1 time.
✗ Branch 2 → 15 not taken.
|
1 | auto result = HookManager::get_instance().create_inline_hook_aob( |
| 1111 | name, module_base, module_size, aob_pattern, aob_offset, | ||
| 1112 |
1/2✓ Branch 3 → 4 taken 1 time.
✗ Branch 3 → 15 not taken.
|
1 | detour_function, original_trampoline, config); |
| 1113 |
1/2✗ Branch 5 → 6 not taken.
✓ Branch 5 → 9 taken 1 time.
|
1 | if (result) |
| 1114 | { | ||
| 1115 | ✗ | return *result; | |
| 1116 | } | ||
| 1117 | 1 | return std::nullopt; | |
| 1118 | 1 | } | |
| 1119 | |||
| 1120 | /** | ||
| 1121 | * @brief Convenience wrapper that installs a mid-function hook by direct | ||
| 1122 | * address. | ||
| 1123 | * @details Diagnostic logging on failure is delegated to the underlying | ||
| 1124 | * create_mid_hook call. | ||
| 1125 | */ | ||
| 1126 | 1 | [[nodiscard]] inline std::optional<std::string> try_install_mid( | |
| 1127 | std::string_view name, | ||
| 1128 | uintptr_t target_address, | ||
| 1129 | safetyhook::MidHookFn detour_function, | ||
| 1130 | const HookConfig &config = HookConfig()) | ||
| 1131 | { | ||
| 1132 |
1/2✓ Branch 2 → 3 taken 1 time.
✗ Branch 2 → 15 not taken.
|
1 | auto result = HookManager::get_instance().create_mid_hook( |
| 1133 |
1/2✓ Branch 3 → 4 taken 1 time.
✗ Branch 3 → 15 not taken.
|
1 | name, target_address, detour_function, config); |
| 1134 |
1/2✗ Branch 5 → 6 not taken.
✓ Branch 5 → 9 taken 1 time.
|
1 | if (result) |
| 1135 | { | ||
| 1136 | ✗ | return *result; | |
| 1137 | } | ||
| 1138 | 1 | return std::nullopt; | |
| 1139 | 1 | } | |
| 1140 | |||
| 1141 | /** | ||
| 1142 | * @brief Convenience wrapper that installs a mid-function hook by AOB scan. | ||
| 1143 | * @details Diagnostic logging on failure is delegated to the underlying | ||
| 1144 | * create_mid_hook_aob call. | ||
| 1145 | */ | ||
| 1146 | 1 | [[nodiscard]] inline std::optional<std::string> try_install_mid_aob( | |
| 1147 | std::string_view name, | ||
| 1148 | uintptr_t module_base, | ||
| 1149 | size_t module_size, | ||
| 1150 | std::string_view aob_pattern, | ||
| 1151 | std::ptrdiff_t aob_offset, | ||
| 1152 | safetyhook::MidHookFn detour_function, | ||
| 1153 | const HookConfig &config = HookConfig()) | ||
| 1154 | { | ||
| 1155 |
1/2✓ Branch 2 → 3 taken 1 time.
✗ Branch 2 → 15 not taken.
|
1 | auto result = HookManager::get_instance().create_mid_hook_aob( |
| 1156 | name, module_base, module_size, aob_pattern, aob_offset, | ||
| 1157 |
1/2✓ Branch 3 → 4 taken 1 time.
✗ Branch 3 → 15 not taken.
|
1 | detour_function, config); |
| 1158 |
1/2✗ Branch 5 → 6 not taken.
✓ Branch 5 → 9 taken 1 time.
|
1 | if (result) |
| 1159 | { | ||
| 1160 | ✗ | return *result; | |
| 1161 | } | ||
| 1162 | 1 | return std::nullopt; | |
| 1163 | 1 | } | |
| 1164 | } // namespace DetourModKit | ||
| 1165 | |||
| 1166 | #endif // DETOURMODKIT_HOOK_MANAGER_HPP | ||
| 1167 |