GCC Code Coverage Report


Directory: ./
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 77.4% 468 / 0 / 605
Functions: 100.0% 73 / 0 / 73
Branches: 39.7% 320 / 0 / 807

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/6
std::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/6
std::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/6
std::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/6
std::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/6
std::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/6
std::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/6
std::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/10
bool 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/10
bool 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/10
bool 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/10
bool 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/10
bool 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/10
bool 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/10
bool 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/16
std::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/16
std::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/16
std::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/48
std::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/16
std::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/6
bool 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/6
bool 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/6
bool 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/18
bool 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/6
bool 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/6
std::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/6
std::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/6
std::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/6
std::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/18
std::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/6
std::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/14
std::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/14
std::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/14
std::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/42
std::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/14
std::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/4
bool 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/4
bool 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/4
bool 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/12
bool 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/4
bool 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/6
std::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/6
std::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/6
std::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/6
std::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/18
std::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/6
std::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