Chrysalis - Hooks
Loading...
Searching...
No Matches
HooksManager.h
Go to the documentation of this file.
1#ifndef CHRYSALIS_HOOKMANAGER_H
2#define CHRYSALIS_HOOKMANAGER_H
3
4#include <list>
5#include <regex>
6#include <string>
7#include <optional>
8
9// ─── undef Qt's emit if it was defined before this header ────────────────────
10// Qt defines `#define emit` (empty). asmjit (pulled in by polyhook) uses
11// `emit` as a member-function identifier. If the macro is still live when
12// asmjit is parsed, `Error emit(InstId instId)` becomes `Error (InstId instId)`
13// and MSVC fails with "missing ')' before identifier 'instId'".
14// We undef here defensively — this header does not depend on Qt and does not
15// restore the macro. TUs that need Qt signal/slot syntax should include Qt
16// headers after this file.
17#ifdef emit
18# undef emit
19#endif
20
21#include "types.h"
22#include "Logging.h"
23#include "BaseHook.h"
24#include "HookHandle.h"
25#include "HooksLibraryExport.h"
26
27// =============================================================================
28// HooksManager
29// Manages x64 detour hooks on arbitrary free and member functions.
30//
31// Usage:
32// HooksManager::addBefore<&QWidget::show>([](HookHandle h, QWidget*& self) {
33// LOG_INFO("QWidget::show called on {}", self->objectName());
34// });
35// =============================================================================
37 static constexpr auto LOGGER_NAME_ = "Hooks Manager";
38 // =========================================================================
39 // HookBase<R, Original, CallArgs...>
40 //
41 // All callback data lives in a heap-allocated instance created when the
42 // first callback is registered and destroyed when the last is removed.
43 // This ensures no static storage lingers after a hook is torn down.
44 //
45 // Only two members remain static:
46 // original — the trampoline pointer filled in by polyhook
47 // _instance — the bridge between the static hook() entry point and the
48 // live instance; set by Hook<> on creation, nulled on deletion
49 //
50 // @tparam R Return type of the hooked function
51 // @tparam Original Type of the trampoline (original function pointer)
52 // @tparam CallArgs Full argument list (first arg is Class* for members)
53 // =========================================================================
54 template<typename R, typename Original, typename... CallArgs>
55 struct HookBase {
56 // ── Callback types ────────────────────────────────────────────────────
57 // Before callbacks receive all call arguments by reference so they can
58 // inspect or modify them before the original function runs.
59 using Before = std::function<void(HookHandle, CallArgs &...)>;
60
61 // After callbacks additionally receive the return value (if non-void)
62 // so they can inspect or replace it. AfterType is used instead of
63 // std::conditional_t to avoid eagerly instantiating void& (ill-formed).
64 using After = AfterType<R, CallArgs...>::type;
65
66 // Replace callbacks fully substitute for the original call: they get
67 // the (mutable) call arguments and must produce the return value.
68 using Replace = ReplaceType<R, CallArgs...>::type;
69
70 // IgnoreConditionally callbacks decide, per call, whether the original
71 // should run — like Before, plus a leading bool& the callback sets.
72 using IgnoreConditionally = IgnoreConditionallyType<R, CallArgs...>::type;
73
74 // ── Instance data — allocated on first use, freed on last removal ─────
75 std::list<Before> _before;
76 std::list<After> _after;
77 std::optional<Replace> _replace; // only one Replace per function
78 std::optional<IgnoreConditionally> _ignoreConditionally; // only one per function
79 bool _ignoreActive = false; // only one Ignore per function (void only)
80 std::list<std::function<void()> > _executeLater;
81 void* _address = nullptr;
82
83 void addBefore(Before cb, std::optional<size_t> position = std::nullopt) {
84 insertAt(_before, std::move(cb), position);
85 }
86 void addAfter(After cb, std::optional<size_t> position = std::nullopt) {
87 insertAt(_after, std::move(cb), position);
88 }
89 void removeBeforeAt(size_t position) {
90 eraseAt(_before, position);
91 }
92 void removeAfterAt(size_t position) {
93 eraseAt(_after, position);
94 }
95 void setReplace(Replace cb) {
96 _replace = std::move(cb);
97 }
98 void clearReplace() {
99 _replace.reset();
100 }
101 void setIgnoreConditionally(IgnoreConditionally cb) {
102 _ignoreConditionally = std::move(cb);
103 }
104 void clearIgnoreConditionally() {
105 _ignoreConditionally.reset();
106 }
107
108 // True once no Before/After/Replace/Ignore/IgnoreConditionally
109 // registration remains — the signal that the detour can be torn down.
110 bool isEmpty() const {
111 return _before.empty() && _after.empty() && !_replace.has_value()
112 && !_ignoreActive && !_ignoreConditionally.has_value();
113 }
114
115 // ── iterate ───────────────────────────────────────────────────────────
116 // Walks `list` and invokes each callback with a HookHandle that, when
117 // remove() is called, schedules erasure via _executeLater. Erasure
118 // itself only happens later, when a flush loop runs the queued
119 // closure (see hook()'s tail) — never synchronously from here, since
120 // that could invalidate the very list this loop is iterating.
121 // std::list is used deliberately: erasing by iterator is O(1) and
122 // does not invalidate any other iterators.
123 template<typename T, typename... Args>
124 void iterate(std::list<T> &list, Args &... args) {
125 for (auto it = list.begin(); it != list.end(); ++it) {
126 (*it)(HookHandle(_executeLater, [this, &list, it] {
127 LOG_DEBUG_TO(LOGGER_NAME_, "Removing callback for hook: {}", getName(_address));
128 list.erase(it);
129 }), args...);
130 }
131 }
132
133 // ── hook ──────────────────────────────────────────────────────────────
134 // Static entry point installed as the detour target — polyhook requires
135 // a plain function pointer, so this cannot be a member function.
136 // It accesses all data through _instance, which is guaranteed non-null
137 // on entry (install() is only called after _instance is set).
138 //
139 // Deferred removals (from handle.remove(), called by any Before/
140 // Replace/IgnoreConditionally/After callback in THIS call) are
141 // flushed at the very end — after Before, Replace/Ignore, and After
142 // have all run — rather than at entry. This is the one safe moment
143 // to also tear the detour down if that flush left nothing registered:
144 // nothing after this point touches `inst` or the trampoline again, so
145 // unHook() invalidating them is harmless. Doing this any earlier (or
146 // synchronously mid-iteration) risks calling through an already-
147 // unhooked/freed trampoline later in the same call — this is exactly
148 // what caused a real crash during development; see git history/PR
149 // discussion for HooksManagerTest's *_RemoveViaHandle tests.
150 //
151 // Replace takes priority over the original call when set (it fully
152 // substitutes for it). Ignore (void functions only) simply skips the
153 // call. Both are mutually exclusive in practice, but if a user sets
154 // both, Replace wins since it is the more explicit instruction.
155 static R hook(CallArgs... args) {
156 auto *inst = _instance;
157 inst->iterate(inst->_before, args...);
158
159 auto replaceHandle = [inst] {
160 return HookHandle(inst->_executeLater, [inst] {
161 inst->_replace.reset();
162 });
163 };
164 auto ignoreCondHandle = [inst] {
165 return HookHandle(inst->_executeLater, [inst] {
166 inst->_ignoreConditionally.reset();
167 });
168 };
169
170 if constexpr (std::is_void_v<R>) {
171 if (inst->_replace) {
172 (*inst->_replace)(replaceHandle(), args...);
173 } else {
174 bool ignore = inst->_ignoreActive;
175 if (inst->_ignoreConditionally) {
176 (*inst->_ignoreConditionally)(ignoreCondHandle(), ignore, args...);
177 }
178 if (!ignore) original(args...);
179 }
180 inst->iterate(inst->_after, args...);
181 finalize(inst);
182 } else if constexpr (std::is_reference_v<R>) {
183 // Reference return: a reference variable cannot be
184 // default-initialized, so use a pointer for result storage.
185 std::remove_reference_t<R>* result_ptr = nullptr;
186 if (inst->_replace) {
187 result_ptr = &(*inst->_replace)(replaceHandle(), args...);
188 } else {
189 bool ignore = inst->_ignoreActive;
190 if (inst->_ignoreConditionally) {
191 (*inst->_ignoreConditionally)(ignoreCondHandle(), ignore, result_ptr, args...);
192 }
193 if (!ignore) result_ptr = &original(args...);
194 }
195 if (result_ptr) inst->iterate(inst->_after, *result_ptr, args...);
196 finalize(inst);
197 return *result_ptr; // NOLINT: caller must ensure result_ptr is non-null
198 } else {
199 R result{};
200 if (inst->_replace) {
201 result = (*inst->_replace)(replaceHandle(), args...);
202 } else {
203 bool ignore = inst->_ignoreActive;
204 if (inst->_ignoreConditionally) {
205 (*inst->_ignoreConditionally)(ignoreCondHandle(), ignore, result, args...);
206 }
207 if (!ignore) result = original(args...);
208 }
209 inst->iterate(inst->_after, result, args...);
210 finalize(inst);
211 return result;
212 }
213 }
214
215 // ── finalize ──────────────────────────────────────────────────────────
216 // Shared tail for every hook() variant in this file (this one and the
217 // four hand-rolled hidden-pointer ones below): flush deferred
218 // removals now that every callback for this call has had its chance
219 // to self-remove, then tear the detour down if nothing is left
220 // registered. Must be the LAST thing done with `inst` in the calling
221 // hook() — see hook()'s comment above for why the timing matters.
222 static void finalize(HookBase *inst) {
223 std::list<std::function<void()> > pending;
224 std::swap(inst->_executeLater, pending);
225 for (auto &f: pending) f();
226 if (inst->isEmpty()) remove(inst->_address);
227 }
228
229 // ── Static members ────────────────────────────────────────────────────
230 // Trampoline to the original function, filled in by polyhook.
231 static inline Original original = nullptr;
232
233 // Bridge from the static hook() entry point to the live instance.
234 // Owning pointer: Hook<Target> creates and destroys it.
235 static inline HookBase *_instance = nullptr;
236 };
237
238 // ── HookTraits forward declaration ────────────────────────────────────────
239 // Specialised below for free functions and member functions.
240 template<auto Function>
241 struct HookTraits;
242 // ── Hook<Target> ──────────────────────────────────────────────────────────
243 // Concrete hook stored in the map. Bridges the type-erased BaseHook
244 // with the typed HookTraits so addBefore/addAfter/addReplace/addIgnore
245 // remain type-safe.
246 //
247 // Owns the HookBase instance: creates it in the constructor, deletes it
248 // (and clears _instance) in the destructor. After destruction, all callback
249 // lists and associated memory are freed.
250 //
251 // Note: template constraint uses `requires` clause — MSVC C7600 rejects
252 // the shorthand `template<HookableFunction auto Target>` for non-type params.
253 template<auto Target> requires HookableFunction<Target>
254 struct Hook : BaseHook {
255 explicit Hook(void *address)
256 : BaseHook(
257 HookTraits<Target>::address(),
258 reinterpret_cast<uint64_t>(&HookTraits<Target>::hook),
259 reinterpret_cast<uint64_t *>(&HookTraits<Target>::original)) {
260 // Allocate and wire up the instance BEFORE installing the detour.
261 // If install() came first, the hooked function could fire between
262 // _detour.hook() and the _instance assignment, hitting a null pointer.
263 auto *inst = new HookTraits<Target>();
264 inst->_address = address;
265 HookTraits<Target>::_instance = inst;
266
267 if (!install()) {
268 // Roll back: the detour never took effect (see install()'s
269 // comment for why), so the instance/address are pointing at
270 // an inert registration nothing will ever call into. Leaving
271 // it in place would make every addBefore/addAfter/etc. call
272 // silently do nothing forever — fail loudly instead so the
273 // caller finds out immediately, at the call site, rather
274 // than debugging "why doesn't my hook fire".
275 delete inst;
276 HookTraits<Target>::_instance = nullptr;
277 throw std::runtime_error(
278 "HooksManager: failed to install detour — target function's "
279 "compiled prologue is too short to hook (this can happen with "
280 "trivial one-line functions under aggressive optimization)");
281 }
282 }
283
284 ~Hook() override {
285 // BaseHook's destructor calls unHook() first, guaranteeing the
286 // detour is removed before we free the instance. No in-flight hook()
287 // call can be using _instance after unHook() returns.
288 //
289 // Cast to the concrete type so the correct destructor is called
290 // without requiring a virtual destructor on HookBase.
291 delete static_cast<HookTraits<Target> *>(HookTraits<Target>::_instance);
292 HookTraits<Target>::_instance = nullptr;
293 }
294
295 void addBefore(HookTraits<Target>::Before cb, std::optional<size_t> position = std::nullopt) {
296 HookTraits<Target>::_instance->addBefore(std::move(cb), position);
297 }
298
299 void addAfter(HookTraits<Target>::After cb, std::optional<size_t> position = std::nullopt) {
300 HookTraits<Target>::_instance->addAfter(std::move(cb), position);
301 }
302
303 void removeBeforeAt(size_t position) {
304 HookTraits<Target>::_instance->removeBeforeAt(position);
305 }
306
307 void removeAfterAt(size_t position) {
308 HookTraits<Target>::_instance->removeAfterAt(position);
309 }
310
311 void setReplace(HookTraits<Target>::Replace cb) {
312 HookTraits<Target>::_instance->setReplace(std::move(cb));
313 }
314
315 void clearReplace() {
316 HookTraits<Target>::_instance->clearReplace();
317 }
318
319 void setIgnoreConditionally(HookTraits<Target>::IgnoreConditionally cb) {
320 HookTraits<Target>::_instance->setIgnoreConditionally(std::move(cb));
321 }
322
323 void clearIgnoreConditionally() {
324 HookTraits<Target>::_instance->clearIgnoreConditionally();
325 }
326
327 void setIgnoreActive(bool active) {
328 HookTraits<Target>::_instance->_ignoreActive = active;
329 }
330
331 bool empty() const {
332 return HookTraits<Target>::_instance->isEmpty();
333 }
334 };
335
336 // =========================================================================
337 // HookTraits — free function specialization
338 // @tparam R Return type
339 // @tparam Args Argument types
340 // @tparam Function Pointer to the free function to hook
341 // =========================================================================
342 template<typename R, typename... Args, R(*Function)(Args...)>
343 struct HookTraits<Function> : HookBase<R, R(*)(Args...), Args...> {
344 static uint64_t address() {
345 return reinterpret_cast<uint64_t>(Function);
346 }
347 };
348
349 // =========================================================================
350 // HookTraits — non-const member function specialization
351 // @tparam R Return type
352 // @tparam Class Class that owns the member function
353 // @tparam Args Argument types (excluding implicit this)
354 // @tparam Function Pointer to the member function to hook
355 //
356 // Handles trivial and void R (no hidden return pointer involved — the
357 // compiler returns the value in a register either way, whether R is
358 // declared here as a real by-value return or the real member function's
359 // own return; both compile identically). Non-trivial, non-void R needs
360 // its own specialization below — see the comment there for why.
361 // =========================================================================
362 template<typename R, typename Class, typename... Args, R(Class::*Function)(Args...)>
363 requires (std::is_trivial_v<R> || std::is_void_v<R>)
364 struct HookTraits<Function> : HookBase<R, R(*)(Class *, Args...), Class *, Args...> {
365 static uint64_t address() {
366 // Reinterpret a member function pointer as a raw address.
367 // A union is used because member pointers cannot be cast via
368 // reinterpret_cast directly — this is the standard workaround
369 // on MSVC/GCC/Clang for x64 single-inheritance vtable layouts.
370 union {
371 R (Class::*mfp)(Args...);
372 uint64_t addr;
373 } u;
374 u.mfp = Function;
375 return u.addr;
376 }
377 };
378
379 // =========================================================================
380 // HookTraits — non-const member function, NON-TRIVIAL non-void return
381 //
382 // Why this needs its own specialization instead of just declaring
383 // hook()/original with plain by-value return type R (as the trivial/void
384 // branch above does): R being non-trivial means the real, compiled
385 // Class::Function *itself* uses a hidden caller-allocated return pointer
386 // under the hood — but WHERE that pointer sits in the parameter list is
387 // a property of the *member function* calling convention specifically:
388 // • MSVC x64: this, hidden-return-pointer, then explicit args
389 // • Itanium ABI: hidden-return-pointer, this, then explicit args
390 // hook()/original, as declared here, are ordinary *free* functions that
391 // happen to take an explicit Class* parameter to model `this` — the
392 // compiler has no idea that parameter is meant to be `this`, so it
393 // applies the *free function* rule when deciding where to insert a
394 // hidden pointer (strictly first, before every explicit parameter,
395 // including the one modeling `this`) — which disagrees with where the
396 // real member function actually put it. Trivial/void R never triggers
397 // this because there's no hidden pointer to place either way, so both
398 // rules coincide and the plain by-value branch above works unmodified.
399 //
400 // The fix: declare the hidden pointer *explicitly*, by hand, in the
401 // platform-correct position, rather than relying on the compiler to
402 // insert one implicitly for a by-value R return. CallArgs (used to
403 // derive Before/After/Replace/IgnoreConditionally) deliberately excludes
404 // the pointer — those callback types stay the same plain-R-by-value
405 // shape used everywhere else in this file; only the low-level
406 // hook()/original signatures need to know about it.
407 // =========================================================================
408#if defined(_MSC_VER)
409 template<typename R, typename Class, typename... Args, R(Class::*Function)(Args...)>
410 requires (!std::is_trivial_v<R> && !std::is_void_v<R>)
411 struct HookTraits<Function> : HookBase<R, void(*)(Class *, R *, Args...), Class *, Args...> {
412 using Base = HookBase<R, void(*)(Class *, R *, Args...), Class *, Args...>;
413
414 static uint64_t address() {
415 union {
416 R (Class::*mfp)(Args...);
417 uint64_t addr;
418 } u;
419 u.mfp = Function;
420 return u.addr;
421 }
422
423 // See the class-level comment above for why this can't just be a
424 // plain by-value-returning hook(): `ret` is placed explicitly, by
425 // hand, in the position MSVC's x64 ABI actually uses for a member
426 // function (this, ret, args...) — it is NOT the compiler-inferred
427 // hidden pointer for a by-value return, which would land elsewhere.
428 //
429 // `*ret` is exactly ONE of: constructed by original() (normal path),
430 // or placement-constructed here from a Replace/IgnoreConditionally
431 // result (skip path) — never both, never neither.
432 static R* hook(Class* thiz, R* ret, Args... args) {
433
434 auto* inst = Base::_instance;
435 inst->iterate(inst->_before, thiz, args...);
436
437 if (inst->_replace) {
438 new (ret) R((*inst->_replace)(HookHandle(inst->_executeLater, [inst] {
439 inst->_replace.reset();
440 }), thiz, args...));
441 } else {
442 bool ignore = inst->_ignoreActive; // always false here (Ignore is void-only)
443 R localResult{}; // safe placeholder — NOT *ret
444 if (inst->_ignoreConditionally) {
445 (*inst->_ignoreConditionally)(HookHandle(inst->_executeLater, [inst] {
446 inst->_ignoreConditionally.reset();
447 }), ignore, localResult, thiz, args...);
448 }
449 if (!ignore) {
450 Base::original(thiz, ret, args...); // constructs *ret itself
451 } else {
452 new (ret) R(std::move(localResult)); // *ret still raw: construct from the substitute
453 }
454 }
455
456 if (Base::_instance) inst->iterate(inst->_after, *ret, thiz, args...);
457
458 Base::finalize(inst);
459
460 return ret;
461 }
462 };
463#else
464 template<typename R, typename Class, typename... Args, R(Class::*Function)(Args...)>
465 requires (!std::is_trivial_v<R> && !std::is_void_v<R>)
466 struct HookTraits<Function> : HookBase<R, void(*)(R *, Class *, Args...), Class *, Args...> {
467 using Base = HookBase<R, void(*)(R *, Class *, Args...), Class *, Args...>;
468
469 static uint64_t address() {
470 union {
471 R (Class::*mfp)(Args...);
472 uint64_t addr;
473 } u;
474 u.mfp = Function;
475 return u.addr;
476 }
477
478 // Itanium ABI order: hidden return pointer first, then `this`. See
479 // the class-level comment above for the full explanation.
480 static R* hook(R* ret, Class* thiz, Args... args) {
481
482 auto* inst = Base::_instance;
483 inst->iterate(inst->_before, thiz, args...);
484
485 if (inst->_replace) {
486 new (ret) R((*inst->_replace)(HookHandle(inst->_executeLater, [inst] {
487 inst->_replace.reset();
488 }), thiz, args...));
489 } else {
490 bool ignore = inst->_ignoreActive;
491 R localResult{};
492 if (inst->_ignoreConditionally) {
493 (*inst->_ignoreConditionally)(HookHandle(inst->_executeLater, [inst] {
494 inst->_ignoreConditionally.reset();
495 }), ignore, localResult, thiz, args...);
496 }
497 if (!ignore) {
498 Base::original(ret, thiz, args...);
499 } else {
500 new (ret) R(std::move(localResult));
501 }
502 }
503
504 if (Base::_instance) inst->iterate(inst->_after, *ret, thiz, args...);
505
506 Base::finalize(inst);
507
508 return ret;
509 }
510 };
511#endif
512
513 // =========================================================================
514 // HookTraits — const member function specialization
515 // Mirrors the non-const variant above, including the trivial/void vs
516 // non-trivial split and why it's needed — see the comment above the
517 // non-const non-trivial specialization for the full explanation. The
518 // implicit `this` pointer becomes `const Class*` throughout.
519 //
520 // Without this specialization any addBefore<&Foo::constMethod> call
521 // silently falls through to the undefined primary template and fails to
522 // compile. Examples: QSettings::value, QSettings::contains, QVariant::toString.
523 // =========================================================================
524 template<typename R, typename Class, typename... Args, R(Class::*Function)(Args...) const>
525 requires (std::is_trivial_v<R> || std::is_void_v<R>)
526 struct HookTraits<Function> : HookBase<R, R(*)(const Class *, Args...), const Class *, Args...> {
527 static uint64_t address() {
528 union {
529 R (Class::*mfp)(Args...) const;
530 uint64_t addr;
531 } u;
532 u.mfp = Function;
533 return u.addr;
534 }
535 };
536
537#if defined(_MSC_VER)
538 template<typename R, typename Class, typename... Args, R(Class::*Function)(Args...) const>
539 requires (!std::is_trivial_v<R> && !std::is_void_v<R>)
540 struct HookTraits<Function> : HookBase<R, void(*)(const Class *, R *, Args...), const Class *, Args...> {
541 using Base = HookBase<R, void(*)(const Class *, R *, Args...), const Class *, Args...>;
542
543 static uint64_t address() {
544 union {
545 R (Class::*mfp)(Args...) const;
546 uint64_t addr;
547 } u;
548 u.mfp = Function;
549 return u.addr;
550 }
551
552 static R* hook(const Class* thiz, R* ret, Args... args) {
553 auto* inst = Base::_instance;
554 inst->iterate(inst->_before, thiz, args...);
555
556 if (inst->_replace) {
557 new (ret) R((*inst->_replace)(HookHandle(inst->_executeLater, [inst] {
558 inst->_replace.reset();
559 }), thiz, args...));
560 } else {
561 bool ignore = inst->_ignoreActive;
562 R localResult{};
563 if (inst->_ignoreConditionally) {
564 (*inst->_ignoreConditionally)(HookHandle(inst->_executeLater, [inst] {
565 inst->_ignoreConditionally.reset();
566 }), ignore, localResult, thiz, args...);
567 }
568 if (!ignore) {
569 Base::original(thiz, ret, args...);
570 } else {
571 new (ret) R(std::move(localResult));
572 }
573 }
574
575 if (Base::_instance) inst->iterate(inst->_after, *ret, thiz, args...);
576
577 Base::finalize(inst);
578
579 return ret;
580 }
581 };
582#else
583 template<typename R, typename Class, typename... Args, R(Class::*Function)(Args...) const>
584 requires (!std::is_trivial_v<R> && !std::is_void_v<R>)
585 struct HookTraits<Function> : HookBase<R, void(*)(R *, const Class *, Args...), const Class *, Args...> {
586 using Base = HookBase<R, void(*)(R *, const Class *, Args...), const Class *, Args...>;
587
588 static uint64_t address() {
589 union {
590 R (Class::*mfp)(Args...) const;
591 uint64_t addr;
592 } u;
593 u.mfp = Function;
594 return u.addr;
595 }
596
597 static R* hook(R* ret, const Class* thiz, Args... args) {
598
599 auto* inst = Base::_instance;
600 inst->iterate(inst->_before, thiz, args...);
601
602 if (inst->_replace) {
603 new (ret) R((*inst->_replace)(HookHandle(inst->_executeLater, [inst] {
604 inst->_replace.reset();
605 }), thiz, args...));
606 } else {
607 bool ignore = inst->_ignoreActive;
608 R localResult{};
609 if (inst->_ignoreConditionally) {
610 (*inst->_ignoreConditionally)(HookHandle(inst->_executeLater, [inst] {
611 inst->_ignoreConditionally.reset();
612 }), ignore, localResult, thiz, args...);
613 }
614 if (!ignore) {
615 Base::original(ret, thiz, args...);
616 } else {
617 new (ret) R(std::move(localResult));
618 }
619 }
620
621 if (Base::_instance) inst->iterate(inst->_after, *ret, thiz, args...);
622
623 Base::finalize(inst);
624
625 return ret;
626 }
627 };
628#endif
629
630 // ── list helpers — positional insert/erase ────────────────────────────────
631 // Shared by Before/After lists so "insert at any place" / "remove via
632 // position" behave identically for both callback kinds.
633 template<typename T>
634 static void insertAt(std::list<T> &list, T item, std::optional<size_t> position) {
635 if (!position || *position >= list.size()) {
636 list.push_back(std::move(item));
637 } else {
638 auto it = list.begin();
639 std::advance(it, static_cast<long>(*position));
640 list.insert(it, std::move(item));
641 }
642 }
643
644 template<typename T>
645 static void eraseAt(std::list<T> &list, size_t position) {
646 if (position >= list.size()) return;
647 auto it = list.begin();
648 std::advance(it, static_cast<long>(position));
649 list.erase(it);
650 }
651 // ── getHook ───────────────────────────────────────────────────────────────
652 // Returns the existing Hook for F, or creates and registers a new one.
653 template<auto F> requires HookableFunction<F>
654 static Hook<F> *getHook() {
655 // Extract the opaque address from the function pointer.
656 union {
657 decltype(F) p;
658 void *addr;
659 } u;
660 u.p = F;
661
662 const auto it = _hooks.find(u.addr);
663 if (it == _hooks.end()) {
664 auto *hook = new Hook<F>(u.addr);
665 _hooks[u.addr] = hook;
666 LOG_INFO_TO(LOGGER_NAME_, "Hook has been created: {}", getName(u.addr));
667 return hook;
668 }
669
670 return dynamic_cast<Hook<F>*>(it->second);
671 }
672
673 // ── findHook ──────────────────────────────────────────────────────────────
674 // Like getHook, but never creates one. Used by the position/remove-style
675 // APIs, which are no-ops on a function that was never hooked.
676 template<auto F> requires HookableFunction<F>
677 static Hook<F> *findHook() {
678 union {
679 decltype(F) p;
680 void *addr;
681 } u;
682 u.p = F;
683
684 const auto it = _hooks.find(u.addr);
685 if (it == _hooks.end()) return nullptr;
686 return dynamic_cast<Hook<F>*>(it->second);
687 }
688
689 // ── tearDownIfEmpty ───────────────────────────────────────────────────────
690 // After an external (non-handle) removal, detach the detour entirely once
691 // no Before/After/Replace/Ignore registration remains — same policy as
692 // the deferred, handle-based removal path in HookBase::finalize(), which
693 // runs this same check at the end of every hook() call.
694 template<auto F>
695 static void tearDownIfEmpty(Hook<F> *hook) {
696 if (hook->empty()) {
697 union {
698 decltype(F) p;
699 void *addr;
700 } u;
701 u.p = F;
702 remove(u.addr);
703 }
704 }
705
706 // ── remove ────────────────────────────────────────────────────────────────
707 // Unregisters and destroys the Hook for `address`. The Hook destructor
708 // unregisters the detour and deletes the HookBase instance, freeing all
709 // callback lists. Called automatically when the last callback is removed.
710 static void remove(void* address);
711
712 // ── getName ───────────────────────────────────────────────────────────────
713 // Extracts a human-readable "Class::method" label from the RTTI type name
714 // of the hook stored at `address`.
715 static std::string getName(void* address);
716
717 // ── _hooks ────────────────────────────────────────────────────────────────
718 // Global map from opaque function address to its live BaseHook.
719 // std::unordered_map gives O(1) average lookup.
720 static inline std::unordered_map<void*, BaseHook*> _hooks;
721
722public:
723 // ── addBefore ─────────────────────────────────────────────────────────────
724 // Register a callback to run before function F. Multiple callbacks may be
725 // registered per function; by default the callback is appended, but an
726 // explicit `position` inserts it at that index (0 = front) instead.
727 // Creates the hook automatically on first registration.
728 //
729 // Compile-time guarantees:
730 // • F must be a free or member function pointer (HookableFunction)
731 // • Callback signature must match (HookHandle, Args&...) (BeforeCallbackFor)
732 template<auto F, typename Callback> requires BeforeCallbackFor<Callback, F>
733 static void addBefore(Callback &&callback, std::optional<size_t> position = std::nullopt) {
734 getHook<F>()->addBefore(std::forward<Callback>(callback), position);
735 }
736
737 // ── removeBefore ──────────────────────────────────────────────────────────
738 // Remove the Before callback at `position` (0-based) without needing a
739 // HookHandle. No-op if F isn't hooked or position is out of range.
740 template<auto F> requires HookableFunction<F>
741 static void removeBefore(size_t position) {
742 if (auto *hook = findHook<F>()) {
743 hook->removeBeforeAt(position);
744 tearDownIfEmpty<F>(hook);
745 }
746 }
747
748 // ── addAfter ──────────────────────────────────────────────────────────────
749 // Register a callback to run after function F. Multiple callbacks may be
750 // registered per function; by default the callback is appended, but an
751 // explicit `position` inserts it at that index (0 = front) instead.
752 // Creates the hook automatically on first registration.
753 //
754 // Compile-time guarantees:
755 // • F must be a free or member function pointer (HookableFunction)
756 // • Callback signature must match (HookHandle[, R&], Args&...) (AfterCallbackFor)
757 template<auto F, typename Callback> requires AfterCallbackFor<Callback, F>
758 static void addAfter(Callback &&callback, std::optional<size_t> position = std::nullopt) {
759 getHook<F>()->addAfter(std::forward<Callback>(callback), position);
760 }
761
762 // ── removeAfter ───────────────────────────────────────────────────────────
763 // Remove the After callback at `position` (0-based) without needing a
764 // HookHandle. No-op if F isn't hooked or position is out of range.
765 template<auto F> requires HookableFunction<F>
766 static void removeAfter(size_t position) {
767 if (auto *hook = findHook<F>()) {
768 hook->removeAfterAt(position);
769 tearDownIfEmpty<F>(hook);
770 }
771 }
772
773 // ── addReplace ────────────────────────────────────────────────────────────
774 // Register the (single) callback that completely replaces function F: the
775 // original body is never invoked, and the callback itself must produce
776 // the return value. Registering again simply overwrites the previous
777 // replacement, since only one Replace can exist per function. The
778 // callback receives a HookHandle exactly like Before/After, so it can
779 // remove itself the same way (handle.remove() inside the callback).
780 //
781 // Compile-time guarantees:
782 // • F must be a free or member function pointer (HookableFunction)
783 // • Callback signature must match (HookHandle, Args&...) -> R (ReplaceCallbackFor)
784 template<auto F, typename Callback> requires ReplaceCallbackFor<Callback, F>
785 static void addReplace(Callback &&callback) {
786 getHook<F>()->setReplace(std::forward<Callback>(callback));
787 }
788
789 // ── removeReplace ─────────────────────────────────────────────────────────
790 // Remove the Replace callback for F (if any) without needing its
791 // HookHandle. No-op if F isn't hooked or has no Replace registered.
792 template<auto F> requires HookableFunction<F>
793 static void removeReplace() {
794 if (auto *hook = findHook<F>()) {
795 hook->clearReplace();
796 tearDownIfEmpty<F>(hook);
797 }
798 }
799
800 // ── addIgnoreConditionally ────────────────────────────────────────────────
801 // Register the (single) callback that decides, per call, whether the
802 // original body of F should run — like Before, but with a leading bool&
803 // the callback sets to skip the call. For non-void F it also receives a
804 // mutable result reference to supply a substitute value when it chooses
805 // to skip. This is the direct successor to the old callback-based Ignore
806 // hook, now capped at one registration per function. Registering again
807 // simply overwrites the previous callback.
808 //
809 // Compile-time guarantees:
810 // • F must be a free or member function pointer (HookableFunction)
811 // • Callback signature must match (HookHandle, bool&[, R&], Args&...) (IgnoreConditionallyCallbackFor)
812 template<auto F, typename Callback> requires IgnoreConditionallyCallbackFor<Callback, F>
813 static void addIgnoreConditionally(Callback &&callback) {
814 getHook<F>()->setIgnoreConditionally(std::forward<Callback>(callback));
815 }
816
817 // ── removeIgnoreConditionally ─────────────────────────────────────────────
818 // Remove the IgnoreConditionally callback for F (if any) without needing
819 // its HookHandle. No-op if F isn't hooked or has none registered.
820 template<auto F> requires HookableFunction<F>
822 if (auto *hook = findHook<F>()) {
823 hook->clearIgnoreConditionally();
824 tearDownIfEmpty<F>(hook);
825 }
826 }
827
828 // ── addIgnore ─────────────────────────────────────────────────────────────
829 // Activate Ignore for function F: the original body stops being called
830 // (After callbacks, if any, still run). Only one Ignore state exists per
831 // function — it is a toggle, not a callback list — and it takes no
832 // callback argument because there is nothing for it to do besides skip
833 // the call. Restricted to void-returning functions (VoidHookableFunction):
834 // a non-void function has no sensible result to produce when skipped.
835 // Safe to call again after removeIgnore() to reactivate.
836 template<auto F> requires VoidHookableFunction<F>
837 static void addIgnore() {
838 getHook<F>()->setIgnoreActive(true);
839 }
840
841 // ── removeIgnore ──────────────────────────────────────────────────────────
842 // Deactivate Ignore for function F, letting the original body run again.
843 // No-op if F isn't hooked or Ignore was never activated.
844 template<auto F> requires VoidHookableFunction<F>
845 static void removeIgnore() {
846 if (auto *hook = findHook<F>()) {
847 hook->setIgnoreActive(false);
848 tearDownIfEmpty<F>(hook);
849 }
850 }
851};
852
853#endif // CHRYSALIS_HOOKMANAGER_H
#define HOOKS
Definition HooksLibraryExport.h:11
Definition BaseHook.h:15
Definition HookHandle.h:19
Definition HooksManager.h:36
static void removeAfter(size_t position)
Definition HooksManager.h:766
static void removeReplace()
Definition HooksManager.h:793
static void removeIgnore()
Definition HooksManager.h:845
static void removeBefore(size_t position)
Definition HooksManager.h:741
static void addIgnore()
Definition HooksManager.h:837
static void addReplace(Callback &&callback)
Definition HooksManager.h:785
static void addAfter(Callback &&callback, std::optional< size_t > position=std::nullopt)
Definition HooksManager.h:758
static void removeIgnoreConditionally()
Definition HooksManager.h:821
static void addBefore(Callback &&callback, std::optional< size_t > position=std::nullopt)
Definition HooksManager.h:733
static void addIgnoreConditionally(Callback &&callback)
Definition HooksManager.h:813
Definition types.h:159
Definition types.h:153
Definition types.h:139
Definition types.h:173
Definition types.h:166
Definition types.h:147
Definition types.h:22
Definition types.h:51
Definition types.h:39