src/hook_manager.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #include "DetourModKit/hook_manager.hpp" | ||
| 2 | #include "DetourModKit/format.hpp" | ||
| 3 | #include "DetourModKit/memory.hpp" | ||
| 4 | #include "platform.hpp" | ||
| 5 | #include "x86_decode.hpp" | ||
| 6 | |||
| 7 | #include <windows.h> | ||
| 8 | |||
| 9 | #include <algorithm> | ||
| 10 | #include <cstddef> | ||
| 11 | #include <cstdint> | ||
| 12 | #include <format> | ||
| 13 | #include <memory> | ||
| 14 | #include <new> | ||
| 15 | #include <optional> | ||
| 16 | #include <vector> | ||
| 17 | |||
| 18 | using namespace DetourModKit; | ||
| 19 | using namespace DetourModKit::Scanner; | ||
| 20 | |||
| 21 | namespace | ||
| 22 | { | ||
| 23 | enum class PrehookState | ||
| 24 | { | ||
| 25 | NotHooked, | ||
| 26 | HookedBySameModule, | ||
| 27 | HookedByOtherModule | ||
| 28 | }; | ||
| 29 | |||
| 30 | struct PrehookDetection | ||
| 31 | { | ||
| 32 | PrehookState state{PrehookState::NotHooked}; | ||
| 33 | std::uintptr_t jmp_destination{0}; | ||
| 34 | }; | ||
| 35 | |||
| 36 | 75 | std::optional<std::uintptr_t> decode_prehook_destination(std::uintptr_t target_address) noexcept | |
| 37 | { | ||
| 38 |
1/2✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 75 times.
|
75 | if (!Memory::is_readable(reinterpret_cast<const void *>(target_address), 2)) |
| 39 | { | ||
| 40 | ✗ | return std::nullopt; | |
| 41 | } | ||
| 42 | 75 | const auto *bytes = reinterpret_cast<const std::uint8_t *>(target_address); | |
| 43 | |||
| 44 |
2/2✓ Branch 5 → 6 taken 2 times.
✓ Branch 5 → 7 taken 73 times.
|
75 | if (bytes[0] == 0xE9) |
| 45 | { | ||
| 46 | 2 | return detail::decode_e9_rel32(target_address); | |
| 47 | } | ||
| 48 | |||
| 49 |
1/2✗ Branch 7 → 8 not taken.
✓ Branch 7 → 9 taken 73 times.
|
73 | if (bytes[0] == 0xEB) |
| 50 | { | ||
| 51 | ✗ | return detail::decode_eb_rel8(target_address); | |
| 52 | } | ||
| 53 | |||
| 54 |
1/4✗ Branch 9 → 10 not taken.
✓ Branch 9 → 12 taken 73 times.
✗ Branch 10 → 11 not taken.
✗ Branch 10 → 12 not taken.
|
73 | if (bytes[0] == 0xFF && bytes[1] == 0x25) |
| 55 | { | ||
| 56 | ✗ | return detail::decode_ff25_indirect(target_address); | |
| 57 | } | ||
| 58 | |||
| 59 | 73 | return std::nullopt; | |
| 60 | } | ||
| 61 | |||
| 62 | 75 | PrehookDetection detect_existing_inline_hook(std::uintptr_t target_address) noexcept | |
| 63 | { | ||
| 64 | 75 | PrehookDetection result; | |
| 65 |
1/2✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 75 times.
|
75 | if (target_address == 0) |
| 66 | { | ||
| 67 | ✗ | return result; | |
| 68 | } | ||
| 69 | |||
| 70 | 75 | const auto destination_opt = decode_prehook_destination(target_address); | |
| 71 |
2/2✓ Branch 6 → 7 taken 73 times.
✓ Branch 6 → 8 taken 2 times.
|
75 | if (!destination_opt) |
| 72 | { | ||
| 73 | 73 | return result; | |
| 74 | } | ||
| 75 | 2 | const auto destination = *destination_opt; | |
| 76 | 2 | result.jmp_destination = destination; | |
| 77 | |||
| 78 | 2 | HMODULE target_module = nullptr; | |
| 79 | 2 | HMODULE dest_module = nullptr; | |
| 80 |
1/2✗ Branch 10 → 11 not taken.
✓ Branch 10 → 12 taken 2 times.
|
2 | if (!GetModuleHandleExW( |
| 81 | GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, | ||
| 82 | reinterpret_cast<LPCWSTR>(target_address), &target_module)) | ||
| 83 | { | ||
| 84 | ✗ | target_module = nullptr; | |
| 85 | } | ||
| 86 |
1/2✓ Branch 13 → 14 taken 2 times.
✗ Branch 13 → 15 not taken.
|
2 | if (!GetModuleHandleExW( |
| 87 | GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, | ||
| 88 | reinterpret_cast<LPCWSTR>(destination), &dest_module)) | ||
| 89 | { | ||
| 90 | 2 | dest_module = nullptr; | |
| 91 | } | ||
| 92 | |||
| 93 |
1/4✗ Branch 15 → 16 not taken.
✓ Branch 15 → 18 taken 2 times.
✗ Branch 16 → 17 not taken.
✗ Branch 16 → 18 not taken.
|
2 | if (dest_module != nullptr && target_module == dest_module) |
| 94 | { | ||
| 95 | ✗ | result.state = PrehookState::HookedBySameModule; | |
| 96 | } | ||
| 97 | else | ||
| 98 | { | ||
| 99 | 2 | result.state = PrehookState::HookedByOtherModule; | |
| 100 | } | ||
| 101 | 2 | return result; | |
| 102 | } | ||
| 103 | } // anonymous namespace | ||
| 104 | |||
| 105 | 240 | HookManager &HookManager::get_instance() | |
| 106 | { | ||
| 107 |
5/10✓ Branch 2 → 3 taken 1 time.
✓ Branch 2 → 9 taken 239 times.
✓ Branch 4 → 5 taken 1 time.
✗ Branch 4 → 9 not taken.
✓ Branch 5 → 6 taken 1 time.
✗ Branch 5 → 11 not taken.
✓ Branch 6 → 7 taken 1 time.
✗ Branch 6 → 11 not taken.
✗ Branch 11 → 12 not taken.
✗ Branch 11 → 13 not taken.
|
240 | static HookManager instance; |
| 108 | 240 | return instance; | |
| 109 | } | ||
| 110 | |||
| 111 | 1 | HookManager::HookManager(Logger &logger) | |
| 112 | 1 | : m_logger(logger) | |
| 113 | { | ||
| 114 |
1/2✓ Branch 8 → 9 taken 1 time.
✗ Branch 8 → 19 not taken.
|
1 | m_allocator = safetyhook::Allocator::global(); |
| 115 |
1/2✗ Branch 12 → 13 not taken.
✓ Branch 12 → 15 taken 1 time.
|
1 | if (!m_allocator) |
| 116 | { | ||
| 117 | ✗ | m_logger.error("HookManager: Failed to get SafetyHook global allocator! Hook creation will fail."); | |
| 118 | } | ||
| 119 | else | ||
| 120 | { | ||
| 121 |
1/2✓ Branch 15 → 16 taken 1 time.
✗ Branch 15 → 21 not taken.
|
1 | m_logger.debug("HookManager: SafetyHook global allocator obtained."); |
| 122 | } | ||
| 123 |
1/2✓ Branch 17 → 18 taken 1 time.
✗ Branch 17 → 22 not taken.
|
1 | m_logger.debug("HookManager: Initialized."); |
| 124 | 1 | } | |
| 125 | |||
| 126 | 2 | HookManager::~HookManager() noexcept | |
| 127 | { | ||
| 128 |
1/2✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 1 time.
|
1 | if (m_shutdown_called.load(std::memory_order_acquire)) |
| 129 | { | ||
| 130 | ✗ | return; | |
| 131 | } | ||
| 132 | |||
| 133 | // Fallback teardown path. Reached only when the process did not call | ||
| 134 | // DMK_Shutdown() / HookManager::shutdown() before static destruction | ||
| 135 | // (abnormal exit, FreeLibrary race, host crash). | ||
| 136 | // | ||
| 137 | // Ordering (matches the shutdown() contract so readers see one story): | ||
| 138 | // 1. Flip m_shutdown_called under m_mutator_gate (exclusive) to | ||
| 139 | // block new mutators and serialize with a late shutdown() call. | ||
| 140 | // 2. Disable all hooks under shared m_hooks_mutex so that in-flight | ||
| 141 | // trampoline callers can drain from SafetyHook::disable(). | ||
| 142 | // 3. Acquire exclusive m_hooks_mutex to wait out any shared_lock | ||
| 143 | // holder still inside a with_* callback. Only then clear the | ||
| 144 | // maps -- destroying the Hook objects would UAF a live reader. | ||
| 145 | // | ||
| 146 | // Loader-lock fallback: if the destructor is fired with the OS loader | ||
| 147 | // lock held (e.g. during abnormal DLL unload), acquiring m_mutator_gate | ||
| 148 | // or blocking readers can deadlock against another thread waiting on a | ||
| 149 | // loader callback. Leak the maps in that case; the pinned module keeps | ||
| 150 | // the hooks' code pages live so SafetyHook trampolines do not dangle. | ||
| 151 | // Mirrors the pattern used in Logger::shutdown_internal(). | ||
| 152 |
1/2✗ Branch 6 → 7 not taken.
✓ Branch 6 → 30 taken 1 time.
|
1 | if (detail::is_loader_lock_held()) |
| 153 | { | ||
| 154 | ✗ | detail::pin_current_module(); | |
| 155 | // Intentional leak under loader lock: draining readers or destroying | ||
| 156 | // Hook / VmtHookEntry values here can deadlock against another thread | ||
| 157 | // waiting on a loader callback. The pinned module keeps the SafetyHook | ||
| 158 | // trampoline pages live for the remainder of the process, so the leaked | ||
| 159 | // maps and their contents stay valid storage even though no one will | ||
| 160 | // ever observe them again. HookManager is a singleton so this branch | ||
| 161 | // runs at most once per process. | ||
| 162 | // | ||
| 163 | // Heap-allocate each map directly rather than nesting them inside an | ||
| 164 | // outer container. A container of containers would force the standard | ||
| 165 | // library to instantiate a copy-construction fallback for the element | ||
| 166 | // type whenever the element's move constructor is not unconditionally | ||
| 167 | // noexcept, and that copy path would try to copy a move-only member | ||
| 168 | // (VmtHookEntry owns a safetyhook::VmtHook). | ||
| 169 | // | ||
| 170 | // Default-construct each map on the heap, then swap content in. | ||
| 171 | // std::unordered_map's move constructor is not guaranteed noexcept on | ||
| 172 | // every standard library implementation (some mark it noexcept(false) | ||
| 173 | // so allocator-propagation fallbacks can allocate); invoking it from a | ||
| 174 | // noexcept destructor would risk std::terminate on an unexpected | ||
| 175 | // allocator fallback. With a default std::allocator (stateless, | ||
| 176 | // is_always_equal) and noexcept hasher / comparator, member swap() is | ||
| 177 | // specified noexcept and performs an O(1) pointer swap that allocates | ||
| 178 | // nothing, so it is safe to call from a noexcept context. On | ||
| 179 | // allocation failure the new (std::nothrow) expression returns | ||
| 180 | // nullptr, the source maps keep their contents, and control falls | ||
| 181 | // through to the normal ~HookManager member destruction epilogue. | ||
| 182 | // That epilogue is best-effort under loader lock, but the pinned | ||
| 183 | // module still keeps trampoline code pages live so straggler | ||
| 184 | // trampoline calls land on valid memory. | ||
| 185 | ✗ | auto *leaked_hooks = new (std::nothrow) detail::HookMap(); | |
| 186 | ✗ | auto *leaked_vmt_hooks = new (std::nothrow) detail::VmtHookMap(); | |
| 187 | ✗ | if (leaked_hooks != nullptr) | |
| 188 | { | ||
| 189 | ✗ | leaked_hooks->swap(m_hooks); | |
| 190 | } | ||
| 191 | ✗ | if (leaked_vmt_hooks != nullptr) | |
| 192 | { | ||
| 193 | ✗ | leaked_vmt_hooks->swap(m_vmt_hooks); | |
| 194 | } | ||
| 195 | ✗ | m_shutdown_called.store(true, std::memory_order_release); | |
| 196 | ✗ | return; | |
| 197 | } | ||
| 198 | |||
| 199 | 1 | std::unique_lock<std::shared_mutex> mutator_gate(m_mutator_gate); | |
| 200 | 1 | m_shutdown_called.store(true, std::memory_order_release); | |
| 201 | |||
| 202 | { | ||
| 203 | 1 | std::shared_lock<std::shared_mutex> shared(m_hooks_mutex); | |
| 204 |
1/2✗ Branch 42 → 35 not taken.
✓ Branch 42 → 43 taken 1 time.
|
1 | for (auto &[name, hook] : m_hooks) |
| 205 | { | ||
| 206 | ✗ | (void)hook->disable(); | |
| 207 | } | ||
| 208 | 1 | } | |
| 209 | |||
| 210 | 1 | std::unique_lock<std::shared_mutex> lock(m_hooks_mutex); | |
| 211 | 1 | m_vmt_hooks.clear(); | |
| 212 | 1 | m_hooks.clear(); | |
| 213 |
3/6✓ Branch 52 → 53 taken 1 time.
✗ Branch 52 → 54 not taken.
✓ Branch 56 → 57 taken 1 time.
✗ Branch 56 → 58 not taken.
✓ Branch 60 → 61 taken 1 time.
✗ Branch 60 → 62 not taken.
|
1 | } |
| 214 | |||
| 215 | 44 | void HookManager::shutdown() | |
| 216 | { | ||
| 217 | // Serialize with remove_all_hooks() via compare_exchange_strong. | ||
| 218 | // Only one teardown owner proceeds. | ||
| 219 | 44 | bool expected = false; | |
| 220 |
1/2✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 44 times.
|
44 | if (!m_shutdown_called.compare_exchange_strong(expected, true, std::memory_order_acq_rel)) |
| 221 | ✗ | return; | |
| 222 | |||
| 223 | // Block all mutators (create_*_hook, enable, disable, remove) before | ||
| 224 | // entering phase 1. They hold shared on m_mutator_gate, so acquiring | ||
| 225 | // exclusive here waits for active mutators and blocks new ones. | ||
| 226 |
1/2✓ Branch 5 → 6 taken 44 times.
✗ Branch 5 → 34 not taken.
|
44 | std::unique_lock<std::shared_mutex> mutator_gate(m_mutator_gate); |
| 227 | |||
| 228 | // Two-phase shutdown: disable hooks under a shared lock first so that | ||
| 229 | // hooked threads blocked on m_hooks_mutex can drain from SafetyHook's | ||
| 230 | // disable() without deadlock, then clear the maps under exclusive lock. | ||
| 231 | { | ||
| 232 |
1/2✓ Branch 6 → 7 taken 44 times.
✗ Branch 6 → 30 not taken.
|
44 | std::shared_lock<std::shared_mutex> shared(m_hooks_mutex); |
| 233 |
2/2✓ Branch 16 → 9 taken 10 times.
✓ Branch 16 → 17 taken 44 times.
|
54 | for (auto &[name, hook] : m_hooks) |
| 234 | { | ||
| 235 |
1/2✓ Branch 13 → 14 taken 10 times.
✗ Branch 13 → 26 not taken.
|
10 | (void)hook->disable(); |
| 236 | } | ||
| 237 | 44 | } | |
| 238 | { | ||
| 239 |
1/2✓ Branch 18 → 19 taken 44 times.
✗ Branch 18 → 31 not taken.
|
44 | std::unique_lock<std::shared_mutex> lock(m_hooks_mutex); |
| 240 | 44 | m_vmt_hooks.clear(); | |
| 241 | 44 | m_hooks.clear(); | |
| 242 | |||
| 243 | // Reset under the lock so concurrent create_*_hook calls cannot | ||
| 244 | // observe the flag as true (rejected) and then immediately see it | ||
| 245 | // as false (accepted) before the map is fully cleared. | ||
| 246 | // | ||
| 247 | // This intentionally allows reuse after shutdown (hot-reload). | ||
| 248 | // The exclusive lock on m_mutator_gate serializes the entire | ||
| 249 | // clear-and-reset sequence, so there is no window where a | ||
| 250 | // concurrent create_*_hook can slip through against a half-cleared | ||
| 251 | // map. The mutator_gate exclusive lock is released here, allowing | ||
| 252 | // new mutators to proceed with a fresh m_shutdown_called=false. | ||
| 253 | 44 | m_shutdown_called.store(false, std::memory_order_release); | |
| 254 | 44 | } | |
| 255 | 44 | } | |
| 256 | |||
| 257 | ✗ | std::string HookManager::error_to_string(const safetyhook::InlineHook::Error &err) const | |
| 258 | { | ||
| 259 | ✗ | const int type_int = static_cast<int>(err.type); | |
| 260 | ✗ | const auto ip_str = DetourModKit::Format::format_address(reinterpret_cast<uintptr_t>(err.ip)); | |
| 261 | |||
| 262 | ✗ | switch (err.type) | |
| 263 | { | ||
| 264 | ✗ | case safetyhook::InlineHook::Error::BAD_ALLOCATION: | |
| 265 | return std::format("SafetyHook InlineHook Error (Type: {}): Bad allocation (Allocator error: {})", | ||
| 266 | ✗ | type_int, static_cast<int>(err.allocator_error)); | |
| 267 | ✗ | case safetyhook::InlineHook::Error::FAILED_TO_DECODE_INSTRUCTION: | |
| 268 | return std::format("SafetyHook InlineHook Error (Type: {}): Failed to decode instruction at address {}", | ||
| 269 | ✗ | type_int, ip_str); | |
| 270 | ✗ | case safetyhook::InlineHook::Error::SHORT_JUMP_IN_TRAMPOLINE: | |
| 271 | return std::format("SafetyHook InlineHook Error (Type: {}): Short jump found in trampoline at address {}", | ||
| 272 | ✗ | type_int, ip_str); | |
| 273 | ✗ | case safetyhook::InlineHook::Error::IP_RELATIVE_INSTRUCTION_OUT_OF_RANGE: | |
| 274 | return std::format("SafetyHook InlineHook Error (Type: {}): IP-relative instruction out of range at address {}", | ||
| 275 | ✗ | type_int, ip_str); | |
| 276 | ✗ | case safetyhook::InlineHook::Error::UNSUPPORTED_INSTRUCTION_IN_TRAMPOLINE: | |
| 277 | return std::format("SafetyHook InlineHook Error (Type: {}): Unsupported instruction in trampoline at address {}", | ||
| 278 | ✗ | type_int, ip_str); | |
| 279 | ✗ | case safetyhook::InlineHook::Error::FAILED_TO_UNPROTECT: | |
| 280 | return std::format("SafetyHook InlineHook Error (Type: {}): Failed to unprotect memory at address {}", | ||
| 281 | ✗ | type_int, ip_str); | |
| 282 | ✗ | case safetyhook::InlineHook::Error::NOT_ENOUGH_SPACE: | |
| 283 | return std::format("SafetyHook InlineHook Error (Type: {}): Not enough space for the hook (prologue too short) at address {}", | ||
| 284 | ✗ | type_int, ip_str); | |
| 285 | ✗ | default: | |
| 286 | ✗ | return std::format("SafetyHook InlineHook Error (Type: {}): Unknown error type", type_int); | |
| 287 | } | ||
| 288 | ✗ | } | |
| 289 | |||
| 290 | ✗ | std::string HookManager::error_to_string(const safetyhook::MidHook::Error &err) const | |
| 291 | { | ||
| 292 | ✗ | const int type_int = static_cast<int>(err.type); | |
| 293 | |||
| 294 | ✗ | switch (err.type) | |
| 295 | { | ||
| 296 | ✗ | case safetyhook::MidHook::Error::BAD_ALLOCATION: | |
| 297 | return std::format("SafetyHook MidHook Error (Type: {}): Bad allocation (Allocator error: {})", | ||
| 298 | ✗ | type_int, static_cast<int>(err.allocator_error)); | |
| 299 | ✗ | case safetyhook::MidHook::Error::BAD_INLINE_HOOK: | |
| 300 | return std::format("SafetyHook MidHook Error (Type: {}): Bad underlying inline hook. Details: {}", | ||
| 301 | ✗ | type_int, error_to_string(err.inline_hook_error)); | |
| 302 | ✗ | default: | |
| 303 | ✗ | return std::format("SafetyHook MidHook Error (Type: {}): Unknown error type", type_int); | |
| 304 | } | ||
| 305 | } | ||
| 306 | |||
| 307 | 83 | std::expected<std::string, HookError> HookManager::create_inline_hook( | |
| 308 | std::string_view name, | ||
| 309 | uintptr_t target_address, | ||
| 310 | void *detour_function, | ||
| 311 | void **original_trampoline, | ||
| 312 | const HookConfig &config) | ||
| 313 | { | ||
| 314 | // Non-locking fast-fail: avoid acquiring mutator_gate when shutdown | ||
| 315 | // is already in progress. | ||
| 316 |
1/2✗ Branch 3 → 4 not taken.
✓ Branch 3 → 10 taken 83 times.
|
83 | if (m_shutdown_called.load(std::memory_order_acquire)) |
| 317 | { | ||
| 318 | ✗ | if (original_trampoline) | |
| 319 | ✗ | *original_trampoline = nullptr; | |
| 320 | ✗ | m_logger.error("HookManager: Shutdown in progress. Cannot create inline hook '{}'.", name); | |
| 321 | ✗ | return std::unexpected(HookError::ShutdownInProgress); | |
| 322 | } | ||
| 323 | |||
| 324 | 83 | auto [result, deferred_logs] = [&]() -> std::pair<std::expected<std::string, HookError>, std::vector<DeferredLogEntry>> | |
| 325 | { | ||
| 326 |
1/2✓ Branch 2 → 3 taken 83 times.
✗ Branch 2 → 515 not taken.
|
83 | std::shared_lock<std::shared_mutex> mutator_gate(m_mutator_gate); |
| 327 |
1/2✓ Branch 3 → 4 taken 83 times.
✗ Branch 3 → 513 not taken.
|
83 | std::unique_lock<std::shared_mutex> lock(m_hooks_mutex); |
| 328 | |||
| 329 | // Re-check after acquiring locks (double-checked pattern). | ||
| 330 |
1/2✗ Branch 5 → 6 not taken.
✓ Branch 5 → 20 taken 83 times.
|
83 | if (m_shutdown_called.load(std::memory_order_acquire)) |
| 331 | { | ||
| 332 | ✗ | return {std::unexpected(HookError::ShutdownInProgress), | |
| 333 | ✗ | {{std::format("HookManager: Shutdown in progress. Cannot create inline hook '{}'.", name), LogLevel::Error}}}; | |
| 334 | } | ||
| 335 |
1/2✗ Branch 21 → 22 not taken.
✓ Branch 21 → 36 taken 83 times.
|
83 | if (!m_allocator) |
| 336 | { | ||
| 337 | ✗ | return {std::unexpected(HookError::AllocatorNotAvailable), | |
| 338 | ✗ | {{std::format("HookManager: Allocator not available. Cannot create inline hook '{}'.", name), LogLevel::Error}}}; | |
| 339 | } | ||
| 340 |
2/2✓ Branch 36 → 37 taken 4 times.
✓ Branch 36 → 51 taken 79 times.
|
83 | if (target_address == 0) |
| 341 | { | ||
| 342 | 4 | return {std::unexpected(HookError::InvalidTargetAddress), | |
| 343 |
3/6✓ Branch 41 → 42 taken 4 times.
✗ Branch 41 → 252 not taken.
✓ Branch 46 → 47 taken 4 times.
✓ Branch 46 → 48 taken 4 times.
✗ Branch 256 → 257 not taken.
✗ Branch 256 → 258 not taken.
|
12 | {{std::format("HookManager: Target address is NULL for inline hook '{}'.", name), LogLevel::Error}}}; |
| 344 | } | ||
| 345 |
2/2✓ Branch 51 → 52 taken 2 times.
✓ Branch 51 → 66 taken 77 times.
|
79 | if (detour_function == nullptr) |
| 346 | { | ||
| 347 | 2 | return {std::unexpected(HookError::InvalidDetourFunction), | |
| 348 |
3/6✓ Branch 56 → 57 taken 2 times.
✗ Branch 56 → 272 not taken.
✓ Branch 61 → 62 taken 2 times.
✓ Branch 61 → 63 taken 2 times.
✗ Branch 276 → 277 not taken.
✗ Branch 276 → 278 not taken.
|
6 | {{std::format("HookManager: Detour function is NULL for inline hook '{}'.", name), LogLevel::Error}}}; |
| 349 | } | ||
| 350 |
2/2✓ Branch 66 → 67 taken 1 time.
✓ Branch 66 → 81 taken 76 times.
|
77 | if (original_trampoline == nullptr) |
| 351 | { | ||
| 352 | 1 | return {std::unexpected(HookError::InvalidTrampolinePointer), | |
| 353 |
3/6✓ Branch 71 → 72 taken 1 time.
✗ Branch 71 → 292 not taken.
✓ Branch 76 → 77 taken 1 time.
✓ Branch 76 → 78 taken 1 time.
✗ Branch 296 → 297 not taken.
✗ Branch 296 → 298 not taken.
|
3 | {{std::format("HookManager: Original trampoline pointer (output) is NULL for inline hook '{}'.", name), LogLevel::Error}}}; |
| 354 | } | ||
| 355 | 76 | *original_trampoline = nullptr; | |
| 356 | |||
| 357 |
3/4✓ Branch 81 → 82 taken 76 times.
✗ Branch 81 → 511 not taken.
✓ Branch 82 → 83 taken 1 time.
✓ Branch 82 → 97 taken 75 times.
|
76 | if (hook_id_exists_locked(name)) |
| 358 | { | ||
| 359 | 1 | return {std::unexpected(HookError::HookAlreadyExists), | |
| 360 |
3/6✓ Branch 87 → 88 taken 1 time.
✗ Branch 87 → 312 not taken.
✓ Branch 92 → 93 taken 1 time.
✓ Branch 92 → 94 taken 1 time.
✗ Branch 316 → 317 not taken.
✗ Branch 316 → 318 not taken.
|
3 | {{std::format("HookManager: A hook with the name '{}' already exists.", name), LogLevel::Error}}}; |
| 361 | } | ||
| 362 | |||
| 363 | 75 | const auto prehook = detect_existing_inline_hook(target_address); | |
| 364 |
2/2✓ Branch 98 → 99 taken 2 times.
✓ Branch 98 → 118 taken 73 times.
|
75 | if (prehook.state == PrehookState::HookedByOtherModule && |
| 365 |
2/2✓ Branch 99 → 100 taken 1 time.
✓ Branch 99 → 118 taken 1 time.
|
2 | config.fail_if_already_hooked) |
| 366 | { | ||
| 367 | 1 | return {std::unexpected(HookError::TargetAlreadyHookedInProcess), | |
| 368 | {{std::format("HookManager: Target {} for inline hook '{}' is already inline-hooked by another module (JMP -> {}). Aborting under strict mode.", | ||
| 369 | ✗ | DetourModKit::Format::format_address(target_address), name, | |
| 370 | 1 | DetourModKit::Format::format_address(prehook.jmp_destination)), | |
| 371 |
3/6✓ Branch 106 → 107 taken 1 time.
✗ Branch 106 → 332 not taken.
✓ Branch 111 → 112 taken 1 time.
✓ Branch 111 → 113 taken 1 time.
✗ Branch 336 → 337 not taken.
✗ Branch 336 → 338 not taken.
|
4 | LogLevel::Error}}}; |
| 372 | } | ||
| 373 | |||
| 374 | try | ||
| 375 | { | ||
| 376 | 74 | auto sh_flags = config.auto_enable | |
| 377 |
2/2✓ Branch 118 → 119 taken 72 times.
✓ Branch 118 → 120 taken 2 times.
|
74 | ? safetyhook::InlineHook::Default |
| 378 | : safetyhook::InlineHook::StartDisabled; | ||
| 379 | |||
| 380 | auto hook_creation_result = safetyhook::InlineHook::create( | ||
| 381 | 74 | m_allocator, | |
| 382 | 74 | reinterpret_cast<void *>(target_address), | |
| 383 | detour_function, | ||
| 384 |
1/2✓ Branch 121 → 122 taken 74 times.
✗ Branch 121 → 433 not taken.
|
74 | sh_flags); |
| 385 | |||
| 386 |
1/2✗ Branch 123 → 124 not taken.
✓ Branch 123 → 143 taken 74 times.
|
74 | if (!hook_creation_result) |
| 387 | { | ||
| 388 | ✗ | return {std::unexpected(HookError::SafetyHookError), | |
| 389 | {{std::format("HookManager: Failed to create SafetyHook::InlineHook for '{}' at {}. Error: {}", | ||
| 390 | ✗ | name, DetourModKit::Format::format_address(target_address), error_to_string(hook_creation_result.error())), | |
| 391 | ✗ | LogLevel::Error}}}; | |
| 392 | } | ||
| 393 | |||
| 394 |
1/2✓ Branch 143 → 144 taken 74 times.
✗ Branch 143 → 431 not taken.
|
148 | auto sh_inline_hook = std::move(hook_creation_result.value()); |
| 395 | 74 | void *trampoline = sh_inline_hook.original<void *>(); | |
| 396 | |||
| 397 |
2/2✓ Branch 149 → 150 taken 72 times.
✓ Branch 149 → 151 taken 2 times.
|
74 | HookStatus initial_status = sh_inline_hook.enabled() ? HookStatus::Active : HookStatus::Disabled; |
| 398 | |||
| 399 | // Pre-build log entries before committing to m_hooks so that | ||
| 400 | // allocation failures in std::format cannot leave a ghost hook. | ||
| 401 |
3/4✓ Branch 154 → 155 taken 72 times.
✓ Branch 154 → 156 taken 2 times.
✓ Branch 157 → 158 taken 74 times.
✗ Branch 157 → 384 not taken.
|
74 | std::string status_message = (initial_status == HookStatus::Active) ? "and enabled" : "(disabled)"; |
| 402 | 74 | std::vector<DeferredLogEntry> logs; | |
| 403 |
1/2✓ Branch 159 → 160 taken 74 times.
✗ Branch 159 → 425 not taken.
|
74 | logs.reserve(3); |
| 404 | 74 | logs.push_back({std::format("HookManager: Successfully created {} inline hook '{}' targeting {}.", | |
| 405 | ✗ | status_message, name, DetourModKit::Format::format_address(target_address)), | |
| 406 | LogLevel::Info}); | ||
| 407 | |||
| 408 |
2/2✓ Branch 167 → 168 taken 1 time.
✓ Branch 167 → 178 taken 73 times.
|
74 | if (prehook.state == PrehookState::HookedByOtherModule) |
| 409 | { | ||
| 410 | 1 | logs.push_back({std::format("HookManager: Target {} for inline hook '{}' was already inline-hooked by another module (JMP -> {}); SafetyHook layered on top.", | |
| 411 | ✗ | DetourModKit::Format::format_address(target_address), name, | |
| 412 | 1 | DetourModKit::Format::format_address(prehook.jmp_destination)), | |
| 413 | LogLevel::Warning}); | ||
| 414 | } | ||
| 415 | |||
| 416 |
3/4✓ Branch 178 → 179 taken 2 times.
✓ Branch 178 → 186 taken 72 times.
✗ Branch 179 → 180 not taken.
✓ Branch 179 → 186 taken 2 times.
|
74 | if (initial_status == HookStatus::Disabled && config.auto_enable) |
| 417 | { | ||
| 418 | ✗ | logs.push_back({std::format("HookManager: Inline hook '{}' was configured for auto-enable but is currently disabled post-creation.", name), | |
| 419 | LogLevel::Warning}); | ||
| 420 | } | ||
| 421 | |||
| 422 |
1/2✓ Branch 188 → 189 taken 74 times.
✗ Branch 188 → 417 not taken.
|
222 | std::string name_str{name}; |
| 423 |
1/2✓ Branch 192 → 193 taken 74 times.
✗ Branch 192 → 423 not taken.
|
74 | auto managed_hook = std::make_unique<InlineHook>(name_str, target_address, std::move(sh_inline_hook), initial_status); |
| 424 |
1/2✓ Branch 195 → 196 taken 74 times.
✗ Branch 195 → 420 not taken.
|
148 | m_hooks.emplace(name_str, std::move(managed_hook)); |
| 425 | 74 | *original_trampoline = trampoline; | |
| 426 | |||
| 427 | 148 | return {std::move(name_str), std::move(logs)}; | |
| 428 | 74 | } | |
| 429 | ✗ | catch (const std::exception &e) | |
| 430 | { | ||
| 431 | ✗ | return {std::unexpected(HookError::UnknownError), | |
| 432 | ✗ | {{std::format("HookManager: An std::exception occurred during inline hook creation for '{}': {}", name, e.what()), | |
| 433 | ✗ | LogLevel::Error}}}; | |
| 434 | ✗ | } | |
| 435 | ✗ | catch (...) | |
| 436 | { | ||
| 437 | ✗ | return {std::unexpected(HookError::UnknownError), | |
| 438 | {{std::format("HookManager: An unknown exception occurred during inline hook creation for '{}'.", name), | ||
| 439 | ✗ | LogLevel::Error}}}; | |
| 440 | ✗ | } | |
| 441 |
22/138✗ Branch 7 → 8 not taken.
✗ Branch 7 → 222 not taken.
✓ Branch 10 → 11 taken 83 times.
✗ Branch 10 → 39 not taken.
✗ Branch 17 → 18 not taken.
✗ Branch 17 → 19 not taken.
✗ Branch 23 → 24 not taken.
✗ Branch 23 → 242 not taken.
✗ Branch 33 → 34 not taken.
✗ Branch 33 → 35 not taken.
✓ Branch 38 → 39 taken 4 times.
✗ Branch 38 → 262 not taken.
✗ Branch 48 → 49 not taken.
✓ Branch 48 → 50 taken 4 times.
✓ Branch 53 → 54 taken 2 times.
✗ Branch 53 → 282 not taken.
✗ Branch 63 → 64 not taken.
✓ Branch 63 → 65 taken 2 times.
✓ Branch 68 → 69 taken 1 time.
✗ Branch 68 → 302 not taken.
✗ Branch 78 → 79 not taken.
✓ Branch 78 → 80 taken 1 time.
✓ Branch 84 → 85 taken 1 time.
✗ Branch 84 → 322 not taken.
✗ Branch 94 → 95 not taken.
✓ Branch 94 → 96 taken 1 time.
✓ Branch 101 → 102 taken 1 time.
✗ Branch 101 → 348 not taken.
✓ Branch 102 → 103 taken 1 time.
✗ Branch 102 → 345 not taken.
✓ Branch 103 → 104 taken 1 time.
✗ Branch 103 → 342 not taken.
✗ Branch 113 → 114 not taken.
✓ Branch 113 → 115 taken 1 time.
✗ Branch 128 → 129 not taken.
✗ Branch 128 → 368 not taken.
✗ Branch 138 → 139 not taken.
✗ Branch 138 → 140 not taken.
✓ Branch 160 → 161 taken 74 times.
✗ Branch 160 → 395 not taken.
✓ Branch 161 → 162 taken 74 times.
✗ Branch 161 → 392 not taken.
✓ Branch 162 → 163 taken 74 times.
✗ Branch 162 → 387 not taken.
✗ Branch 164 → 165 not taken.
✓ Branch 164 → 166 taken 74 times.
✓ Branch 168 → 169 taken 1 time.
✗ Branch 168 → 408 not taken.
✓ Branch 169 → 170 taken 1 time.
✗ Branch 169 → 405 not taken.
✓ Branch 170 → 171 taken 1 time.
✗ Branch 170 → 402 not taken.
✓ Branch 171 → 172 taken 1 time.
✗ Branch 171 → 397 not taken.
✗ Branch 173 → 174 not taken.
✓ Branch 173 → 175 taken 1 time.
✗ Branch 180 → 181 not taken.
✗ Branch 180 → 415 not taken.
✗ Branch 181 → 182 not taken.
✗ Branch 181 → 410 not taken.
✗ Branch 183 → 184 not taken.
✗ Branch 183 → 185 not taken.
✗ Branch 219 → 220 not taken.
✗ Branch 219 → 221 not taken.
✗ Branch 223 → 224 not taken.
✗ Branch 223 → 227 not taken.
✗ Branch 225 → 226 not taken.
✗ Branch 225 → 227 not taken.
✗ Branch 239 → 240 not taken.
✗ Branch 239 → 241 not taken.
✗ Branch 243 → 244 not taken.
✗ Branch 243 → 247 not taken.
✗ Branch 245 → 246 not taken.
✗ Branch 245 → 247 not taken.
✗ Branch 259 → 260 not taken.
✗ Branch 259 → 261 not taken.
✗ Branch 263 → 264 not taken.
✗ Branch 263 → 267 not taken.
✗ Branch 265 → 266 not taken.
✗ Branch 265 → 267 not taken.
✗ Branch 279 → 280 not taken.
✗ Branch 279 → 281 not taken.
✗ Branch 283 → 284 not taken.
✗ Branch 283 → 287 not taken.
✗ Branch 285 → 286 not taken.
✗ Branch 285 → 287 not taken.
✗ Branch 299 → 300 not taken.
✗ Branch 299 → 301 not taken.
✗ Branch 303 → 304 not taken.
✗ Branch 303 → 307 not taken.
✗ Branch 305 → 306 not taken.
✗ Branch 305 → 307 not taken.
✗ Branch 319 → 320 not taken.
✗ Branch 319 → 321 not taken.
✗ Branch 323 → 324 not taken.
✗ Branch 323 → 327 not taken.
✗ Branch 325 → 326 not taken.
✗ Branch 325 → 327 not taken.
✗ Branch 339 → 340 not taken.
✗ Branch 339 → 341 not taken.
✗ Branch 349 → 350 not taken.
✗ Branch 349 → 353 not taken.
✗ Branch 351 → 352 not taken.
✗ Branch 351 → 353 not taken.
✗ Branch 365 → 366 not taken.
✗ Branch 365 → 367 not taken.
✗ Branch 375 → 376 not taken.
✗ Branch 375 → 379 not taken.
✗ Branch 377 → 378 not taken.
✗ Branch 377 → 379 not taken.
✗ Branch 389 → 390 not taken.
✗ Branch 389 → 391 not taken.
✗ Branch 399 → 400 not taken.
✗ Branch 399 → 401 not taken.
✗ Branch 412 → 413 not taken.
✗ Branch 412 → 414 not taken.
✗ Branch 438 → 439 not taken.
✗ Branch 438 → 476 not taken.
✗ Branch 448 → 449 not taken.
✗ Branch 448 → 450 not taken.
✗ Branch 453 → 454 not taken.
✗ Branch 453 → 499 not taken.
✗ Branch 463 → 464 not taken.
✗ Branch 463 → 465 not taken.
✗ Branch 465 → 208 not taken.
✗ Branch 465 → 511 not taken.
✗ Branch 473 → 474 not taken.
✗ Branch 473 → 475 not taken.
✗ Branch 478 → 479 not taken.
✗ Branch 478 → 482 not taken.
✗ Branch 480 → 481 not taken.
✗ Branch 480 → 482 not taken.
✗ Branch 496 → 497 not taken.
✗ Branch 496 → 498 not taken.
✗ Branch 500 → 501 not taken.
✗ Branch 500 → 504 not taken.
✗ Branch 502 → 503 not taken.
✗ Branch 502 → 504 not taken.
|
326 | }(); |
| 442 | |||
| 443 |
2/2✓ Branch 28 → 15 taken 84 times.
✓ Branch 28 → 29 taken 83 times.
|
250 | for (const auto &entry : deferred_logs) |
| 444 | { | ||
| 445 |
1/2✓ Branch 18 → 19 taken 84 times.
✗ Branch 18 → 40 not taken.
|
84 | m_logger.log(entry.level, entry.msg); |
| 446 | } | ||
| 447 | 83 | return result; | |
| 448 |
1/4✗ Branch 30 → 31 not taken.
✓ Branch 30 → 32 taken 83 times.
✗ Branch 41 → 42 not taken.
✗ Branch 41 → 43 not taken.
|
166 | } |
| 449 | |||
| 450 | 8 | std::expected<std::string, HookError> HookManager::create_inline_hook_aob( | |
| 451 | std::string_view name, | ||
| 452 | uintptr_t module_base, | ||
| 453 | size_t module_size, | ||
| 454 | std::string_view aob_pattern_str, | ||
| 455 | ptrdiff_t aob_offset, | ||
| 456 | void *detour_function, | ||
| 457 | void **original_trampoline, | ||
| 458 | const HookConfig &config) | ||
| 459 | { | ||
| 460 |
1/2✓ Branch 3 → 4 taken 8 times.
✗ Branch 3 → 35 not taken.
|
8 | m_logger.debug("HookManager: Attempting AOB scan for inline hook '{}' with pattern: \"{}\", offset: {}.", |
| 461 |
1/2✓ Branch 2 → 3 taken 8 times.
✗ Branch 2 → 38 not taken.
|
16 | name, aob_pattern_str, DetourModKit::Format::format_hex(aob_offset)); |
| 462 | |||
| 463 |
1/2✓ Branch 5 → 6 taken 8 times.
✗ Branch 5 → 53 not taken.
|
8 | auto pattern = parse_aob(aob_pattern_str); |
| 464 |
2/2✓ Branch 7 → 8 taken 3 times.
✓ Branch 7 → 14 taken 5 times.
|
8 | if (!pattern.has_value()) |
| 465 | { | ||
| 466 |
1/2✓ Branch 8 → 9 taken 3 times.
✗ Branch 8 → 39 not taken.
|
3 | m_logger.error("HookManager: AOB pattern parsing failed for inline hook '{}'. Pattern: \"{}\".", name, aob_pattern_str); |
| 467 |
1/2✓ Branch 9 → 10 taken 3 times.
✗ Branch 9 → 11 not taken.
|
3 | if (original_trampoline) |
| 468 | 3 | *original_trampoline = nullptr; | |
| 469 | 3 | return std::unexpected(HookError::InvalidTargetAddress); | |
| 470 | } | ||
| 471 | |||
| 472 |
2/4✓ Branch 14 → 15 taken 5 times.
✗ Branch 14 → 51 not taken.
✓ Branch 15 → 16 taken 5 times.
✗ Branch 15 → 51 not taken.
|
5 | const std::byte *found_address_start = find_pattern(reinterpret_cast<const std::byte *>(module_base), module_size, pattern.value()); |
| 473 |
2/2✓ Branch 16 → 17 taken 3 times.
✓ Branch 16 → 23 taken 2 times.
|
5 | if (!found_address_start) |
| 474 | { | ||
| 475 |
1/2✓ Branch 17 → 18 taken 3 times.
✗ Branch 17 → 40 not taken.
|
3 | m_logger.error("HookManager: AOB pattern not found for inline hook '{}'. Pattern: \"{}\".", name, aob_pattern_str); |
| 476 |
1/2✓ Branch 18 → 19 taken 3 times.
✗ Branch 18 → 20 not taken.
|
3 | if (original_trampoline) |
| 477 | 3 | *original_trampoline = nullptr; | |
| 478 | 3 | return std::unexpected(HookError::InvalidTargetAddress); | |
| 479 | } | ||
| 480 | |||
| 481 | 2 | uintptr_t target_address = reinterpret_cast<uintptr_t>(found_address_start) + aob_offset; | |
| 482 |
1/2✓ Branch 26 → 27 taken 2 times.
✗ Branch 26 → 41 not taken.
|
2 | m_logger.debug("HookManager: AOB pattern for inline hook '{}' found at {}. Applying offset {}. Final target hook address: {}.", |
| 483 |
1/2✓ Branch 25 → 26 taken 2 times.
✗ Branch 25 → 44 not taken.
|
4 | name, DetourModKit::Format::format_address(reinterpret_cast<uintptr_t>(found_address_start)), |
| 484 |
2/4✓ Branch 23 → 24 taken 2 times.
✗ Branch 23 → 50 not taken.
✓ Branch 24 → 25 taken 2 times.
✗ Branch 24 → 47 not taken.
|
4 | DetourModKit::Format::format_hex(aob_offset), DetourModKit::Format::format_address(target_address)); |
| 485 | |||
| 486 |
1/2✓ Branch 30 → 31 taken 2 times.
✗ Branch 30 → 51 not taken.
|
2 | return create_inline_hook(name, target_address, detour_function, original_trampoline, config); |
| 487 | 8 | } | |
| 488 | |||
| 489 | 27 | std::expected<std::string, HookError> HookManager::create_mid_hook( | |
| 490 | std::string_view name, | ||
| 491 | uintptr_t target_address, | ||
| 492 | safetyhook::MidHookFn detour_function, | ||
| 493 | const HookConfig &config) | ||
| 494 | { | ||
| 495 |
1/2✗ Branch 3 → 4 not taken.
✓ Branch 3 → 8 taken 27 times.
|
27 | if (m_shutdown_called.load(std::memory_order_acquire)) |
| 496 | { | ||
| 497 | ✗ | m_logger.error("HookManager: Shutdown in progress. Cannot create mid hook '{}'.", name); | |
| 498 | ✗ | return std::unexpected(HookError::ShutdownInProgress); | |
| 499 | } | ||
| 500 | |||
| 501 | 27 | auto [result, deferred_logs] = [&]() -> std::pair<std::expected<std::string, HookError>, std::vector<DeferredLogEntry>> | |
| 502 | { | ||
| 503 |
1/2✓ Branch 2 → 3 taken 27 times.
✗ Branch 2 → 408 not taken.
|
27 | std::shared_lock<std::shared_mutex> mutator_gate(m_mutator_gate); |
| 504 |
1/2✓ Branch 3 → 4 taken 27 times.
✗ Branch 3 → 406 not taken.
|
27 | std::unique_lock<std::shared_mutex> lock(m_hooks_mutex); |
| 505 | |||
| 506 |
1/2✗ Branch 5 → 6 not taken.
✓ Branch 5 → 20 taken 27 times.
|
27 | if (m_shutdown_called.load(std::memory_order_acquire)) |
| 507 | { | ||
| 508 | ✗ | return {std::unexpected(HookError::ShutdownInProgress), | |
| 509 | ✗ | {{std::format("HookManager: Shutdown in progress. Cannot create mid hook '{}'.", name), LogLevel::Error}}}; | |
| 510 | } | ||
| 511 |
1/2✗ Branch 21 → 22 not taken.
✓ Branch 21 → 36 taken 27 times.
|
27 | if (!m_allocator) |
| 512 | { | ||
| 513 | ✗ | return {std::unexpected(HookError::AllocatorNotAvailable), | |
| 514 | ✗ | {{std::format("HookManager: Allocator not available. Cannot create mid hook '{}'.", name), LogLevel::Error}}}; | |
| 515 | } | ||
| 516 |
2/2✓ Branch 36 → 37 taken 3 times.
✓ Branch 36 → 51 taken 24 times.
|
27 | if (target_address == 0) |
| 517 | { | ||
| 518 | 3 | return {std::unexpected(HookError::InvalidTargetAddress), | |
| 519 |
3/6✓ Branch 41 → 42 taken 3 times.
✗ Branch 41 → 204 not taken.
✓ Branch 46 → 47 taken 3 times.
✓ Branch 46 → 48 taken 3 times.
✗ Branch 208 → 209 not taken.
✗ Branch 208 → 210 not taken.
|
9 | {{std::format("HookManager: Target address is NULL for mid hook '{}'.", name), LogLevel::Error}}}; |
| 520 | } | ||
| 521 |
2/2✓ Branch 51 → 52 taken 1 time.
✓ Branch 51 → 66 taken 23 times.
|
24 | if (detour_function == nullptr) |
| 522 | { | ||
| 523 | 1 | return {std::unexpected(HookError::InvalidDetourFunction), | |
| 524 |
3/6✓ Branch 56 → 57 taken 1 time.
✗ Branch 56 → 224 not taken.
✓ Branch 61 → 62 taken 1 time.
✓ Branch 61 → 63 taken 1 time.
✗ Branch 228 → 229 not taken.
✗ Branch 228 → 230 not taken.
|
3 | {{std::format("HookManager: Detour function is NULL for mid hook '{}'.", name), LogLevel::Error}}}; |
| 525 | } | ||
| 526 | |||
| 527 |
3/4✓ Branch 66 → 67 taken 23 times.
✗ Branch 66 → 404 not taken.
✓ Branch 67 → 68 taken 1 time.
✓ Branch 67 → 82 taken 22 times.
|
23 | if (hook_id_exists_locked(name)) |
| 528 | { | ||
| 529 | 1 | return {std::unexpected(HookError::HookAlreadyExists), | |
| 530 |
3/6✓ Branch 72 → 73 taken 1 time.
✗ Branch 72 → 244 not taken.
✓ Branch 77 → 78 taken 1 time.
✓ Branch 77 → 79 taken 1 time.
✗ Branch 248 → 249 not taken.
✗ Branch 248 → 250 not taken.
|
3 | {{std::format("HookManager: A hook with the name '{}' already exists.", name), LogLevel::Error}}}; |
| 531 | } | ||
| 532 | |||
| 533 | try | ||
| 534 | { | ||
| 535 | 22 | auto sh_flags = config.auto_enable | |
| 536 |
2/2✓ Branch 82 → 83 taken 20 times.
✓ Branch 82 → 84 taken 2 times.
|
22 | ? safetyhook::MidHook::Default |
| 537 | : safetyhook::MidHook::StartDisabled; | ||
| 538 | |||
| 539 | auto hook_creation_result = safetyhook::MidHook::create( | ||
| 540 | 22 | m_allocator, | |
| 541 | 22 | reinterpret_cast<void *>(target_address), | |
| 542 | detour_function, | ||
| 543 |
1/2✓ Branch 85 → 86 taken 22 times.
✗ Branch 85 → 326 not taken.
|
22 | sh_flags); |
| 544 | |||
| 545 |
1/2✗ Branch 87 → 88 not taken.
✓ Branch 87 → 107 taken 22 times.
|
22 | if (!hook_creation_result) |
| 546 | { | ||
| 547 | ✗ | return {std::unexpected(HookError::SafetyHookError), | |
| 548 | {{std::format("HookManager: Failed to create SafetyHook::MidHook for '{}' at {}. Error: {}", | ||
| 549 | ✗ | name, DetourModKit::Format::format_address(target_address), error_to_string(hook_creation_result.error())), | |
| 550 | ✗ | LogLevel::Error}}}; | |
| 551 | } | ||
| 552 | |||
| 553 |
1/2✓ Branch 107 → 108 taken 22 times.
✗ Branch 107 → 324 not taken.
|
44 | auto sh_mid_hook = std::move(hook_creation_result.value()); |
| 554 | |||
| 555 |
2/2✓ Branch 112 → 113 taken 20 times.
✓ Branch 112 → 114 taken 2 times.
|
22 | HookStatus initial_status = sh_mid_hook.enabled() ? HookStatus::Active : HookStatus::Disabled; |
| 556 | |||
| 557 | // Pre-build log entries before committing to m_hooks so that | ||
| 558 | // allocation failures in std::format cannot leave a ghost hook. | ||
| 559 |
3/4✓ Branch 117 → 118 taken 20 times.
✓ Branch 117 → 119 taken 2 times.
✓ Branch 120 → 121 taken 22 times.
✗ Branch 120 → 290 not taken.
|
22 | std::string status_message = (initial_status == HookStatus::Active) ? "and enabled" : "(disabled)"; |
| 560 | 22 | std::vector<DeferredLogEntry> logs; | |
| 561 |
1/2✓ Branch 122 → 123 taken 22 times.
✗ Branch 122 → 318 not taken.
|
22 | logs.reserve(2); |
| 562 | 22 | logs.push_back({std::format("HookManager: Successfully created {} mid hook '{}' targeting {}.", | |
| 563 | ✗ | status_message, name, DetourModKit::Format::format_address(target_address)), | |
| 564 | LogLevel::Info}); | ||
| 565 | |||
| 566 |
3/4✓ Branch 130 → 131 taken 2 times.
✓ Branch 130 → 138 taken 20 times.
✗ Branch 131 → 132 not taken.
✓ Branch 131 → 138 taken 2 times.
|
22 | if (initial_status == HookStatus::Disabled && config.auto_enable) |
| 567 | { | ||
| 568 | ✗ | logs.push_back({std::format("HookManager: Mid hook '{}' was configured for auto-enable but is currently disabled post-creation.", name), | |
| 569 | LogLevel::Warning}); | ||
| 570 | } | ||
| 571 | |||
| 572 |
1/2✓ Branch 140 → 141 taken 22 times.
✗ Branch 140 → 310 not taken.
|
66 | std::string name_str{name}; |
| 573 |
1/2✓ Branch 144 → 145 taken 22 times.
✗ Branch 144 → 316 not taken.
|
22 | auto managed_hook = std::make_unique<MidHook>(name_str, target_address, std::move(sh_mid_hook), initial_status); |
| 574 |
1/2✓ Branch 147 → 148 taken 22 times.
✗ Branch 147 → 313 not taken.
|
44 | m_hooks.emplace(name_str, std::move(managed_hook)); |
| 575 | |||
| 576 | 44 | return {std::move(name_str), std::move(logs)}; | |
| 577 | 22 | } | |
| 578 | ✗ | catch (const std::exception &e) | |
| 579 | { | ||
| 580 | ✗ | return {std::unexpected(HookError::UnknownError), | |
| 581 | ✗ | {{std::format("HookManager: An std::exception occurred during mid hook creation for '{}': {}", name, e.what()), | |
| 582 | ✗ | LogLevel::Error}}}; | |
| 583 | ✗ | } | |
| 584 | ✗ | catch (...) | |
| 585 | { | ||
| 586 | ✗ | return {std::unexpected(HookError::UnknownError), | |
| 587 | {{std::format("HookManager: An unknown exception occurred during mid hook creation for '{}'.", name), | ||
| 588 | ✗ | LogLevel::Error}}}; | |
| 589 | ✗ | } | |
| 590 |
11/102✗ Branch 7 → 8 not taken.
✗ Branch 7 → 174 not taken.
✓ Branch 8 → 9 taken 27 times.
✗ Branch 8 → 37 not taken.
✗ Branch 17 → 18 not taken.
✗ Branch 17 → 19 not taken.
✗ Branch 23 → 24 not taken.
✗ Branch 23 → 194 not taken.
✗ Branch 33 → 34 not taken.
✗ Branch 33 → 35 not taken.
✓ Branch 38 → 39 taken 3 times.
✗ Branch 38 → 214 not taken.
✗ Branch 48 → 49 not taken.
✓ Branch 48 → 50 taken 3 times.
✓ Branch 53 → 54 taken 1 time.
✗ Branch 53 → 234 not taken.
✗ Branch 63 → 64 not taken.
✓ Branch 63 → 65 taken 1 time.
✓ Branch 69 → 70 taken 1 time.
✗ Branch 69 → 254 not taken.
✗ Branch 79 → 80 not taken.
✓ Branch 79 → 81 taken 1 time.
✗ Branch 92 → 93 not taken.
✗ Branch 92 → 274 not taken.
✗ Branch 102 → 103 not taken.
✗ Branch 102 → 104 not taken.
✓ Branch 123 → 124 taken 22 times.
✗ Branch 123 → 301 not taken.
✓ Branch 124 → 125 taken 22 times.
✗ Branch 124 → 298 not taken.
✓ Branch 125 → 126 taken 22 times.
✗ Branch 125 → 293 not taken.
✗ Branch 127 → 128 not taken.
✓ Branch 127 → 129 taken 22 times.
✗ Branch 132 → 133 not taken.
✗ Branch 132 → 308 not taken.
✗ Branch 133 → 134 not taken.
✗ Branch 133 → 303 not taken.
✗ Branch 135 → 136 not taken.
✗ Branch 135 → 137 not taken.
✗ Branch 171 → 172 not taken.
✗ Branch 171 → 173 not taken.
✗ Branch 175 → 176 not taken.
✗ Branch 175 → 179 not taken.
✗ Branch 177 → 178 not taken.
✗ Branch 177 → 179 not taken.
✗ Branch 191 → 192 not taken.
✗ Branch 191 → 193 not taken.
✗ Branch 195 → 196 not taken.
✗ Branch 195 → 199 not taken.
✗ Branch 197 → 198 not taken.
✗ Branch 197 → 199 not taken.
✗ Branch 211 → 212 not taken.
✗ Branch 211 → 213 not taken.
✗ Branch 215 → 216 not taken.
✗ Branch 215 → 219 not taken.
✗ Branch 217 → 218 not taken.
✗ Branch 217 → 219 not taken.
✗ Branch 231 → 232 not taken.
✗ Branch 231 → 233 not taken.
✗ Branch 235 → 236 not taken.
✗ Branch 235 → 239 not taken.
✗ Branch 237 → 238 not taken.
✗ Branch 237 → 239 not taken.
✗ Branch 251 → 252 not taken.
✗ Branch 251 → 253 not taken.
✗ Branch 255 → 256 not taken.
✗ Branch 255 → 259 not taken.
✗ Branch 257 → 258 not taken.
✗ Branch 257 → 259 not taken.
✗ Branch 271 → 272 not taken.
✗ Branch 271 → 273 not taken.
✗ Branch 281 → 282 not taken.
✗ Branch 281 → 285 not taken.
✗ Branch 283 → 284 not taken.
✗ Branch 283 → 285 not taken.
✗ Branch 295 → 296 not taken.
✗ Branch 295 → 297 not taken.
✗ Branch 305 → 306 not taken.
✗ Branch 305 → 307 not taken.
✗ Branch 331 → 332 not taken.
✗ Branch 331 → 369 not taken.
✗ Branch 341 → 342 not taken.
✗ Branch 341 → 343 not taken.
✗ Branch 346 → 347 not taken.
✗ Branch 346 → 392 not taken.
✗ Branch 356 → 357 not taken.
✗ Branch 356 → 358 not taken.
✗ Branch 358 → 160 not taken.
✗ Branch 358 → 404 not taken.
✗ Branch 366 → 367 not taken.
✗ Branch 366 → 368 not taken.
✗ Branch 371 → 372 not taken.
✗ Branch 371 → 375 not taken.
✗ Branch 373 → 374 not taken.
✗ Branch 373 → 375 not taken.
✗ Branch 389 → 390 not taken.
✗ Branch 389 → 391 not taken.
✗ Branch 393 → 394 not taken.
✗ Branch 393 → 397 not taken.
✗ Branch 395 → 396 not taken.
✗ Branch 395 → 397 not taken.
|
103 | }(); |
| 591 | |||
| 592 |
2/2✓ Branch 26 → 13 taken 27 times.
✓ Branch 26 → 27 taken 27 times.
|
81 | for (const auto &entry : deferred_logs) |
| 593 | { | ||
| 594 |
1/2✓ Branch 16 → 17 taken 27 times.
✗ Branch 16 → 38 not taken.
|
27 | m_logger.log(entry.level, entry.msg); |
| 595 | } | ||
| 596 | 27 | return result; | |
| 597 |
1/4✗ Branch 28 → 29 not taken.
✓ Branch 28 → 30 taken 27 times.
✗ Branch 39 → 40 not taken.
✗ Branch 39 → 41 not taken.
|
54 | } |
| 598 | |||
| 599 | 6 | std::expected<std::string, HookError> HookManager::create_mid_hook_aob( | |
| 600 | std::string_view name, | ||
| 601 | uintptr_t module_base, | ||
| 602 | size_t module_size, | ||
| 603 | std::string_view aob_pattern_str, | ||
| 604 | ptrdiff_t aob_offset, | ||
| 605 | safetyhook::MidHookFn detour_function, | ||
| 606 | const HookConfig &config) | ||
| 607 | { | ||
| 608 |
1/2✓ Branch 3 → 4 taken 6 times.
✗ Branch 3 → 31 not taken.
|
6 | m_logger.debug("HookManager: Attempting AOB scan for mid hook '{}' with pattern: \"{}\", offset: {}.", |
| 609 |
1/2✓ Branch 2 → 3 taken 6 times.
✗ Branch 2 → 34 not taken.
|
12 | name, aob_pattern_str, DetourModKit::Format::format_hex(aob_offset)); |
| 610 | |||
| 611 |
1/2✓ Branch 5 → 6 taken 6 times.
✗ Branch 5 → 49 not taken.
|
6 | auto pattern = parse_aob(aob_pattern_str); |
| 612 |
2/2✓ Branch 7 → 8 taken 2 times.
✓ Branch 7 → 12 taken 4 times.
|
6 | if (!pattern.has_value()) |
| 613 | { | ||
| 614 |
1/2✓ Branch 8 → 9 taken 2 times.
✗ Branch 8 → 35 not taken.
|
2 | m_logger.error("HookManager: AOB pattern parsing failed for mid hook '{}'. Pattern: \"{}\".", name, aob_pattern_str); |
| 615 | 2 | return std::unexpected(HookError::InvalidTargetAddress); | |
| 616 | } | ||
| 617 | |||
| 618 |
2/4✓ Branch 12 → 13 taken 4 times.
✗ Branch 12 → 47 not taken.
✓ Branch 13 → 14 taken 4 times.
✗ Branch 13 → 47 not taken.
|
4 | const std::byte *found_address_start = find_pattern(reinterpret_cast<const std::byte *>(module_base), module_size, pattern.value()); |
| 619 |
2/2✓ Branch 14 → 15 taken 3 times.
✓ Branch 14 → 19 taken 1 time.
|
4 | if (!found_address_start) |
| 620 | { | ||
| 621 |
1/2✓ Branch 15 → 16 taken 3 times.
✗ Branch 15 → 36 not taken.
|
3 | m_logger.error("HookManager: AOB pattern not found for mid hook '{}'. Pattern: \"{}\".", name, aob_pattern_str); |
| 622 | 3 | return std::unexpected(HookError::InvalidTargetAddress); | |
| 623 | } | ||
| 624 | |||
| 625 | 1 | uintptr_t target_address = reinterpret_cast<uintptr_t>(found_address_start) + aob_offset; | |
| 626 |
1/2✓ Branch 22 → 23 taken 1 time.
✗ Branch 22 → 37 not taken.
|
1 | m_logger.debug("HookManager: AOB pattern for mid hook '{}' found at {}. Applying offset {}. Final target hook address: {}.", |
| 627 |
1/2✓ Branch 21 → 22 taken 1 time.
✗ Branch 21 → 40 not taken.
|
2 | name, DetourModKit::Format::format_address(reinterpret_cast<uintptr_t>(found_address_start)), |
| 628 |
2/4✓ Branch 19 → 20 taken 1 time.
✗ Branch 19 → 46 not taken.
✓ Branch 20 → 21 taken 1 time.
✗ Branch 20 → 43 not taken.
|
2 | DetourModKit::Format::format_hex(aob_offset), DetourModKit::Format::format_address(target_address)); |
| 629 | |||
| 630 |
1/2✓ Branch 26 → 27 taken 1 time.
✗ Branch 26 → 47 not taken.
|
1 | return create_mid_hook(name, target_address, detour_function, config); |
| 631 | 6 | } | |
| 632 | |||
| 633 | 20 | bool HookManager::is_target_already_hooked(uintptr_t target_address) const noexcept | |
| 634 | { | ||
| 635 |
2/2✓ Branch 2 → 3 taken 1 time.
✓ Branch 2 → 4 taken 19 times.
|
20 | if (target_address == 0) |
| 636 | { | ||
| 637 | 1 | return false; | |
| 638 | } | ||
| 639 | 19 | std::shared_lock<std::shared_mutex> lock(m_hooks_mutex); | |
| 640 |
2/2✓ Branch 22 → 7 taken 11 times.
✓ Branch 22 → 23 taken 12 times.
|
23 | for (const auto &[name, hook_ptr] : m_hooks) |
| 641 | { | ||
| 642 |
5/6✓ Branch 12 → 13 taken 11 times.
✗ Branch 12 → 17 not taken.
✓ Branch 15 → 16 taken 7 times.
✓ Branch 15 → 17 taken 4 times.
✓ Branch 18 → 19 taken 7 times.
✓ Branch 18 → 20 taken 4 times.
|
22 | if (hook_ptr->get_type() == HookType::Inline && |
| 643 | 11 | hook_ptr->get_target_address() == target_address) | |
| 644 | { | ||
| 645 | 7 | return true; | |
| 646 | } | ||
| 647 | } | ||
| 648 | 12 | return false; | |
| 649 | 19 | } | |
| 650 | |||
| 651 | 29 | std::expected<void, HookError> HookManager::remove_hook(std::string_view hook_id) | |
| 652 | { | ||
| 653 |
1/2✗ Branch 3 → 4 not taken.
✓ Branch 3 → 9 taken 29 times.
|
29 | if (m_shutdown_called.load(std::memory_order_acquire)) |
| 654 | { | ||
| 655 | ✗ | m_logger.warning("HookManager: Shutdown in progress. Cannot remove hook '{}'.", hook_id); | |
| 656 | ✗ | return std::unexpected(HookError::ShutdownInProgress); | |
| 657 | } | ||
| 658 | |||
| 659 |
1/2✓ Branch 9 → 10 taken 29 times.
✗ Branch 9 → 68 not taken.
|
29 | std::shared_lock<std::shared_mutex> mutator_gate(m_mutator_gate); |
| 660 | |||
| 661 | // Two-phase removal: disable under shared lock first so that in-flight | ||
| 662 | // trampoline callers (which may acquire shared_lock via with_inline_hook) | ||
| 663 | // can drain before we take the exclusive lock to erase. Without this, | ||
| 664 | // SafetyHook's destructor waiting for trampoline threads while holding | ||
| 665 | // the exclusive lock would deadlock against those threads. | ||
| 666 | { | ||
| 667 |
1/2✓ Branch 10 → 11 taken 29 times.
✗ Branch 10 → 58 not taken.
|
29 | std::shared_lock<std::shared_mutex> shared(m_hooks_mutex); |
| 668 |
1/2✓ Branch 11 → 12 taken 29 times.
✗ Branch 11 → 56 not taken.
|
29 | auto it = m_hooks.find(hook_id); |
| 669 |
2/2✓ Branch 14 → 15 taken 6 times.
✓ Branch 14 → 20 taken 23 times.
|
29 | if (it == m_hooks.end()) |
| 670 | { | ||
| 671 |
1/2✓ Branch 15 → 16 taken 6 times.
✗ Branch 15 → 54 not taken.
|
6 | m_logger.warning("HookManager: Attempted to remove hook with ID '{}', but it was not found.", hook_id); |
| 672 | 6 | return std::unexpected(HookError::HookNotFound); | |
| 673 | } | ||
| 674 |
1/2✓ Branch 22 → 23 taken 23 times.
✗ Branch 22 → 55 not taken.
|
23 | (void)it->second->disable(); |
| 675 |
2/2✓ Branch 25 → 26 taken 23 times.
✓ Branch 25 → 28 taken 6 times.
|
29 | } |
| 676 | |||
| 677 |
1/2✓ Branch 27 → 29 taken 23 times.
✗ Branch 27 → 66 not taken.
|
23 | std::unique_lock<std::shared_mutex> lock(m_hooks_mutex); |
| 678 |
1/2✓ Branch 29 → 30 taken 23 times.
✗ Branch 29 → 64 not taken.
|
23 | auto it = m_hooks.find(hook_id); |
| 679 |
1/2✓ Branch 32 → 33 taken 23 times.
✗ Branch 32 → 47 not taken.
|
23 | if (it != m_hooks.end()) |
| 680 | { | ||
| 681 |
1/2✓ Branch 36 → 37 taken 23 times.
✗ Branch 36 → 63 not taken.
|
23 | std::string name_of_removed_hook = it->second->get_name(); |
| 682 | 23 | HookType type_of_removed_hook = it->second->get_type(); | |
| 683 |
1/2✓ Branch 40 → 41 taken 23 times.
✗ Branch 40 → 61 not taken.
|
23 | m_hooks.erase(it); |
| 684 | ✗ | m_logger.debug("HookManager: Hook '{}' of type '{}' has been removed and unhooked.", | |
| 685 |
3/4✓ Branch 41 → 42 taken 17 times.
✓ Branch 41 → 43 taken 6 times.
✓ Branch 44 → 45 taken 23 times.
✗ Branch 44 → 59 not taken.
|
23 | name_of_removed_hook, (type_of_removed_hook == HookType::Inline ? "Inline" : "Mid")); |
| 686 | 23 | } | |
| 687 | 23 | return {}; | |
| 688 | 29 | } | |
| 689 | |||
| 690 | 334 | void HookManager::remove_all_hooks() | |
| 691 | { | ||
| 692 | // Serialize with shutdown() via compare_exchange_strong. | ||
| 693 | // Only one teardown owner proceeds. | ||
| 694 | 334 | bool expected = false; | |
| 695 |
1/2✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 334 times.
|
334 | if (!m_shutdown_called.compare_exchange_strong(expected, true, std::memory_order_acq_rel)) |
| 696 | ✗ | return; | |
| 697 | |||
| 698 | // Block all mutators (create_*_hook, enable, disable, remove) before | ||
| 699 | // entering phase 1. They hold shared on m_mutator_gate, so acquiring | ||
| 700 | // exclusive here waits for active mutators and blocks new ones. | ||
| 701 |
1/2✓ Branch 5 → 6 taken 334 times.
✗ Branch 5 → 52 not taken.
|
334 | std::unique_lock<std::shared_mutex> mutator_gate(m_mutator_gate); |
| 702 | |||
| 703 | // Two-phase removal: disable hooks under shared lock first so that | ||
| 704 | // in-flight trampoline callers (which may hold shared_lock via | ||
| 705 | // with_inline_hook) can drain before we take the exclusive lock | ||
| 706 | // to erase. Without this, SafetyHook's destructor waiting for | ||
| 707 | // trampoline threads while holding the exclusive lock would | ||
| 708 | // deadlock against those threads. | ||
| 709 | { | ||
| 710 |
1/2✓ Branch 6 → 7 taken 334 times.
✗ Branch 6 → 42 not taken.
|
334 | std::shared_lock<std::shared_mutex> shared(m_hooks_mutex); |
| 711 |
2/2✓ Branch 16 → 9 taken 63 times.
✓ Branch 16 → 17 taken 334 times.
|
397 | for (auto &[name, hook] : m_hooks) |
| 712 | { | ||
| 713 |
1/2✓ Branch 13 → 14 taken 63 times.
✗ Branch 13 → 38 not taken.
|
63 | (void)hook->disable(); |
| 714 | } | ||
| 715 | 334 | } | |
| 716 | |||
| 717 |
1/2✓ Branch 18 → 19 taken 334 times.
✗ Branch 18 → 50 not taken.
|
334 | std::unique_lock<std::shared_mutex> lock(m_hooks_mutex); |
| 718 | |||
| 719 | 334 | size_t num_vmt = m_vmt_hooks.size(); | |
| 720 |
2/2✓ Branch 20 → 21 taken 2 times.
✓ Branch 20 → 23 taken 332 times.
|
334 | if (num_vmt > 0) |
| 721 | { | ||
| 722 |
1/2✓ Branch 21 → 22 taken 2 times.
✗ Branch 21 → 43 not taken.
|
2 | m_logger.debug("HookManager: Removing all {} VMT hooks...", num_vmt); |
| 723 | 2 | m_vmt_hooks.clear(); | |
| 724 | } | ||
| 725 | |||
| 726 |
2/2✓ Branch 24 → 25 taken 55 times.
✓ Branch 24 → 30 taken 279 times.
|
334 | if (!m_hooks.empty()) |
| 727 | { | ||
| 728 | 55 | size_t num_hooks = m_hooks.size(); | |
| 729 |
1/2✓ Branch 26 → 27 taken 55 times.
✗ Branch 26 → 44 not taken.
|
55 | m_logger.debug("HookManager: Removing all {} managed hooks...", num_hooks); |
| 730 | 55 | m_hooks.clear(); | |
| 731 |
1/2✓ Branch 28 → 29 taken 55 times.
✗ Branch 28 → 45 not taken.
|
55 | m_logger.debug("HookManager: All {} managed hooks have been removed and unhooked.", num_hooks); |
| 732 | } | ||
| 733 |
2/2✓ Branch 30 → 31 taken 277 times.
✓ Branch 30 → 33 taken 2 times.
|
279 | else if (num_vmt == 0) |
| 734 | { | ||
| 735 |
1/2✓ Branch 31 → 32 taken 277 times.
✗ Branch 31 → 47 not taken.
|
277 | m_logger.debug("HookManager: remove_all_hooks called, but no hooks were active to remove."); |
| 736 | } | ||
| 737 | |||
| 738 | // Reset under the lock so concurrent create_*_hook calls cannot | ||
| 739 | // observe the flag as true (rejected) and then immediately see it | ||
| 740 | // as false (accepted) before the map is fully cleared. | ||
| 741 | // The mutator_gate exclusive lock is released here, allowing new | ||
| 742 | // mutators to proceed with a fresh m_shutdown_called=false. | ||
| 743 | 334 | m_shutdown_called.store(false, std::memory_order_release); | |
| 744 | 334 | } | |
| 745 | |||
| 746 | 450 | std::expected<void, HookError> HookManager::enable_hook(std::string_view hook_id) | |
| 747 | { | ||
| 748 |
1/2✗ Branch 3 → 4 not taken.
✓ Branch 3 → 9 taken 450 times.
|
450 | if (m_shutdown_called.load(std::memory_order_acquire)) |
| 749 | { | ||
| 750 | ✗ | m_logger.warning("HookManager: Shutdown in progress. Cannot enable hook '{}'.", hook_id); | |
| 751 | ✗ | return std::unexpected(HookError::ShutdownInProgress); | |
| 752 | } | ||
| 753 | |||
| 754 |
1/2✓ Branch 9 → 10 taken 450 times.
✗ Branch 9 → 65 not taken.
|
450 | std::shared_lock<std::shared_mutex> mutator_gate(m_mutator_gate); |
| 755 |
1/2✓ Branch 10 → 11 taken 450 times.
✗ Branch 10 → 63 not taken.
|
450 | std::shared_lock<std::shared_mutex> lock(m_hooks_mutex); |
| 756 |
1/2✗ Branch 12 → 13 not taken.
✓ Branch 12 → 18 taken 450 times.
|
450 | if (m_shutdown_called.load(std::memory_order_acquire)) |
| 757 | { | ||
| 758 | ✗ | m_logger.warning("HookManager: Shutdown in progress. Cannot enable hook '{}'.", hook_id); | |
| 759 | ✗ | return std::unexpected(HookError::ShutdownInProgress); | |
| 760 | } | ||
| 761 |
1/2✓ Branch 18 → 19 taken 450 times.
✗ Branch 18 → 61 not taken.
|
450 | auto it = m_hooks.find(hook_id); |
| 762 |
2/2✓ Branch 21 → 22 taken 43 times.
✓ Branch 21 → 27 taken 407 times.
|
450 | if (it == m_hooks.end()) |
| 763 | { | ||
| 764 |
1/2✓ Branch 22 → 23 taken 43 times.
✗ Branch 22 → 55 not taken.
|
43 | m_logger.warning("HookManager: Hook ID '{}' not found for enable operation.", hook_id); |
| 765 | 43 | return std::unexpected(HookError::HookNotFound); | |
| 766 | } | ||
| 767 | |||
| 768 | 407 | Hook *hook = it->second.get(); | |
| 769 |
1/2✓ Branch 29 → 30 taken 407 times.
✗ Branch 29 → 61 not taken.
|
407 | auto result = hook->enable(); |
| 770 |
2/2✓ Branch 31 → 32 taken 114 times.
✓ Branch 31 → 36 taken 293 times.
|
407 | if (result) |
| 771 | { | ||
| 772 |
1/2✓ Branch 32 → 33 taken 114 times.
✗ Branch 32 → 56 not taken.
|
114 | m_logger.debug("HookManager: Hook '{}' successfully enabled.", hook_id); |
| 773 | 114 | return {}; | |
| 774 | } | ||
| 775 | |||
| 776 | 293 | const auto error = result.error(); | |
| 777 |
1/2✓ Branch 37 → 38 taken 293 times.
✗ Branch 37 → 42 not taken.
|
293 | if (error == HookError::InvalidHookState) |
| 778 | { | ||
| 779 |
1/2✓ Branch 40 → 41 taken 293 times.
✗ Branch 40 → 57 not taken.
|
293 | m_logger.warning("HookManager: Hook '{}' cannot be enabled. Current status: {}", hook_id, Hook::status_to_string(hook->get_status())); |
| 780 | } | ||
| 781 | else | ||
| 782 | { | ||
| 783 | ✗ | m_logger.error("HookManager: Failed to enable hook '{}': {}", hook_id, Hook::error_to_string(error)); | |
| 784 | } | ||
| 785 | 293 | return std::unexpected(error); | |
| 786 | 450 | } | |
| 787 | |||
| 788 | 452 | std::expected<void, HookError> HookManager::disable_hook(std::string_view hook_id) | |
| 789 | { | ||
| 790 |
1/2✗ Branch 3 → 4 not taken.
✓ Branch 3 → 9 taken 452 times.
|
452 | if (m_shutdown_called.load(std::memory_order_acquire)) |
| 791 | { | ||
| 792 | ✗ | m_logger.warning("HookManager: Shutdown in progress. Cannot disable hook '{}'.", hook_id); | |
| 793 | ✗ | return std::unexpected(HookError::ShutdownInProgress); | |
| 794 | } | ||
| 795 | |||
| 796 |
1/2✓ Branch 9 → 10 taken 452 times.
✗ Branch 9 → 65 not taken.
|
452 | std::shared_lock<std::shared_mutex> mutator_gate(m_mutator_gate); |
| 797 |
1/2✓ Branch 10 → 11 taken 452 times.
✗ Branch 10 → 63 not taken.
|
452 | std::shared_lock<std::shared_mutex> lock(m_hooks_mutex); |
| 798 |
1/2✗ Branch 12 → 13 not taken.
✓ Branch 12 → 18 taken 452 times.
|
452 | if (m_shutdown_called.load(std::memory_order_acquire)) |
| 799 | { | ||
| 800 | ✗ | m_logger.warning("HookManager: Shutdown in progress. Cannot disable hook '{}'.", hook_id); | |
| 801 | ✗ | return std::unexpected(HookError::ShutdownInProgress); | |
| 802 | } | ||
| 803 |
1/2✓ Branch 18 → 19 taken 452 times.
✗ Branch 18 → 61 not taken.
|
452 | auto it = m_hooks.find(hook_id); |
| 804 |
2/2✓ Branch 21 → 22 taken 43 times.
✓ Branch 21 → 27 taken 409 times.
|
452 | if (it == m_hooks.end()) |
| 805 | { | ||
| 806 |
1/2✓ Branch 22 → 23 taken 43 times.
✗ Branch 22 → 55 not taken.
|
43 | m_logger.warning("HookManager: Hook ID '{}' not found for disable operation.", hook_id); |
| 807 | 43 | return std::unexpected(HookError::HookNotFound); | |
| 808 | } | ||
| 809 | |||
| 810 | 409 | Hook *hook = it->second.get(); | |
| 811 |
1/2✓ Branch 29 → 30 taken 409 times.
✗ Branch 29 → 61 not taken.
|
409 | auto result = hook->disable(); |
| 812 |
2/2✓ Branch 31 → 32 taken 134 times.
✓ Branch 31 → 36 taken 274 times.
|
409 | if (result) |
| 813 | { | ||
| 814 |
1/2✓ Branch 32 → 33 taken 135 times.
✗ Branch 32 → 56 not taken.
|
134 | m_logger.debug("HookManager: Hook '{}' successfully disabled.", hook_id); |
| 815 | 135 | return {}; | |
| 816 | } | ||
| 817 | |||
| 818 | 274 | const auto error = result.error(); | |
| 819 |
1/2✓ Branch 37 → 38 taken 274 times.
✗ Branch 37 → 42 not taken.
|
274 | if (error == HookError::InvalidHookState) |
| 820 | { | ||
| 821 |
1/2✓ Branch 40 → 41 taken 274 times.
✗ Branch 40 → 57 not taken.
|
274 | m_logger.warning("HookManager: Hook '{}' cannot be disabled. Current status: {}", hook_id, Hook::status_to_string(hook->get_status())); |
| 822 | } | ||
| 823 | else | ||
| 824 | { | ||
| 825 | ✗ | m_logger.error("HookManager: Failed to disable hook '{}': {}", hook_id, Hook::error_to_string(error)); | |
| 826 | } | ||
| 827 | 274 | return std::unexpected(error); | |
| 828 | 452 | } | |
| 829 | |||
| 830 | 41720 | std::optional<HookStatus> HookManager::get_hook_status(std::string_view hook_id) const | |
| 831 | { | ||
| 832 |
1/2✓ Branch 2 → 3 taken 41753 times.
✗ Branch 2 → 21 not taken.
|
41720 | std::shared_lock<std::shared_mutex> lock(m_hooks_mutex); |
| 833 |
1/2✓ Branch 3 → 4 taken 41696 times.
✗ Branch 3 → 19 not taken.
|
41753 | auto it = m_hooks.find(hook_id); |
| 834 |
2/2✓ Branch 6 → 7 taken 41637 times.
✓ Branch 6 → 13 taken 45 times.
|
41696 | if (it != m_hooks.end()) |
| 835 | { | ||
| 836 | 41637 | return it->second->get_status(); | |
| 837 | } | ||
| 838 | 45 | return std::nullopt; | |
| 839 | 41692 | } | |
| 840 | |||
| 841 | 45 | std::unordered_map<HookStatus, size_t> HookManager::get_hook_counts() const | |
| 842 | { | ||
| 843 |
1/2✓ Branch 2 → 3 taken 45 times.
✗ Branch 2 → 28 not taken.
|
45 | std::shared_lock<std::shared_mutex> lock(m_hooks_mutex); |
| 844 | 45 | std::unordered_map<HookStatus, size_t> counts; | |
| 845 |
1/2✓ Branch 4 → 5 taken 45 times.
✗ Branch 4 → 20 not taken.
|
45 | counts[HookStatus::Active] = 0; |
| 846 |
1/2✓ Branch 5 → 6 taken 45 times.
✗ Branch 5 → 21 not taken.
|
45 | counts[HookStatus::Disabled] = 0; |
| 847 |
2/2✓ Branch 16 → 8 taken 4 times.
✓ Branch 16 → 17 taken 45 times.
|
49 | for (const auto &[name, hook_ptr] : m_hooks) |
| 848 | { | ||
| 849 |
1/2✓ Branch 13 → 14 taken 4 times.
✗ Branch 13 → 22 not taken.
|
4 | counts[hook_ptr->get_status()]++; |
| 850 | } | ||
| 851 | 45 | return counts; | |
| 852 | 45 | } | |
| 853 | |||
| 854 | 58 | std::vector<std::string> HookManager::get_hook_ids(std::optional<HookStatus> status_filter) const | |
| 855 | { | ||
| 856 |
1/2✓ Branch 2 → 3 taken 58 times.
✗ Branch 2 → 31 not taken.
|
58 | std::shared_lock<std::shared_mutex> lock(m_hooks_mutex); |
| 857 | 58 | std::vector<std::string> ids; | |
| 858 |
1/2✓ Branch 4 → 5 taken 58 times.
✗ Branch 4 → 27 not taken.
|
58 | ids.reserve(m_hooks.size()); |
| 859 |
2/2✓ Branch 22 → 7 taken 9 times.
✓ Branch 22 → 23 taken 58 times.
|
67 | for (const auto &[name, hook_ptr] : m_hooks) |
| 860 | { | ||
| 861 |
5/8✓ Branch 11 → 12 taken 6 times.
✓ Branch 11 → 16 taken 3 times.
✓ Branch 14 → 15 taken 6 times.
✗ Branch 14 → 26 not taken.
✓ Branch 15 → 16 taken 6 times.
✗ Branch 15 → 17 not taken.
✓ Branch 18 → 19 taken 9 times.
✗ Branch 18 → 20 not taken.
|
9 | if (!status_filter.has_value() || hook_ptr->get_status() == status_filter.value()) |
| 862 | { | ||
| 863 |
1/2✓ Branch 19 → 20 taken 9 times.
✗ Branch 19 → 26 not taken.
|
9 | ids.push_back(name); |
| 864 | } | ||
| 865 | } | ||
| 866 | 58 | return ids; | |
| 867 | 58 | } | |
| 868 | |||
| 869 | 30 | std::expected<std::string, HookError> HookManager::create_vmt_hook( | |
| 870 | std::string_view name, void *object) | ||
| 871 | { | ||
| 872 |
2/2✓ Branch 3 → 4 taken 5 times.
✓ Branch 3 → 8 taken 24 times.
|
30 | if (m_shutdown_called.load(std::memory_order_acquire)) |
| 873 | { | ||
| 874 |
1/2✓ Branch 4 → 5 taken 6 times.
✗ Branch 4 → 36 not taken.
|
5 | m_logger.error("HookManager: Shutdown in progress. Cannot create VMT hook '{}'.", name); |
| 875 | 6 | return std::unexpected(HookError::ShutdownInProgress); | |
| 876 | } | ||
| 877 | |||
| 878 | 24 | auto [result, deferred_logs] = [&]() -> std::pair<std::expected<std::string, HookError>, std::vector<DeferredLogEntry>> | |
| 879 | { | ||
| 880 |
1/2✓ Branch 2 → 3 taken 24 times.
✗ Branch 2 → 287 not taken.
|
24 | std::shared_lock<std::shared_mutex> mutator_gate(m_mutator_gate); |
| 881 |
1/2✓ Branch 3 → 4 taken 24 times.
✗ Branch 3 → 285 not taken.
|
24 | std::unique_lock<std::shared_mutex> lock(m_hooks_mutex); |
| 882 | |||
| 883 |
2/2✓ Branch 5 → 6 taken 1 time.
✓ Branch 5 → 20 taken 23 times.
|
24 | if (m_shutdown_called.load(std::memory_order_acquire)) |
| 884 | { | ||
| 885 | 1 | return {std::unexpected(HookError::ShutdownInProgress), | |
| 886 |
3/6✓ Branch 10 → 11 taken 1 time.
✗ Branch 10 → 100 not taken.
✓ Branch 15 → 16 taken 1 time.
✓ Branch 15 → 17 taken 1 time.
✗ Branch 104 → 105 not taken.
✗ Branch 104 → 106 not taken.
|
3 | {{std::format("HookManager: Shutdown in progress. Cannot create VMT hook '{}'.", name), LogLevel::Error}}}; |
| 887 | } | ||
| 888 |
2/2✓ Branch 20 → 21 taken 1 time.
✓ Branch 20 → 35 taken 22 times.
|
23 | if (object == nullptr) |
| 889 | { | ||
| 890 | 1 | return {std::unexpected(HookError::InvalidObject), | |
| 891 |
3/6✓ Branch 25 → 26 taken 1 time.
✗ Branch 25 → 120 not taken.
✓ Branch 30 → 31 taken 1 time.
✓ Branch 30 → 32 taken 1 time.
✗ Branch 124 → 125 not taken.
✗ Branch 124 → 126 not taken.
|
3 | {{std::format("HookManager: Object pointer is NULL for VMT hook '{}'.", name), LogLevel::Error}}}; |
| 892 | } | ||
| 893 |
3/4✓ Branch 35 → 36 taken 22 times.
✗ Branch 35 → 283 not taken.
✓ Branch 36 → 37 taken 1 time.
✓ Branch 36 → 51 taken 21 times.
|
22 | if (vmt_hook_exists_locked(name)) |
| 894 | { | ||
| 895 | 1 | return {std::unexpected(HookError::HookAlreadyExists), | |
| 896 |
3/6✓ Branch 41 → 42 taken 1 time.
✗ Branch 41 → 140 not taken.
✓ Branch 46 → 47 taken 1 time.
✓ Branch 46 → 48 taken 1 time.
✗ Branch 144 → 145 not taken.
✗ Branch 144 → 146 not taken.
|
3 | {{std::format("HookManager: A VMT hook with the name '{}' already exists.", name), LogLevel::Error}}}; |
| 897 | } | ||
| 898 | |||
| 899 | try | ||
| 900 | { | ||
| 901 |
1/2✓ Branch 51 → 52 taken 21 times.
✗ Branch 51 → 205 not taken.
|
21 | auto vmt_result = safetyhook::VmtHook::create(object); |
| 902 | |||
| 903 |
1/2✗ Branch 53 → 54 not taken.
✓ Branch 53 → 70 taken 21 times.
|
21 | if (!vmt_result) |
| 904 | { | ||
| 905 | ✗ | return {std::unexpected(HookError::SafetyHookError), | |
| 906 | {{std::format("HookManager: Failed to create SafetyHook::VmtHook for '{}' on object {}.", | ||
| 907 | ✗ | name, DetourModKit::Format::format_address(reinterpret_cast<uintptr_t>(object))), | |
| 908 | ✗ | LogLevel::Error}}}; | |
| 909 | } | ||
| 910 | |||
| 911 |
1/2✓ Branch 72 → 73 taken 21 times.
✗ Branch 72 → 183 not taken.
|
42 | std::string name_str{name}; |
| 912 | |||
| 913 | 21 | std::vector<DeferredLogEntry> logs; | |
| 914 | 21 | logs.push_back({std::format("HookManager: Successfully created VMT hook '{}' on object {}.", | |
| 915 | ✗ | name, DetourModKit::Format::format_address(reinterpret_cast<uintptr_t>(object))), | |
| 916 | LogLevel::Info}); | ||
| 917 | |||
| 918 |
1/2✓ Branch 86 → 87 taken 21 times.
✗ Branch 86 → 196 not taken.
|
21 | m_vmt_hooks.emplace( |
| 919 | std::piecewise_construct, | ||
| 920 | 21 | std::forward_as_tuple(name_str), | |
| 921 |
1/2✓ Branch 81 → 82 taken 21 times.
✗ Branch 81 → 197 not taken.
|
63 | std::forward_as_tuple(name_str, std::move(vmt_result.value()))); |
| 922 | |||
| 923 | 42 | return {std::move(name_str), std::move(logs)}; | |
| 924 | 21 | } | |
| 925 | ✗ | catch (const std::exception &e) | |
| 926 | { | ||
| 927 | ✗ | return {std::unexpected(HookError::UnknownError), | |
| 928 | ✗ | {{std::format("HookManager: Exception during VMT hook creation for '{}': {}", name, e.what()), | |
| 929 | ✗ | LogLevel::Error}}}; | |
| 930 | ✗ | } | |
| 931 | ✗ | catch (...) | |
| 932 | { | ||
| 933 | ✗ | return {std::unexpected(HookError::UnknownError), | |
| 934 | {{std::format("HookManager: Unknown exception during VMT hook creation for '{}'.", name), | ||
| 935 | ✗ | LogLevel::Error}}}; | |
| 936 | ✗ | } | |
| 937 |
11/76✓ Branch 7 → 8 taken 1 time.
✗ Branch 7 → 110 not taken.
✓ Branch 8 → 9 taken 24 times.
✗ Branch 8 → 37 not taken.
✗ Branch 17 → 18 not taken.
✓ Branch 17 → 19 taken 1 time.
✓ Branch 22 → 23 taken 1 time.
✗ Branch 22 → 130 not taken.
✗ Branch 32 → 33 not taken.
✓ Branch 32 → 34 taken 1 time.
✓ Branch 38 → 39 taken 1 time.
✗ Branch 38 → 150 not taken.
✗ Branch 48 → 49 not taken.
✓ Branch 48 → 50 taken 1 time.
✗ Branch 55 → 56 not taken.
✗ Branch 55 → 173 not taken.
✗ Branch 56 → 57 not taken.
✗ Branch 56 → 170 not taken.
✗ Branch 66 → 67 not taken.
✗ Branch 66 → 68 not taken.
✓ Branch 74 → 75 taken 21 times.
✗ Branch 74 → 194 not taken.
✓ Branch 75 → 76 taken 21 times.
✗ Branch 75 → 191 not taken.
✓ Branch 76 → 77 taken 21 times.
✗ Branch 76 → 186 not taken.
✗ Branch 78 → 79 not taken.
✓ Branch 78 → 80 taken 21 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 174 → 175 not taken.
✗ Branch 174 → 178 not taken.
✗ Branch 176 → 177 not taken.
✗ Branch 176 → 178 not taken.
✗ Branch 188 → 189 not taken.
✗ Branch 188 → 190 not taken.
✗ Branch 210 → 211 not taken.
✗ Branch 210 → 248 not taken.
✗ Branch 220 → 221 not taken.
✗ Branch 220 → 222 not taken.
✗ Branch 225 → 226 not taken.
✗ Branch 225 → 271 not taken.
✗ Branch 235 → 236 not taken.
✗ Branch 235 → 237 not taken.
✗ Branch 237 → 96 not taken.
✗ Branch 237 → 283 not taken.
✗ Branch 245 → 246 not taken.
✗ Branch 245 → 247 not taken.
✗ Branch 250 → 251 not taken.
✗ Branch 250 → 254 not taken.
✗ Branch 252 → 253 not taken.
✗ Branch 252 → 254 not taken.
✗ Branch 268 → 269 not taken.
✗ Branch 268 → 270 not taken.
✗ Branch 272 → 273 not taken.
✗ Branch 272 → 276 not taken.
✗ Branch 274 → 275 not taken.
✗ Branch 274 → 276 not taken.
|
93 | }(); |
| 938 | |||
| 939 |
2/2✓ Branch 26 → 13 taken 24 times.
✓ Branch 26 → 27 taken 24 times.
|
72 | for (const auto &entry : deferred_logs) |
| 940 | { | ||
| 941 |
1/2✓ Branch 16 → 17 taken 24 times.
✗ Branch 16 → 38 not taken.
|
24 | m_logger.log(entry.level, entry.msg); |
| 942 | } | ||
| 943 | 24 | return result; | |
| 944 |
1/4✗ Branch 28 → 29 not taken.
✓ Branch 28 → 30 taken 24 times.
✗ Branch 39 → 40 not taken.
✗ Branch 39 → 41 not taken.
|
48 | } |
| 945 | |||
| 946 | 2 | std::expected<void, HookError> HookManager::remove_vmt_hook(std::string_view vmt_name) | |
| 947 | { | ||
| 948 |
1/2✗ Branch 3 → 4 not taken.
✓ Branch 3 → 9 taken 2 times.
|
2 | if (m_shutdown_called.load(std::memory_order_acquire)) |
| 949 | { | ||
| 950 | ✗ | m_logger.warning("HookManager: Shutdown in progress. Cannot remove VMT hook '{}'.", vmt_name); | |
| 951 | ✗ | return std::unexpected(HookError::ShutdownInProgress); | |
| 952 | } | ||
| 953 | |||
| 954 |
1/2✓ Branch 9 → 10 taken 2 times.
✗ Branch 9 → 43 not taken.
|
2 | std::shared_lock<std::shared_mutex> mutator_gate(m_mutator_gate); |
| 955 |
1/2✓ Branch 10 → 11 taken 2 times.
✗ Branch 10 → 41 not taken.
|
2 | std::unique_lock<std::shared_mutex> lock(m_hooks_mutex); |
| 956 |
1/2✓ Branch 11 → 12 taken 2 times.
✗ Branch 11 → 39 not taken.
|
2 | auto it = m_vmt_hooks.find(vmt_name); |
| 957 |
2/2✓ Branch 14 → 15 taken 1 time.
✓ Branch 14 → 24 taken 1 time.
|
2 | if (it != m_vmt_hooks.end()) |
| 958 | { | ||
| 959 |
1/2✓ Branch 17 → 18 taken 1 time.
✗ Branch 17 → 37 not taken.
|
1 | std::string removed_name = it->second.get_name(); |
| 960 |
1/2✓ Branch 18 → 19 taken 1 time.
✗ Branch 18 → 35 not taken.
|
1 | m_vmt_hooks.erase(it); |
| 961 |
1/2✓ Branch 19 → 20 taken 1 time.
✗ Branch 19 → 34 not taken.
|
1 | m_logger.debug("HookManager: VMT hook '{}' has been removed.", removed_name); |
| 962 | 1 | return {}; | |
| 963 | 1 | } | |
| 964 |
1/2✓ Branch 24 → 25 taken 1 time.
✗ Branch 24 → 38 not taken.
|
1 | m_logger.warning("HookManager: Attempted to remove VMT hook '{}', but it was not found.", vmt_name); |
| 965 | 1 | return std::unexpected(HookError::VmtHookNotFound); | |
| 966 | 2 | } | |
| 967 | |||
| 968 | 2 | std::expected<void, HookError> HookManager::remove_vmt_method(std::string_view vmt_name, size_t method_index) | |
| 969 | { | ||
| 970 |
1/2✗ Branch 3 → 4 not taken.
✓ Branch 3 → 9 taken 2 times.
|
2 | if (m_shutdown_called.load(std::memory_order_acquire)) |
| 971 | { | ||
| 972 | ✗ | m_logger.warning("HookManager: Shutdown in progress. Cannot remove VMT method on '{}'.", vmt_name); | |
| 973 | ✗ | return std::unexpected(HookError::ShutdownInProgress); | |
| 974 | } | ||
| 975 | |||
| 976 |
1/2✓ Branch 9 → 10 taken 2 times.
✗ Branch 9 → 44 not taken.
|
2 | std::shared_lock<std::shared_mutex> mutator_gate(m_mutator_gate); |
| 977 |
1/2✓ Branch 10 → 11 taken 2 times.
✗ Branch 10 → 42 not taken.
|
2 | std::unique_lock<std::shared_mutex> lock(m_hooks_mutex); |
| 978 |
1/2✓ Branch 11 → 12 taken 2 times.
✗ Branch 11 → 40 not taken.
|
2 | auto it = m_vmt_hooks.find(vmt_name); |
| 979 |
1/2✗ Branch 14 → 15 not taken.
✓ Branch 14 → 20 taken 2 times.
|
2 | if (it == m_vmt_hooks.end()) |
| 980 | { | ||
| 981 | ✗ | m_logger.warning("HookManager: VMT hook '{}' not found for method removal.", vmt_name); | |
| 982 | ✗ | return std::unexpected(HookError::VmtHookNotFound); | |
| 983 | } | ||
| 984 | |||
| 985 |
3/4✓ Branch 21 → 22 taken 2 times.
✗ Branch 21 → 40 not taken.
✓ Branch 22 → 23 taken 1 time.
✓ Branch 22 → 27 taken 1 time.
|
2 | if (it->second.remove_method_hook(method_index)) |
| 986 | { | ||
| 987 |
1/2✓ Branch 23 → 24 taken 1 time.
✗ Branch 23 → 38 not taken.
|
1 | m_logger.debug("HookManager: VMT '{}' method index {} has been unhooked.", vmt_name, method_index); |
| 988 | 1 | return {}; | |
| 989 | } | ||
| 990 | |||
| 991 |
1/2✓ Branch 27 → 28 taken 1 time.
✗ Branch 27 → 39 not taken.
|
1 | m_logger.warning("HookManager: VMT '{}' has no hooked method at index {}.", vmt_name, method_index); |
| 992 | 1 | return std::unexpected(HookError::MethodNotFound); | |
| 993 | 2 | } | |
| 994 | |||
| 995 | 3 | bool HookManager::apply_vmt_hook(std::string_view vmt_name, void *object) | |
| 996 | { | ||
| 997 |
1/2✗ Branch 3 → 4 not taken.
✓ Branch 3 → 6 taken 3 times.
|
3 | if (m_shutdown_called.load(std::memory_order_acquire)) |
| 998 | { | ||
| 999 | ✗ | m_logger.warning("HookManager: Shutdown in progress. Cannot apply VMT hook '{}'.", vmt_name); | |
| 1000 | ✗ | return false; | |
| 1001 | } | ||
| 1002 |
2/2✓ Branch 6 → 7 taken 1 time.
✓ Branch 6 → 9 taken 2 times.
|
3 | if (object == nullptr) |
| 1003 | { | ||
| 1004 |
1/2✓ Branch 7 → 8 taken 1 time.
✗ Branch 7 → 29 not taken.
|
1 | m_logger.warning("HookManager: Cannot apply VMT hook '{}' to null object.", vmt_name); |
| 1005 | 1 | return false; | |
| 1006 | } | ||
| 1007 | |||
| 1008 |
1/2✓ Branch 9 → 10 taken 2 times.
✗ Branch 9 → 39 not taken.
|
2 | std::shared_lock<std::shared_mutex> mutator_gate(m_mutator_gate); |
| 1009 |
1/2✓ Branch 10 → 11 taken 2 times.
✗ Branch 10 → 37 not taken.
|
2 | std::unique_lock<std::shared_mutex> lock(m_hooks_mutex); |
| 1010 |
1/2✓ Branch 11 → 12 taken 2 times.
✗ Branch 11 → 35 not taken.
|
2 | auto it = m_vmt_hooks.find(vmt_name); |
| 1011 |
2/2✓ Branch 14 → 15 taken 1 time.
✓ Branch 14 → 17 taken 1 time.
|
2 | if (it == m_vmt_hooks.end()) |
| 1012 | { | ||
| 1013 |
1/2✓ Branch 15 → 16 taken 1 time.
✗ Branch 15 → 30 not taken.
|
1 | m_logger.warning("HookManager: VMT hook '{}' not found for apply.", vmt_name); |
| 1014 | 1 | return false; | |
| 1015 | } | ||
| 1016 | |||
| 1017 |
1/2✓ Branch 19 → 20 taken 1 time.
✗ Branch 19 → 35 not taken.
|
1 | it->second.vmt_hook().apply(object); |
| 1018 |
1/2✓ Branch 21 → 22 taken 1 time.
✗ Branch 21 → 31 not taken.
|
1 | m_logger.debug("HookManager: VMT hook '{}' applied to object {}.", |
| 1019 |
1/2✓ Branch 20 → 21 taken 1 time.
✗ Branch 20 → 34 not taken.
|
2 | vmt_name, DetourModKit::Format::format_address(reinterpret_cast<uintptr_t>(object))); |
| 1020 | 1 | return true; | |
| 1021 | 2 | } | |
| 1022 | |||
| 1023 | 3 | bool HookManager::remove_vmt_from_object(std::string_view vmt_name, void *object) | |
| 1024 | { | ||
| 1025 |
1/2✗ Branch 3 → 4 not taken.
✓ Branch 3 → 6 taken 3 times.
|
3 | if (m_shutdown_called.load(std::memory_order_acquire)) |
| 1026 | { | ||
| 1027 | ✗ | m_logger.warning("HookManager: Shutdown in progress. Cannot remove VMT hook '{}' from object.", vmt_name); | |
| 1028 | ✗ | return false; | |
| 1029 | } | ||
| 1030 |
2/2✓ Branch 6 → 7 taken 1 time.
✓ Branch 6 → 9 taken 2 times.
|
3 | if (object == nullptr) |
| 1031 | { | ||
| 1032 |
1/2✓ Branch 7 → 8 taken 1 time.
✗ Branch 7 → 29 not taken.
|
1 | m_logger.warning("HookManager: Cannot remove VMT hook '{}' from null object.", vmt_name); |
| 1033 | 1 | return false; | |
| 1034 | } | ||
| 1035 | |||
| 1036 |
1/2✓ Branch 9 → 10 taken 2 times.
✗ Branch 9 → 39 not taken.
|
2 | std::shared_lock<std::shared_mutex> mutator_gate(m_mutator_gate); |
| 1037 |
1/2✓ Branch 10 → 11 taken 2 times.
✗ Branch 10 → 37 not taken.
|
2 | std::unique_lock<std::shared_mutex> lock(m_hooks_mutex); |
| 1038 |
1/2✓ Branch 11 → 12 taken 2 times.
✗ Branch 11 → 35 not taken.
|
2 | auto it = m_vmt_hooks.find(vmt_name); |
| 1039 |
2/2✓ Branch 14 → 15 taken 1 time.
✓ Branch 14 → 17 taken 1 time.
|
2 | if (it == m_vmt_hooks.end()) |
| 1040 | { | ||
| 1041 |
1/2✓ Branch 15 → 16 taken 1 time.
✗ Branch 15 → 30 not taken.
|
1 | m_logger.warning("HookManager: VMT hook '{}' not found for object removal.", vmt_name); |
| 1042 | 1 | return false; | |
| 1043 | } | ||
| 1044 | |||
| 1045 |
1/2✓ Branch 19 → 20 taken 1 time.
✗ Branch 19 → 35 not taken.
|
1 | it->second.vmt_hook().remove(object); |
| 1046 |
1/2✓ Branch 21 → 22 taken 1 time.
✗ Branch 21 → 31 not taken.
|
1 | m_logger.debug("HookManager: VMT hook '{}' removed from object {}.", |
| 1047 |
1/2✓ Branch 20 → 21 taken 1 time.
✗ Branch 20 → 34 not taken.
|
2 | vmt_name, DetourModKit::Format::format_address(reinterpret_cast<uintptr_t>(object))); |
| 1048 | 1 | return true; | |
| 1049 | 2 | } | |
| 1050 | |||
| 1051 | 16 | void HookManager::remove_all_vmt_hooks() | |
| 1052 | { | ||
| 1053 |
1/2✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 16 times.
|
16 | if (m_shutdown_called.load(std::memory_order_acquire)) |
| 1054 | ✗ | return; | |
| 1055 | |||
| 1056 |
1/2✓ Branch 5 → 6 taken 16 times.
✗ Branch 5 → 28 not taken.
|
16 | std::shared_lock<std::shared_mutex> mutator_gate(m_mutator_gate); |
| 1057 |
1/2✓ Branch 6 → 7 taken 16 times.
✗ Branch 6 → 26 not taken.
|
16 | std::unique_lock<std::shared_mutex> lock(m_hooks_mutex); |
| 1058 |
2/2✓ Branch 8 → 9 taken 14 times.
✓ Branch 8 → 14 taken 2 times.
|
16 | if (!m_vmt_hooks.empty()) |
| 1059 | { | ||
| 1060 | 14 | size_t num_hooks = m_vmt_hooks.size(); | |
| 1061 |
1/2✓ Branch 10 → 11 taken 14 times.
✗ Branch 10 → 20 not taken.
|
14 | m_logger.debug("HookManager: Removing all {} VMT hooks...", num_hooks); |
| 1062 | 14 | m_vmt_hooks.clear(); | |
| 1063 |
1/2✓ Branch 12 → 13 taken 14 times.
✗ Branch 12 → 21 not taken.
|
14 | m_logger.debug("HookManager: All {} VMT hooks have been removed.", num_hooks); |
| 1064 | } | ||
| 1065 | else | ||
| 1066 | { | ||
| 1067 |
1/2✓ Branch 14 → 15 taken 2 times.
✗ Branch 14 → 23 not taken.
|
2 | m_logger.debug("HookManager: remove_all_vmt_hooks called, but no VMT hooks were active."); |
| 1068 | } | ||
| 1069 | 16 | } | |
| 1070 | |||
| 1071 | 10 | std::vector<std::string> HookManager::get_vmt_hook_names() const | |
| 1072 | { | ||
| 1073 |
1/2✓ Branch 2 → 3 taken 10 times.
✗ Branch 2 → 22 not taken.
|
10 | std::shared_lock<std::shared_mutex> lock(m_hooks_mutex); |
| 1074 | 10 | std::vector<std::string> names; | |
| 1075 |
1/2✓ Branch 4 → 5 taken 10 times.
✗ Branch 4 → 18 not taken.
|
10 | names.reserve(m_vmt_hooks.size()); |
| 1076 |
2/2✓ Branch 13 → 7 taken 5 times.
✓ Branch 13 → 14 taken 10 times.
|
15 | for (const auto &[name, entry] : m_vmt_hooks) |
| 1077 | { | ||
| 1078 |
1/2✓ Branch 10 → 11 taken 5 times.
✗ Branch 10 → 17 not taken.
|
5 | names.push_back(name); |
| 1079 | } | ||
| 1080 | 10 | return names; | |
| 1081 | 10 | } | |
| 1082 |