CommonLib
Loading...
Searching...
No Matches
hook_win32.h
Go to the documentation of this file.
1#pragma once
2
3#include <functional>
4
5#include "../thirdparty/Detours/src/detours.h"
6#include "host/common.h"
7#include "mem/mem.h"
8#include "ut/os/preprocessor_win32.h"
9
10#define __CMNLIB_INTERNAL_STATIC_LIB_ENROLMENT
11
12#define __CMNLIB_INTERNAL_STATIC_HOOK_IMPL(NAME, ADDRESS, INSTALLER)
13 static bool g_result_##NAME{};
14 static bool install_##NAME()
15 {
16 if (!ADDRESS)
17 return g_result_##NAME = false;
18
19 return g_result_##NAME = INSTALLER(NAME);
20 }
21 static bool g_runner_##NAME = install_##NAME();
22
23///
24/// \class __CMNLIB_INTERNAL_HOOK_COMMON_PARAMS
25///
26/// \param RETURN_TYPE The return type of the function.
27/// \param CALLING_CONVENTION The calling convention of the function (e.g.
28/// `__cdecl`, `__stdcall`, `__fastcall`, etc).
29/// For custom calling conventions on x86, such as
30/// `__usercall` or `__userpurge`, see
31/// \ref hook_usercall_win32.h.
32/// \param FUNCTION_NAME The name of the function.
33/// \param ADDRESS The address of the function.
34/// \param __VA_ARGS__ The parameters of the function.
35///
36
37///
38/// Declares a pointer to a function in memory.
39///
40/// \copydoc __CMNLIB_INTERNAL_HOOK_COMMON_PARAMS
41///
42#define FUNCTION_PTR(RETURN_TYPE, CALLING_CONVENTION, FUNCTION_NAME, ADDRESS, ...)
43 RETURN_TYPE (CALLING_CONVENTION* FUNCTION_NAME)(__VA_ARGS__) = (RETURN_TYPE (CALLING_CONVENTION*)(__VA_ARGS__))(ADDRESS)
44
45///
46/// Loads a dynamic link library into memory and declares a pointer to an exported
47/// function from it.
48///
49/// \param RETURN_TYPE The return type of the function.
50/// \param LIBRARY_NAME The name of the dynamic link library.
51/// \param FUNCTION_NAME The name of the exported function.
52/// \param __VA_ARGS__ The parameters of the exported function.
53///
54#define DLL_IMPORT_FUNCTION_PTR(RETURN_TYPE, LIBRARY_NAME, FUNCTION_NAME, ...)
55 typedef RETURN_TYPE FUNCTION_NAME##_t(__VA_ARGS__);
56 FUNCTION_NAME##_t* FUNCTION_NAME = reinterpret_cast<FUNCTION_NAME##_t*>(DLL_IMPORT_EXPLICIT(LIBRARY_NAME, #FUNCTION_NAME));
57
58///
59/// Defines the body of a hook for a function in memory.
60///
61/// \copydoc __CMNLIB_INTERNAL_HOOK_COMMON_PARAMS
62///
63#define HOOK(RETURN_TYPE, CALLING_CONVENTION, FUNCTION_NAME, ADDRESS, ...)
64 void* g_address_##FUNCTION_NAME = reinterpret_cast<void*>(ADDRESS);
65 void* g_address_post_hook_##FUNCTION_NAME{};
66 typedef RETURN_TYPE CALLING_CONVENTION FUNCTION_NAME(__VA_ARGS__);
67 FUNCTION_NAME* original_##FUNCTION_NAME = reinterpret_cast<FUNCTION_NAME*>(g_address_##FUNCTION_NAME);
68 RETURN_TYPE CALLING_CONVENTION impl_##FUNCTION_NAME(__VA_ARGS__)
69
70///
71/// Defines the body of a hook for a function in memory, and installs it
72/// upon initialisation.
73///
74/// \copydoc __CMNLIB_INTERNAL_HOOK_COMMON_PARAMS
75///
76/// \returns Use \ref GET_STATIC_HOOK_RESULT.
77///
78#define STATIC_HOOK(RETURN_TYPE, CALLING_CONVENTION, FUNCTION_NAME, ADDRESS, ...)
79 HOOK(RETURN_TYPE, CALLING_CONVENTION, FUNCTION_NAME, ADDRESS, __VA_ARGS__);
81 RETURN_TYPE CALLING_CONVENTION impl_##FUNCTION_NAME(__VA_ARGS__)
82
83///
84/// Gets the installation result of a hook defined with \ref STATIC_HOOK,
85/// \ref STATIC_USER_HOOK or \ref STATIC_ASM_HOOK.
86///
87/// \param FUNCTION_NAME The name of the function that was hooked.
88///
89/// \returns `true` if the installation succeeeded. Otherwise, `false`.
90///
91#define GET_STATIC_HOOK_RESULT(FUNCTION_NAME)
92 g_result_##FUNCTION_NAME
93
94///
95/// An alias for `__fastcall`.
96///
97/// This is intended for use with the other `THISCALL` macros to make setting up
98/// function pointers and hooks with this calling convention easier.
99///
100/// `__thiscall` is a calling convention that stores a pointer to `this` in `ECX`,
101/// before falling back to using the stack, similar to `__fastcall`.
102///
103/// > [!NOTE]
104/// > **For x64:** This calling convention is always `__fastcall` regardless, with
105/// > the class pointer stored in `RCX`.
106///
107#define THISCALL __fastcall
108
109///
110/// \class __CMNLIB_INTERNAL_THISCALL_PARAMS_COMMON
111///
112/// > [!NOTE]
113/// > **For x86:** This macro creates a discard parameter to fill in for `EDX`,
114/// > allowing the rest of the arguments to be pushed onto the stack.
115/// > **For x64:** This macro uses standard `__fastcall` conventions.
116///
117
118///
119/// \def THISCALL_PARAMS
120/// \brief Expands to the necessary function parameters required by `__thiscall`
121/// calling convention.
122/// \hideinitializer
123///
124/// \param CLASS_NAME The name of the class this function is from.
125///
126/// \copydoc __CMNLIB_INTERNAL_THISCALL_PARAMS_COMMON
127///
128/// ## Examples
129/// \code{.cpp}
130/// FUNCTION_PTR(int, THISCALL, my_class_function, 0xDEADBEEF, THISCALL_PARAMS(my_class_name), int in_second_param);
131/// \endcode
132///
133
134///
135/// \def THISCALL_RETURN_PARAMS
136/// \brief Expands to the necessary function arguments required by `__thiscall`
137/// calling convention for calling the original function.
138/// \hideinitializer
139///
140/// \copydoc __CMNLIB_INTERNAL_THISCALL_PARAMS_COMMON
141///
142/// ## Examples
143/// \code{.cpp}
144/// HOOK(int, THISCALL, my_class_function_hook, 0xDEADBEEF, THISCALL_PARAMS(my_class_name), int in_second_param)
145/// {
146/// return original_my_class_function_hook(THISCALL_RETURN_PARAMS, in_secondParam);
147/// }
148/// \endcode
149///
150
151#if defined(CMNLIB_X64)
152#define THISCALL_PARAMS(CLASS_NAME) CLASS_NAME* self
153#define THISCALL_RETURN_PARAMS self
154#elif defined(CMNLIB_X86)
155#define THISCALL_PARAMS(CLASS_NAME) CLASS_NAME* self, void* _
156#define THISCALL_RETURN_PARAMS self, _
157#endif
158
159///
160/// Defines the body of a hook for a function in a virtual function table in memory.
161///
162/// \param RETURN_TYPE The return type of the function.
163/// \param CALLING_CONVENTION The calling convention of the function (e.g.
164/// `__cdecl`, `__stdcall`, `__fastcall`, etc).
165/// This does not support optimised calling conventions,
166/// such as `__usercall` or `__userpurge`.
167/// \param CLASS_NAME The name of the class that contains the function
168/// being hooked.
169/// \param FUNCTION_NAME The name of the function.
170/// \param FUNCTION_INDEX The index of the function in the virtual
171/// function table.
172/// \param __VA_ARGS__ The parameters of the function.
173///
174#define VFTABLE_HOOK(RETURN_TYPE, CALLING_CONVENTION, CLASS_NAME, FUNCTION_NAME, FUNCTION_INDEX, ...)
175 const size_t k_index_of_##CLASS_NAME##_##FUNCTION_NAME = size_t(FUNCTION_INDEX);
176 HOOK(RETURN_TYPE, CALLING_CONVENTION, CLASS_NAME##_##FUNCTION_NAME, nullptr, __VA_ARGS__)
177
178///
179/// \class __CMNLIB_INTERNAL_ASM_HOOK_COMMON
180///
181/// > [!NOTE]
182/// > **For x64:** The body must be defined in an `*.asm` file using MASM. This
183/// > macro creates a scope for members of the hook to be declared in. If no
184/// > members are needed, close the scope immediately using empty braces.
185///
186/// ## Examples
187/// ### x86
188/// `mod.cpp`
189/// \code{.cpp}
190/// ASM_HOOK(my_asm_hook, 0xDEADBEEF)
191/// {
192/// static uint32_t g_my_hook_member = 0x900DF00D;
193///
194/// __asm
195/// {
196/// mov eax, [g_my_hook_member]
197/// }
198///
199/// ASM_HOOK_BRANCH(my_asm_hook)
200/// }
201/// \endcode
202///
203/// ### x64
204/// `mod.cpp`
205/// \code{.cpp}
206/// ASM_HOOK(my_asm_hook, 0xDEADBEEF)
207/// {
208/// uint64_t g_my_hook_member = 0x900DF00D;
209/// }
210/// \endcode
211///
212/// `my_asm_hook.asm`
213/// \code{.asm}
214/// include CommonLib.masm.inc
215///
216/// EXTERN g_my_hook_member : QWORD
217///
218/// ASM_HOOK_START my_asm_hook
219/// mov rax, [g_my_hook_member]
220/// ASM_HOOK_BRANCH my_asm_hook
221/// ASM_HOOK_END my_asm_hook
222///
223/// END
224/// \endcode
225///
226
227///
228/// \class __CMNLIB_INTERNAL_ASM_HOOK_RETURN_BRANCH_COMMON
229///
230/// > [!NOTE]
231/// > **For x64:** This macro can be accessed in an `*.asm` file by
232/// > including `CommonLib.masm.inc`.
233///
234
235///
236/// \def ASM_HOOK
237/// \brief Creates an assembly hook.
238/// \hideinitializer
239///
240/// \param NAME The name of the hook.
241/// \param ADDRESS The address to hook.
242///
243/// \copydoc __CMNLIB_INTERNAL_ASM_HOOK_COMMON
244///
245
246///
247/// \def STATIC_ASM_HOOK
248/// \brief Creates an assembly hook, and installs it upon initialisation.
249/// \hideinitializer
250///
251/// \param NAME The name of the hook.
252/// \param ADDRESS The address to hook.
253///
254/// \copydoc __CMNLIB_INTERNAL_ASM_HOOK_COMMON
255///
256
257///
258/// \def ASM_HOOK_RETURN
259/// \brief Returns from an assembly hook and executes the original code.
260/// \hideinitializer
261///
262/// \param NAME The name of the hook.
263///
264/// \copydoc __CMNLIB_INTERNAL_ASM_HOOK_RETURN_BRANCH_COMMON
265///
266
267///
268/// \def ASM_HOOK_BRANCH
269/// \brief Returns from an assembly hook and skips the original code.
270/// \hideinitializer
271///
272/// \param NAME The name of the hook.
273///
274/// \copydoc __CMNLIB_INTERNAL_ASM_HOOK_RETURN_BRANCH_COMMON
275///
276
277#ifdef CMNLIB_X64
278
279#define ASM_HOOK(NAME, ADDRESS)
280 extern "C" uintptr_t g_address_##NAME = uintptr_t(ADDRESS);
281 extern "C" uintptr_t g_address_post_hook_##NAME{};
282 extern "C" uintptr_t original_##NAME = g_address_##NAME;
283 extern "C" void* impl_##NAME{};
284 extern "C"
285
286#define STATIC_ASM_HOOK(NAME, ADDRESS)
287 ASM_HOOK(NAME, ADDRESS) {}
288 __CMNLIB_INTERNAL_STATIC_HOOK_IMPL(NAME, ADDRESS, INSTALL_HOOK);
289 extern "C"
290
291#endif // CMNLIB_X64
292
293#ifdef CMNLIB_X86
294
295#define ASM_HOOK(NAME, ADDRESS)
296 void* g_address_##NAME = reinterpret_cast<void*>(ADDRESS);
297 void* g_address_post_hook_##NAME{};
298 void* original_##NAME = g_address_##NAME;
299 void NAKED impl_##NAME()
300
301#define STATIC_ASM_HOOK(NAME, ADDRESS)
302 void* g_address_##NAME = reinterpret_cast<void*>(ADDRESS);
303 void* g_address_post_hook_##NAME{};
304 void* original_##NAME = g_address_##NAME;
305 void impl_##NAME();
306 __CMNLIB_INTERNAL_STATIC_HOOK_IMPL(NAME, ADDRESS, INSTALL_HOOK);
307 void NAKED impl_##NAME()
308
309#define ASM_HOOK_RETURN(NAME) __asm jmp original_##NAME
310
311#define ASM_HOOK_BRANCH(NAME) __asm jmp g_address_post_hook_##NAME
312
313#endif // CMNLIB_X86
314
315///
316/// Installs a hook defined with \ref HOOK or \ref ASM_HOOK.
317///
318/// \param NAME The name of the hook to install.
319///
320/// \returns `true` if the installation succeeeded, or if the hook was already
321/// installed. Otherwise, `false`.
322///
323#define INSTALL_HOOK(NAME)
324 INSTALL_HOOK_EXPLICIT(NAME, original_##NAME)
325
326///
327/// Installs a hook defined with \ref HOOK or \ref ASM_HOOK at an explicit address.
328///
329/// \param NAME The name of the hook to install.
330/// \param ADDRESS The address to install the hook to.
331///
332/// \returns `true` if the installation succeeeded, or if the hook was already
333/// installed. Otherwise, `false`.
334///
335#define INSTALL_HOOK_EXPLICIT(NAME, ADDRESS)
336 std::invoke([&]() -> bool
337 {
338 if (!original_##NAME && !(ADDRESS))
339 return false;
340
341 *reinterpret_cast<void**>(&original_##NAME) = reinterpret_cast<void*>(ADDRESS);
342
343 DetourTransactionBegin();
344 DetourUpdateThread(GetCurrentThread());
345 DetourAttach(reinterpret_cast<void**>(&original_##NAME), &impl_##NAME);
346
347 const auto result = DetourTransactionCommit() == NO_ERROR;
348
349 if (result)
350 {
351 g_address_post_hook_##NAME =
352 hedgedev::csl::hook::_get_post_hook_address(reinterpret_cast<void*>(g_address_##NAME));
353 }
354
355 return result;
356 })
357
358///
359/// Uninstalls a hook installed with \ref INSTALL_HOOK.
360///
361/// \param NAME The name of the hook to uninstall.
362///
363/// \returns `true` if the uninstallation succeeeded, or if the hook was already
364/// uninstalled. Otherwise, `false`.
365///
366#define UNINSTALL_HOOK(NAME)
367 std::invoke([&]() -> bool
368 {
369 if (g_address_##NAME == original_##NAME)
370 return true;
371
372 DetourTransactionBegin();
373 DetourUpdateThread(GetCurrentThread());
374 DetourDetach(reinterpret_cast<void**>(&original_##NAME), &impl_##NAME);
375
376 return DetourTransactionCommit() == NO_ERROR;
377 })
378
379///
380/// Installs a hook defined with \ref VFTABLE_HOOK.
381///
382/// \param INSTANCE A pointer to an instance of the class to extract the
383/// virtual function table pointer from.
384/// \param CLASS_NAME The name of the class that owns the function being hooked.
385/// \param FUNCTION_NAME The name of the function being hooked.
386///
387/// \returns `true` if the installation succeeeded, or if the hook was already
388/// installed. Otherwise, `false`.
389///
390#define INSTALL_VFTABLE_HOOK(INSTANCE, CLASS_NAME, FUNCTION_NAME)
391 INSTALL_VFTABLE_HOOK_EXPLICIT(INSTANCE, CLASS_NAME, FUNCTION_NAME, k_index_of_##CLASS_NAME##_##FUNCTION_NAME)
392
393///
394/// Installs a hook defined with \ref VFTABLE_HOOK at an explicit index.
395///
396/// \param INSTANCE A pointer to an instance of the class to extract the
397/// virtual function table pointer from.
398/// \param CLASS_NAME The name of the class that owns the function being hooked.
399/// \param FUNCTION_NAME The name of the function being hooked.
400/// \param FUNCTION_INDEX The index of the function in the virtual function table.
401///
402/// \returns `true` if the installation succeeeded, or if the hook was already
403/// installed. Otherwise, `false`.
404///
405#define INSTALL_VFTABLE_HOOK_EXPLICIT(INSTANCE, CLASS_NAME, FUNCTION_NAME, FUNCTION_INDEX)
406 std::invoke([&]() -> bool
407 {
408 if (original_##CLASS_NAME##_##FUNCTION_NAME)
409 return true;
410
411 original_##CLASS_NAME##_##FUNCTION_NAME = *reinterpret_cast<CLASS_NAME##_##FUNCTION_NAME***>(INSTANCE)[FUNCTION_INDEX];
412
413 DetourTransactionBegin();
414 DetourUpdateThread(GetCurrentThread());
415 DetourAttach(reinterpret_cast<void**>(&original_##CLASS_NAME##_##FUNCTION_NAME), impl_##CLASS_NAME##_##FUNCTION_NAME);
416
417 const auto result = DetourTransactionCommit() == NO_ERROR;
418
419 if (result)
420 {
421 g_address_post_hook_##CLASS_NAME##_##FUNCTION_NAME =
422 hedgedev::csl::hook::_get_post_hook_address(reinterpret_cast<void*>(g_address_##CLASS_NAME##_##FUNCTION_NAME));
423 }
424
425 return result;
426 })
427
428///
429/// Uninstalls a hook installed with \ref INSTALL_VFTABLE_HOOK.
430///
431/// \param CLASS_NAME The name of the class that owns the function that
432/// was hooked.
433/// \param FUNCTION_NAME The name of the function to unhook.
434///
435/// \returns `true` if the uninstallation succeeeded, or if the hook was already
436/// uninstalled. Otherwise, `false`.
437///
438#define UNINSTALL_VFTABLE_HOOK(CLASS_NAME, FUNCTION_NAME)
439 UNINSTALL_HOOK(CLASS_NAME##_##FUNCTION_NAME)
440
441namespace hedgedev::csl::hook
442{
443 inline void* _get_post_hook_address(void* in_hook)
444 {
445 uint8_t* result{};
446
447 if (!in_hook)
448 return result;
449
450 const auto branch_info = mem::get_branch_info(in_hook);
451
452 result = reinterpret_cast<uint8_t*>(uintptr_t(in_hook) + branch_info.instr_length);
453
454 while (*result == 0xCC)
455 result++;
456
457 return result;
458 }
459}
460
461#ifdef CMNLIB_X86
462#include "hook_usercall_win32.h"
463#endif
#define __CMNLIB_INTERNAL_STATIC_HOOK_IMPL(NAME, ADDRESS, INSTALLER)
Definition hook_win32.h:12
#define INSTALL_VFTABLE_HOOK_EXPLICIT(INSTANCE, CLASS_NAME, FUNCTION_NAME, FUNCTION_INDEX)
Definition hook_win32.h:405
#define INSTALL_HOOK(NAME)
Definition hook_win32.h:323
#define HOOK(RETURN_TYPE, CALLING_CONVENTION, FUNCTION_NAME, ADDRESS,...)
Definition hook_win32.h:63
#define INSTALL_HOOK_EXPLICIT(NAME, ADDRESS)
Definition hook_win32.h:335
#define UNINSTALL_HOOK(NAME)
Definition hook_win32.h:366
Definition hook_usercall_win32.h:223
void * _get_post_hook_address(void *in_hook)
Definition hook_win32.h:443
Definition mem.h:169
branch_info get_branch_info(void *in_address)
Definition mem_x86.inl:44
#define DLL_IMPORT_EXPLICIT(LIBRARY_NAME, MEMBER_NAME)
Definition preprocessor_win32.h:19