CommonLib
Loading...
Searching...
No Matches
hook_usercall_win32.h
Go to the documentation of this file.
1#pragma once
2
3#include <bit>
4#include <functional>
5#include <optional>
6#include <typeindex>
7#include <type_traits>
8#include <vector>
9
10#include "../thirdparty/Detours/src/detours.h"
11#include "host/common.h"
12#include "mem/mem.h"
13
14#define __CMNLIB_INTERNAL_USER_SIZE_OF(RETURN_TYPE)
15 sizeof(typename std::conditional_t<std::is_void_v<RETURN_TYPE>, RETURN_TYPE*, RETURN_TYPE>)
16
17///
18/// A custom calling convention that stores arguments in optimised locations,
19/// before falling back to using the stack, similar to `__fastcall`.
20///
21/// This calling convention puts the responsibility of cleaning up stack
22/// arguments on the caller.
23///
24/// This identifier cannot be used as a standard calling convention.
25///
26#define __usercall 0
27
28///
29/// A custom calling convention that stores arguments in optimised locations,
30/// before falling back to using the stack, similar to `__fastcall`.
31///
32/// This calling convention puts the responsibility of cleaning up stack
33/// arguments on the callee.
34///
35/// This identifier cannot be used as a standard calling convention.
36///
37#define __userpurge 1
38
39#define USER_REGISTER(REGISTER)
40 uint64_t(hedgedev::csl::hook::user_register(hedgedev::csl::hook::user_register_alias::REGISTER))
41
42///
43/// Specify the register to use for the return value.
44///
45/// \param REGISTER The register to use for the return value.
46///
47#define USER_RETURN(REGISTER)
48 USER_REGISTER(REGISTER)
49
50///
51/// Specifies the hook has no return value.
52///
53#define USER_RETURN_VOID
54 USER_RETURN(none)
55
56///
57/// Specify the register to use for a specific parameter.
58///
59/// \param INDEX The index of the parameter.
60/// \param REGISTER The register to use for the parameter.
61///
62#define USER_PARAM(INDEX, REGISTER)
63 (USER_REGISTER(REGISTER) << ((INDEX + 1) * hedgedev::csl::hook::k_user_register_size))
64
65///
66/// \class __CMNLIB_INTERNAL_USER_HOOK_COMMON_PARAMS
67///
68/// \param RETURN_TYPE The return type of the function.
69/// \param CALLING_CONVENTION The calling convention of the function, such as
70/// `__usercall` or `__userpurge`.
71/// \param FUNCTION_NAME The name of the function.
72/// \param ADDRESS The address of the function.
73/// \param REGISTERS The registers used by the return value and parameters.
74/// If a parameter has an unspecified register, it'll
75/// automatically be put on the stack.
76/// \param PARAM_COUNT The total number of parameters in `__VA_ARGS__`.
77/// \param __VA_ARGS__ The parameters of the function.
78///
79
80///
81/// \class __CMNLIB_INTERNAL_USER_HOOK_COMMON_PROTOTYPE
82///
83/// For an optimised function with the following prototype:
84/// \code{.cpp}
85/// int __usercall my_optimised_function@<eax>(int in_first_param@<eax>, float in_second_param@<xmm0>, int in_third_param)
86/// \endcode
87///
88
89///
90/// Declares a pointer to a function with custom calling convention in memory.
91///
92/// \copydoc __CMNLIB_INTERNAL_USER_HOOK_COMMON_PARAMS
93///
94/// ## Examples
95/// \copydoc __CMNLIB_INTERNAL_USER_HOOK_COMMON_PROTOTYPE
96///
97/// A function pointer should be set up like the following:
98/// \code{.cpp}
99/// USER_FUNCTION_PTR(int, __usercall, my_optimised_function, 0xDEADBEEF, USER_RETURN(EAX) | USER_PARAM(0, EAX) | USER_PARAM(1, XMM0), 3, int in_first_param, float in_second_param, int in_third_param);
100/// \endcode
101///
102#define USER_FUNCTION_PTR(RETURN_TYPE, CALLING_CONVENTION, FUNCTION_NAME, ADDRESS, REGISTERS, PARAM_COUNT, ...)
103 hedgedev::csl::hook::usercall_info g_info_##FUNCTION_NAME##_fp
104 { typeid(RETURN_TYPE), __CMNLIB_INTERNAL_USER_SIZE_OF(RETURN_TYPE), CALLING_CONVENTION, reinterpret_cast<void*>(ADDRESS), reinterpret_cast<void*>(ADDRESS), REGISTERS, PARAM_COUNT };
105 void* g_trampoline_to_##FUNCTION_NAME = hedgedev::csl::hook::emit_user_trampoline(g_info_##FUNCTION_NAME##_fp, reinterpret_cast<void*>(&g_info_##FUNCTION_NAME##_fp.original_address), true);
106 FUNCTION_PTR(RETURN_TYPE, __cdecl, FUNCTION_NAME, g_trampoline_to_##FUNCTION_NAME, __VA_ARGS__)
107
108///
109/// \class __CMNLIB_INTERNAL_USER_HOOK_COMMON
110///
111/// \copydoc __CMNLIB_INTERNAL_USER_HOOK_COMMON_PARAMS
112///
113/// ## Examples
114/// \copydoc __CMNLIB_INTERNAL_USER_HOOK_COMMON_PROTOTYPE
115///
116/// A hook should be set up like the following:
117/// \code{.cpp}
118/// USER_HOOK(int, __usercall, my_optimised_function_hook, 0xDEADBEEF, USER_RETURN(EAX) | USER_PARAM(0, EAX) | USER_PARAM(1, XMM0), 3, int in_first_param, float in_second_param, int in_third_param)
119/// {
120/// return original_my_optimised_function_hook(in_first_param, in_second_param, in_third_param);
121/// }
122/// \endcode
123///
124
125///
126/// Defines the body of a hook for a function with custom calling convention
127/// in memory.
128///
129/// \copydoc __CMNLIB_INTERNAL_USER_HOOK_COMMON
130///
131#define USER_HOOK(RETURN_TYPE, CALLING_CONVENTION, FUNCTION_NAME, ADDRESS, REGISTERS, PARAM_COUNT, ...)
132 hedgedev::csl::hook::usercall_info g_info_##FUNCTION_NAME
133 { typeid(RETURN_TYPE), __CMNLIB_INTERNAL_USER_SIZE_OF(RETURN_TYPE), CALLING_CONVENTION, reinterpret_cast<void*>(ADDRESS), reinterpret_cast<void*>(ADDRESS), REGISTERS, PARAM_COUNT };
134 RETURN_TYPE __cdecl impl_##FUNCTION_NAME(__VA_ARGS__);
135 void* g_trampoline_to_##FUNCTION_NAME##_hook = hedgedev::csl::hook::emit_user_trampoline(g_info_##FUNCTION_NAME, reinterpret_cast<void*>(&impl_##FUNCTION_NAME));
136 HOOK(RETURN_TYPE, __cdecl, FUNCTION_NAME, nullptr, __VA_ARGS__)
137
138///
139/// Defines the body of a hook for a function with custom calling convention
140/// in memory, and installs it upon initialisation.
141///
142/// \copydoc __CMNLIB_INTERNAL_USER_HOOK_COMMON
143///
144/// \returns Use \ref GET_STATIC_HOOK_RESULT.
145///
146#define STATIC_USER_HOOK(RETURN_TYPE, CALLING_CONVENTION, FUNCTION_NAME, ADDRESS, REGISTERS, PARAM_COUNT, ...)
147 USER_HOOK(RETURN_TYPE, CALLING_CONVENTION, FUNCTION_NAME, ADDRESS, REGISTERS, PARAM_COUNT, __VA_ARGS__);
148 __CMNLIB_INTERNAL_STATIC_HOOK_IMPL(FUNCTION_NAME, ADDRESS, INSTALL_USER_HOOK)
149 RETURN_TYPE __cdecl impl_##FUNCTION_NAME(__VA_ARGS__)
150
151///
152/// Installs a hook defined with \ref USER_HOOK.
153///
154/// \param FUNCTION_NAME The name of the function to call before the original.
155///
156/// \returns `true` if the installation succeeeded, or if the hook was already
157/// installed. Otherwise, `false`.
158///
159#define INSTALL_USER_HOOK(FUNCTION_NAME)
160 INSTALL_USER_HOOK_EXPLICIT(FUNCTION_NAME, g_info_##FUNCTION_NAME.detour_address)
161
162///
163/// Installs a hook defined with \ref USER_HOOK at an explicit address.
164///
165/// \param FUNCTION_NAME The name of the function to call before the original.
166/// \param ADDRESS The address of the function to hook.
167///
168/// \returns `true` if the installation succeeeded, or if the hook was already
169/// installed. Otherwise, `false`.
170///
171#define INSTALL_USER_HOOK_EXPLICIT(FUNCTION_NAME, ADDRESS)
172 std::invoke([&]() -> bool
173 {
174 auto& info = g_info_##FUNCTION_NAME;
175
176 if (!info.detour_address && !(ADDRESS))
177 return false;
178
179 *reinterpret_cast<void**>(&info.detour_address) = reinterpret_cast<void*>(ADDRESS);
180
181 DetourTransactionBegin();
182 DetourUpdateThread(GetCurrentThread());
183 DetourAttach(reinterpret_cast<void**>(&info.detour_address), g_trampoline_to_##FUNCTION_NAME##_hook);
184
185 const auto result = DetourTransactionCommit() == NO_ERROR;
186
187 if (result)
188 {
189 *reinterpret_cast<void**>(&original_##FUNCTION_NAME) =
190 hedgedev::csl::hook::emit_user_trampoline(info, reinterpret_cast<void*>(&info.detour_address), true);
191
192 g_address_post_hook_##FUNCTION_NAME =
193 hedgedev::csl::hook::_get_post_hook_address(reinterpret_cast<void*>(g_address_##FUNCTION_NAME));
194 }
195
196 return result;
197 })
198
199///
200/// Uninstalls a hook installed with \ref INSTALL_USER_HOOK.
201///
202/// \param FUNCTION_NAME The name of the function to unhook.
203///
204/// \returns `true` if the uninstallation succeeeded, or if the hook was already
205/// uninstalled. Otherwise, `false`.
206///
207#define UNINSTALL_USER_HOOK(FUNCTION_NAME)
208 std::invoke([&]() -> bool
209 {
210 auto& info = g_info_##FUNCTION_NAME;
211
212 if (info.original_address == info.detour_address)
213 return true;
214
215 DetourTransactionBegin();
216 DetourUpdateThread(GetCurrentThread());
217 DetourDetach(reinterpret_cast<void**>(&info.detour_address), g_trampoline_to_##FUNCTION_NAME##_hook);
218
219 return DetourTransactionCommit() == NO_ERROR;
220 })
221
222namespace hedgedev::csl::hook
223{
224 ///
225 /// Registers used for `__usercall`/`__userpurge` calling conventions.
226 ///
227 /// These registers have been ordered in an specific way that line up with
228 /// register IDs used for encoding instructions.
229 ///
263
318
327
328 ///
329 /// The total number of bits in the register flags.
330 ///
331 inline static constexpr size_t k_user_register_bits_length = sizeof(uint64_t) * sizeof(uint64_t);
332
333 ///
334 /// The total number of bits per register in the register flags.
335 ///
336 inline static constexpr size_t k_user_register_size = std::bit_width(uint64_t(user_register::count));
337
338 ///
339 /// The maximum number of registers that can fit in the register flags.
340 ///
341 inline static constexpr size_t k_user_register_max = k_user_register_bits_length / k_user_register_size;
342
343 ///
344 /// The number of remaining bits when all slots are used up in the register flags.
345 ///
346 inline static constexpr size_t k_user_register_bits_remainder = k_user_register_bits_length % k_user_register_size;
347
348 ///
349 /// The mask for getting the register bits.
350 ///
351 inline static constexpr uint64_t k_user_register_mask = 0x1F;
352
354 {
355 std::type_index return_type;
362
364 {
365 return size_t(std::bit_width(registers) + k_user_register_bits_remainder) / k_user_register_size;
366 }
367
369 {
370 const auto register_count = get_register_count();
371
372 if (register_count <= 1)
373 return 0;
374
375 return register_count - 1;
376 }
377
379 {
380 return param_count - get_register_param_count();
381 }
382
384 {
385 return get_register_param_count() + get_stack_param_count();
386 }
387
388 user_register get_register(int in_index) const
389 {
390 return user_register(registers >> (k_user_register_size * in_index) & k_user_register_mask);
391 }
392
394 {
395 return get_register(0);
396 }
397
399 {
400 return get_register(in_index + 1);
401 }
402
403 template <typename T>
404 bool is_return_type() const
405 {
406 return typeid(T) == return_type;
407 }
408
409 bool is_stack_param(int in_index) const
410 {
411 return get_register_param_count() < size_t(in_index + 1);
412 }
413 };
414
415 ///
416 /// Gets the family a register belongs to.
417 ///
418 /// \param in_register The register to check.
419 ///
421 {
422 if (in_register >= user_register::EAX && in_register <= user_register::EDI)
423 {
425 }
426 else if (in_register >= user_register::ST0 && in_register <= user_register::ST7)
427 {
429 }
430 else if (in_register >= user_register::XMM0 && in_register <= user_register::XMM7)
431 {
433 }
434
436 }
437
438 ///
439 /// Gets the ID of a register.
440 ///
441 /// \param in_register The register to get the ID for.
442 ///
443 inline int get_register_id(user_register in_register)
444 {
445 return int(in_register) - int(get_register_family(in_register));
446 }
447
448 ///
449 /// Checks if a register is protected and should be restored after calling the
450 /// original function.
451 ///
452 /// \param in_register The register to check.
453 ///
454 inline bool is_protected_register(user_register in_register)
455 {
456 return in_register == user_register::EBX ||
457 in_register == user_register::EBP ||
458 in_register == user_register::ESI ||
459 in_register == user_register::EDI;
460 }
461
462 ///
463 /// Emits a trampoline that forwards arguments from a function with custom calling
464 /// convention to a `__cdecl` function in a hook defined with \ref USER_HOOK.
465 ///
466 /// \param in_info The info about the hook.
467 /// \param in_address The address of the `__cdecl` function to trampoline to.
468 /// \param in_to_original Determines whether this trampoline is going back to the
469 /// original function.
470 ///
471 /// \returns A pointer to the trampoline that was emitted.
472 ///
473 inline void* emit_user_trampoline(const usercall_info& in_info, void* in_address, bool in_to_original = false)
474 {
475 std::vector<uint8_t> result{};
476
477 const auto emit_imm8 = [&](int in_value) -> size_t
478 {
479 auto pos = result.size();
480
481 result.push_back(in_value & 0xFF);
482
483 return pos;
484 };
485
486 const auto emit_imm16 = [&](int in_value) -> size_t
487 {
488 auto pos = emit_imm8(in_value);
489
490 result.push_back((in_value >> 8) & 0xFF);
491
492 return pos;
493 };
494
495 const auto emit_imm32 = [&](int in_value) -> size_t
496 {
497 auto pos = emit_imm16(in_value);
498
499 result.push_back((in_value >> 16) & 0xFF);
500 result.push_back((in_value >> 24) & 0xFF);
501
502 return pos;
503 };
504
505 const auto emit_push = [&](user_register in_register, std::optional<int> in_offset = {})
506 {
507 const auto register_id = get_register_id(in_register);
508
509 if (in_offset.has_value())
510 {
511 const auto offset = in_offset.value();
512 const auto emit_ebp_no_offset = in_register == user_register::EBP && offset == 0;
513
514 result.push_back(0xFF);
515
516 if (!offset && !emit_ebp_no_offset)
517 {
518 // push [{register}]
519 result.push_back(uint8_t(0x30 | register_id));
520 }
521 else if (offset <= 0x7F || emit_ebp_no_offset)
522 {
523 // push [{register} + {imm8}]
524 result.push_back(uint8_t(0x70 | register_id));
525 }
526 else
527 {
528 // push [{register} + {imm32}]
529 result.push_back(uint8_t(0xB0 | register_id));
530 }
531
532 if (in_register == user_register::ESP)
533 result.push_back(0x24);
534
535 if (!offset && !emit_ebp_no_offset)
536 return;
537
538 if ((offset > 0 && offset <= 0x7F) || emit_ebp_no_offset)
539 {
540 emit_imm8(offset);
541 }
542 else if (offset > 0x7F)
543 {
544 emit_imm32(offset);
545 }
546
547 return;
548 }
549
550 // push {register}
551 result.push_back(uint8_t(0x50 | register_id));
552 };
553
554 const auto emit_pop = [&](user_register in_register)
555 {
556 // pop {register}
557 result.push_back(uint8_t(0x58 | get_register_id(in_register)));
558 };
559
560 const auto emit_add_subtract = [&](user_register in_register, int in_value)
561 {
562 if (!in_value)
563 return;
564
565 const auto register_id = get_register_id(in_register);
566 const auto is_add = in_value > 0;
567 const auto value_abs = std::abs(in_value);
568
569 if (value_abs == 1)
570 {
571 auto opcode = 0x40;
572
573 if (!is_add)
574 opcode |= 8;
575
576 // inc/dec {register}
577 result.push_back(uint8_t(opcode | register_id));
578 }
579 else
580 {
581 auto modifier = 0;
582
583 if (!is_add)
584 modifier = 0x28;
585
586 if (value_abs > 0 && value_abs <= 0x7F)
587 {
588 // add/sub {register}, {imm8}
589 result.push_back(0x83);
590 result.push_back(uint8_t(0xC0 | modifier | register_id));
591 emit_imm8(value_abs);
592 }
593 else if (value_abs > 0x7F)
594 {
595 // add/sub {register}, {imm32}
596 if (in_register == user_register::EAX)
597 {
598 result.push_back(uint8_t(0x05 | modifier));
599 }
600 else
601 {
602 result.push_back(0x81);
603 result.push_back(uint8_t(0xC0 | modifier | register_id));
604 }
605
606 emit_imm32(value_abs);
607 }
608 }
609 };
610
611 const auto emit_load_store_sse = [&](user_register in_dst, user_register in_src, int in_offset = 0, int in_float_size = sizeof(float), bool in_store = false)
612 {
613 const auto dst_register_id = get_register_id(in_dst);
614 const auto src_register_id = get_register_id(in_src);
615
616 const auto ptr_register = in_store ? in_dst : in_src;
617 const auto ptr_register_id = in_store ? dst_register_id : src_register_id;
618
619 const auto data_register = in_store ? in_src : in_dst;
620 const auto data_register_id = in_store ? src_register_id : dst_register_id;
621
622 const auto emit_ebp_no_offset = ptr_register == user_register::EBP && in_offset == 0;
623
624 // movss dword ptr
625 auto opcode = 0xF3;
626
627 // movsd qword ptr
628 if (in_float_size == sizeof(double))
629 opcode = 0xF2;
630
631 result.push_back(opcode);
632 result.push_back(0x0F);
633 result.push_back(in_store ? 0x11 : 0x10);
634
635 auto modifier = 0;
636
637 if ((in_offset > 0 && in_offset <= 0x7F) || emit_ebp_no_offset)
638 {
639 modifier = 0x40;
640 }
641 else if (in_offset > 0x7F)
642 {
643 modifier = 0x80;
644 }
645
646 if (ptr_register == user_register::ESP)
647 {
648 result.push_back(uint8_t(0x04 | modifier | (data_register_id << 3)));
649 result.push_back(0x24);
650 }
651 else
652 {
653 result.push_back(uint8_t(modifier | (ptr_register_id << 3) | data_register_id));
654 }
655
656 if ((in_offset > 0 && in_offset <= 0x7F) || emit_ebp_no_offset)
657 {
658 // movss/movsd {dst}, dword/qword ptr [{src} + {imm8}]
659 // movss/movsd dword/qword ptr [{dst} + {imm8}], {src}
660 emit_imm8(in_offset);
661 }
662 else if (in_offset > 0x7F)
663 {
664 // movss/movsd {dst}, dword/qword ptr [{src} + {imm32}]
665 // movss/movsd dword/qword ptr [{dst} + {imm32}], {src}
666 emit_imm32(in_offset);
667 }
668 };
669
670 const auto emit_exchange_register_fpu = [&](user_register in_register)
671 {
673 return;
674
675 // fxch {register}
676 result.push_back(0xD9);
677 result.push_back(uint8_t(0xC8 | get_register_id(in_register)));
678 };
679
680 const auto emit_load_store_fpu = [&](user_register in_register, int in_offset = 0, int in_float_size = sizeof(float), bool in_store = false)
681 {
682 const auto registerId = get_register_id(in_register);
683
684 switch (get_register_family(in_register))
685 {
687 {
688 const auto emit_ebp_no_offset = in_register == user_register::EBP && in_offset == 0;
689
690 // fld dword ptr
691 auto opcode = 0xD9;
692
693 // fld qword ptr
694 if (in_float_size == sizeof(double))
695 opcode |= 4;
696
697 result.push_back(opcode);
698
699 auto modifier = 0;
700
701 if ((in_offset > 0 && in_offset <= 0x7F) || emit_ebp_no_offset)
702 {
703 modifier = 0x40;
704 }
705 else if (in_offset > 0x7F)
706 {
707 modifier = 0x80;
708 }
709
710 // fstp
711 if (in_store)
712 modifier |= 0x18;
713
714 if (in_register == user_register::ESP)
715 modifier |= 4;
716
717 result.push_back(uint8_t(modifier | registerId));
718
719 if (in_register == user_register::ESP)
720 result.push_back(0x24);
721
722 if ((in_offset > 0 && in_offset <= 0x7F) || emit_ebp_no_offset)
723 {
724 // fld/fstp dword/qword ptr [{register} + {imm8}]
725 emit_imm8(in_offset);
726 }
727 else if (in_offset > 0x7F)
728 {
729 // fld/fstp dword/qword ptr [{register} + {imm32}]
730 emit_imm32(in_offset);
731 }
732
733 break;
734 }
735
736 // fstp {dst}
738 result.push_back(0xDD);
739 result.push_back(uint8_t(0xD8 | registerId));
740 break;
741 }
742 };
743
744 const auto emit_load_store = [&](user_register in_dst, user_register in_src, int in_offset = 0, bool in_store = false)
745 {
746 const auto dst_register_id = get_register_id(in_dst);
747 const auto src_register_id = get_register_id(in_src);
748
749 const auto ptr_register = in_store ? in_dst : in_src;
750 const auto ptr_register_id = in_store ? dst_register_id : src_register_id;
751
752 const auto data_register = in_store ? in_src : in_dst;
753 const auto data_register_id = in_store ? src_register_id : dst_register_id;
754
755 const auto emit_ebp_no_offset = ptr_register == user_register::EBP && in_offset == 0;
756
757 // mov [{dst}], {src}
758 auto opcode = 0x89;
759
760 // mov {dst}, [{src}]
761 if (!in_store)
762 opcode |= 2;
763
764 result.push_back(opcode);
765
766 auto modifier = 0;
767
768 if ((in_offset > 0 && in_offset <= 0x7F) || emit_ebp_no_offset)
769 {
770 modifier = 0x40;
771 }
772 else if (in_offset > 0x7F)
773 {
774 modifier = 0x80;
775 }
776
777 if (ptr_register == user_register::ESP)
778 {
779 result.push_back(uint8_t(0x04 | modifier | (data_register_id << 3)));
780 result.push_back(0x24);
781 }
782 else
783 {
784 result.push_back(uint8_t(modifier | (ptr_register_id << 3) | data_register_id));
785 }
786
787 if ((in_offset > 0 && in_offset <= 0x7F) || emit_ebp_no_offset)
788 {
789 // mov {dst}, [{src} + {imm8}]
790 // mov [{dst} + {imm8}], {src}
791 emit_imm8(in_offset);
792 }
793 else if (in_offset > 0x7F)
794 {
795 // mov {dst}, [{src} + {imm32}]
796 // mov [{dst} + {imm32}], {src}
797 emit_imm32(in_offset);
798 }
799 };
800
801 const auto emit_move_register_fpu = [&](user_register in_dst, user_register in_src, int in_float_size = sizeof(float))
802 {
803 const auto dst_register_family = get_register_family(in_dst);
804 const auto src_register_family = get_register_family(in_src);
805
806 if (dst_register_family == user_register_family::FPU)
807 {
808 if (dst_register_family == src_register_family)
809 {
810 if (in_dst == in_src)
811 return;
812
813 // Exchange ST(0) with source register.
814 emit_exchange_register_fpu(in_src);
815
816 // Copy ST(0) to destination register.
817 emit_load_store_fpu(in_dst);
818
819 // Restore ST(0).
820 emit_exchange_register_fpu(in_src);
821 }
822 else
823 {
824 // Allocate stack space for register.
825 emit_add_subtract(user_register::ESP, -in_float_size);
826
827 if (src_register_family == user_register_family::GPR)
828 {
829 // Copy GPR register into stack.
830 emit_load_store(user_register::ESP, in_src, 0, true);
831 }
832 else if (src_register_family == user_register_family::SSE)
833 {
834 // Copy SSE register into stack.
835 emit_load_store_sse(user_register::ESP, in_src, 0, in_float_size, true);
836 }
837
838 // Exchange ST(0) with destination register.
839 if (in_dst != user_register::ST0)
840 emit_exchange_register_fpu(in_dst);
841
842 // Load stack value into FPU register.
843 emit_load_store_fpu(user_register::ESP, 0, in_float_size);
844
845 // Restore ST(0).
846 if (in_dst != user_register::ST0)
847 emit_exchange_register_fpu(in_dst);
848
849 // Deallocate stack space.
850 emit_add_subtract(user_register::ESP, in_float_size);
851 }
852 }
853 else
854 {
855 // Allocate stack space for FPU register.
856 emit_add_subtract(user_register::ESP, -in_float_size);
857
858 // Exchange ST(0) with source register.
859 if (in_src != user_register::ST0)
860 emit_exchange_register_fpu(in_src);
861
862 // Copy ST(0) into stack.
863 emit_load_store_fpu(user_register::ESP, 0, in_float_size, true);
864
865 // Restore ST(0).
866 if (in_src != user_register::ST0)
867 emit_exchange_register_fpu(in_src);
868
869 if (dst_register_family == user_register_family::GPR)
870 {
871 // Load stack value into GPR register.
872 emit_load_store(in_dst, user_register::ESP, 0);
873 }
874 else if (dst_register_family == user_register_family::SSE)
875 {
876 // Load stack value into SSE register.
877 emit_load_store_sse(in_dst, user_register::ESP, 0, in_float_size);
878 }
879
880 // Deallocate stack space.
881 emit_add_subtract(user_register::ESP, in_float_size);
882 }
883 };
884
885 const auto emit_move_register_sse = [&](user_register in_dst, user_register in_src, int in_float_size = sizeof(float))
886 {
887 const auto dst_register_id = get_register_id(in_dst);
888 const auto src_register_id = get_register_id(in_src);
889 const auto dst_register_family = get_register_family(in_dst);
890 const auto src_register_family = get_register_family(in_src);
891
892 if (dst_register_family == src_register_family && dst_register_family == user_register_family::SSE)
893 {
894 // movss {dst}, {src}
895 auto opcode = 0xF3;
896
897 // movsd {dst}, {src}
898 if (in_float_size == sizeof(double))
899 opcode = 0xF2;
900
901 result.push_back(opcode);
902 result.push_back(0x0F);
903 result.push_back(0x10);
904 result.push_back(uint8_t(0xC0 | (src_register_id << 3) | dst_register_id));
905 }
906 else if ((dst_register_family == user_register_family::GPR || dst_register_family == user_register_family::SSE) &&
907 (src_register_family == user_register_family::GPR || src_register_family == user_register_family::SSE))
908 {
909 auto modifier = 0x6E;
910
911 if (dst_register_family == user_register_family::GPR)
912 modifier |= 0x10;
913
914 // movd {dst}, {src}
915 result.push_back(0x66);
916 result.push_back(0x0F);
917 result.push_back(modifier);
918 result.push_back(uint8_t(0xC0 | (src_register_id << 3) | dst_register_id));
919 }
920 else
921 {
922 emit_move_register_fpu(in_dst, in_src, in_float_size);
923 }
924 };
925
926 const auto emit_move_register = [&](user_register in_dst, user_register in_src, int in_float_size = sizeof(float))
927 {
928 const auto dst_register_family = get_register_family(in_dst);
929 const auto src_register_family = get_register_family(in_src);
930
931 if (dst_register_family == src_register_family)
932 {
933 switch (dst_register_family)
934 {
935 // mov {dst}, {src}
937 result.push_back(0x89);
938 result.push_back(uint8_t(0xC0 | (get_register_id(in_src) << 3) | get_register_id(in_dst)));
939 break;
940
942 emit_move_register_fpu(in_dst, in_src, in_float_size);
943 break;
944
946 emit_move_register_sse(in_dst, in_src, in_float_size);
947 break;
948 }
949 }
950 else
951 {
952 emit_move_register_sse(in_dst, in_src, in_float_size);
953 }
954 };
955
956 const auto emit_branch = [&](bool in_call, bool in_far) -> size_t
957 {
958 if (in_far)
959 {
960 // jmp/call [{imm32}]
961 result.push_back(0xFF);
962 result.push_back(in_call ? 0x15 : 0x25);
963 }
964 else
965 {
966 // jmp/call {imm32}
967 result.push_back(in_call ? 0xE8 : 0xE9);
968 }
969
970 return emit_imm32(0);
971 };
972
973 const auto emit_branch_address = [&](void* in_trampoline, ptrdiff_t in_branch_offset, void* in_target, bool in_absolute)
974 {
975 const auto offset = uintptr_t(in_trampoline) + in_branch_offset;
976
977 if (in_absolute)
978 {
979 *reinterpret_cast<uint32_t*>(offset) = uint32_t(in_target);
980 }
981 else
982 {
983 *reinterpret_cast<uint32_t*>(offset) = uint32_t(uintptr_t(in_target) - offset - sizeof(uint32_t));
984 }
985 };
986
987 const auto emit_return = [&](size_t in_size = 0)
988 {
989 auto opcode = 0xC2;
990
991 if (!in_size)
992 opcode |= 1;
993
994 // ret
995 result.push_back(opcode);
996
997 if (!in_size)
998 return;
999
1000 // ret {imm16}
1001 emit_imm16(in_size);
1002 };
1003
1004 const auto register_count = in_info.get_register_count();
1005 const auto stack_param_count = in_info.get_stack_param_count();
1006 const auto param_count = in_info.get_param_count();
1007
1008 auto stack_offset = in_to_original
1009 ? param_count * 4
1010 : stack_param_count * 4;
1011
1012 if (in_to_original)
1013 {
1014 // Back up protected registers.
1015 for (size_t i = 0; i < register_count; i++)
1016 {
1017 const auto current_register = in_info.get_register(i);
1018
1019 if (!is_protected_register(current_register))
1020 continue;
1021
1022 emit_push(current_register);
1023 stack_offset += 4;
1024 }
1025 }
1026
1027 // Set up arguments.
1028 for (auto i = param_count; i-- > 0;)
1029 {
1030 if (in_info.is_stack_param(i))
1031 {
1032 emit_push(user_register::ESP, stack_offset);
1033 }
1034 else
1035 {
1036 const auto current_register = in_info.get_param_register(i);
1037
1038 if (in_to_original)
1039 {
1040 // Move __cdecl arguments into __usercall registers.
1041 emit_load_store(current_register, user_register::ESP, stack_offset);
1042 stack_offset -= 4;
1043 }
1044 else
1045 {
1046 // Push __usercall registers into __cdecl arguments.
1047 emit_push(current_register);
1048 }
1049 }
1050 }
1051
1052 // Reserve call to hook/original function.
1053 // Uses a far call to return to the original function so we can mutate
1054 // the return address (e.g. user uninstalls the hook inside the hook).
1055 auto branch_offset = emit_branch(true, in_to_original);
1056
1057 if (in_to_original)
1058 {
1059 // Clean up __usercall stack arguments.
1060 if (!in_info.is_userpurge && stack_param_count > 0)
1061 emit_add_subtract(user_register::ESP, stack_param_count * 4);
1062
1063 // Restore protected registers.
1064 for (auto i = register_count; i-- > 0;)
1065 {
1066 const auto current_register = in_info.get_register(i);
1067
1068 if (!is_protected_register(current_register))
1069 continue;
1070
1071 emit_pop(current_register);
1072 }
1073 }
1074
1075 const auto return_register = in_info.get_return_register();
1076
1077 if (return_register != user_register::none && !in_info.is_return_type<void>())
1078 {
1079 // Move __cdecl return value into __usercall return register and vice-versa.
1080 if ((in_info.is_return_type<float>() || in_info.is_return_type<double>()))
1081 {
1082 if (return_register != user_register::ST0)
1083 {
1084 if (in_to_original)
1085 {
1086 emit_move_register(user_register::ST0, return_register, in_info.return_type_size);
1087 }
1088 else
1089 {
1090 emit_move_register(return_register, user_register::ST0, in_info.return_type_size);
1091 }
1092 }
1093 }
1094 else if (return_register != user_register::EAX)
1095 {
1096 if (in_to_original)
1097 {
1098 emit_move_register(user_register::EAX, return_register);
1099 }
1100 else
1101 {
1102 emit_move_register(return_register, user_register::EAX);
1103 }
1104 }
1105 }
1106
1107 if (in_to_original)
1108 {
1109 emit_return();
1110 }
1111 else
1112 {
1113 // Pop register arguments.
1114 for (size_t i = 0; i < param_count; i++)
1115 {
1116 if (in_info.is_stack_param(i))
1117 continue;
1118
1119 const auto current_register = in_info.get_param_register(i);
1120
1121 if (current_register == return_register)
1122 {
1123 emit_add_subtract(user_register::ESP, 4);
1124 continue;
1125 }
1126
1127 emit_pop(current_register);
1128 }
1129
1130 if (stack_param_count > 0)
1131 {
1132 stack_offset = stack_param_count * 4;
1133
1134 // Restore stack pointer for original call.
1135 emit_add_subtract(user_register::ESP, stack_offset);
1136
1137 // Pop stack arguments in __userpurge.
1138 emit_return(in_info.is_userpurge ? stack_offset : 0);
1139 }
1140 else
1141 {
1142 emit_return();
1143 }
1144 }
1145
1146 const auto size = result.size();
1147
1148 auto trampoline = _aligned_malloc(size, sizeof(void*));
1149
1150 memcpy_s(trampoline, size, result.data(), size);
1151
1152 emit_branch_address(trampoline, branch_offset, in_address, in_to_original);
1153
1154 mem::protect(trampoline, size, mem::get_protect_flags(mem::page_protection::rwx));
1155
1156 return trampoline;
1157 }
1158}
#define __CMNLIB_INTERNAL_USER_SIZE_OF(RETURN_TYPE)
Definition hook_usercall_win32.h:14
#define USER_HOOK(RETURN_TYPE, CALLING_CONVENTION, FUNCTION_NAME, ADDRESS, REGISTERS, PARAM_COUNT,...)
Definition hook_usercall_win32.h:131
#define INSTALL_USER_HOOK(FUNCTION_NAME)
Definition hook_usercall_win32.h:159
#define USER_REGISTER(REGISTER)
Definition hook_usercall_win32.h:39
#define USER_RETURN(REGISTER)
Definition hook_usercall_win32.h:47
#define INSTALL_USER_HOOK_EXPLICIT(FUNCTION_NAME, ADDRESS)
Definition hook_usercall_win32.h:171
Definition hook_usercall_win32.h:223
bool is_protected_register(user_register in_register)
Definition hook_usercall_win32.h:454
int get_register_id(user_register in_register)
Definition hook_usercall_win32.h:443
user_register_family get_register_family(user_register in_register)
Definition hook_usercall_win32.h:420
void * emit_user_trampoline(const usercall_info &in_info, void *in_address, bool in_to_original=false)
Definition hook_usercall_win32.h:473
user_register_alias
Definition hook_usercall_win32.h:265
@ ST1
Definition hook_usercall_win32.h:301
@ XMM5
Definition hook_usercall_win32.h:314
@ SPL
Definition hook_usercall_win32.h:284
@ XMM7
Definition hook_usercall_win32.h:316
@ EDX
Definition hook_usercall_win32.h:278
@ BPL
Definition hook_usercall_win32.h:288
@ ST5
Definition hook_usercall_win32.h:305
@ none
Definition hook_usercall_win32.h:266
@ ST3
Definition hook_usercall_win32.h:303
@ XMM0
Definition hook_usercall_win32.h:309
@ ST6
Definition hook_usercall_win32.h:306
@ ESP
Definition hook_usercall_win32.h:286
@ EDI
Definition hook_usercall_win32.h:298
@ EBP
Definition hook_usercall_win32.h:290
@ XMM3
Definition hook_usercall_win32.h:312
@ XMM4
Definition hook_usercall_win32.h:313
@ AX
Definition hook_usercall_win32.h:269
@ CL
Definition hook_usercall_win32.h:272
@ SP
Definition hook_usercall_win32.h:285
@ DX
Definition hook_usercall_win32.h:277
@ ST2
Definition hook_usercall_win32.h:302
@ CX
Definition hook_usercall_win32.h:273
@ EBX
Definition hook_usercall_win32.h:282
@ SIL
Definition hook_usercall_win32.h:292
@ BP
Definition hook_usercall_win32.h:289
@ XMM1
Definition hook_usercall_win32.h:310
@ DIL
Definition hook_usercall_win32.h:296
@ XMM6
Definition hook_usercall_win32.h:315
@ DI
Definition hook_usercall_win32.h:297
@ BL
Definition hook_usercall_win32.h:280
@ ST4
Definition hook_usercall_win32.h:304
@ BX
Definition hook_usercall_win32.h:281
@ ST7
Definition hook_usercall_win32.h:307
@ SI
Definition hook_usercall_win32.h:293
@ ST0
Definition hook_usercall_win32.h:300
@ ESI
Definition hook_usercall_win32.h:294
@ XMM2
Definition hook_usercall_win32.h:311
@ AL
Definition hook_usercall_win32.h:268
@ EAX
Definition hook_usercall_win32.h:270
@ ECX
Definition hook_usercall_win32.h:274
@ DL
Definition hook_usercall_win32.h:276
user_register_family
Definition hook_usercall_win32.h:320
@ FPU
Definition hook_usercall_win32.h:324
@ none
Definition hook_usercall_win32.h:321
@ SSE
Definition hook_usercall_win32.h:325
@ GPR
Definition hook_usercall_win32.h:323
user_register
Definition hook_usercall_win32.h:231
@ ST1
Definition hook_usercall_win32.h:244
@ XMM5
Definition hook_usercall_win32.h:257
@ XMM7
Definition hook_usercall_win32.h:259
@ EDX
Definition hook_usercall_win32.h:236
@ ST5
Definition hook_usercall_win32.h:248
@ none
Definition hook_usercall_win32.h:232
@ ST3
Definition hook_usercall_win32.h:246
@ XMM0
Definition hook_usercall_win32.h:252
@ ST6
Definition hook_usercall_win32.h:249
@ ESP
Definition hook_usercall_win32.h:238
@ EDI
Definition hook_usercall_win32.h:241
@ EBP
Definition hook_usercall_win32.h:239
@ XMM3
Definition hook_usercall_win32.h:255
@ XMM4
Definition hook_usercall_win32.h:256
@ ST2
Definition hook_usercall_win32.h:245
@ EBX
Definition hook_usercall_win32.h:237
@ XMM1
Definition hook_usercall_win32.h:253
@ XMM6
Definition hook_usercall_win32.h:258
@ ST4
Definition hook_usercall_win32.h:247
@ ST7
Definition hook_usercall_win32.h:250
@ ST0
Definition hook_usercall_win32.h:243
@ ESI
Definition hook_usercall_win32.h:240
@ XMM2
Definition hook_usercall_win32.h:254
@ count
Definition hook_usercall_win32.h:261
@ EAX
Definition hook_usercall_win32.h:234
@ ECX
Definition hook_usercall_win32.h:235
Definition mem.h:169
page_protection
Definition mem.h:171
Definition hook_usercall_win32.h:354
void * original_address
Definition hook_usercall_win32.h:358
size_t get_stack_param_count() const
Definition hook_usercall_win32.h:378
user_register get_register(int in_index) const
Definition hook_usercall_win32.h:388
bool is_userpurge
Definition hook_usercall_win32.h:357
std::type_index return_type
Definition hook_usercall_win32.h:355
void * detour_address
Definition hook_usercall_win32.h:359
size_t return_type_size
Definition hook_usercall_win32.h:356
uint8_t param_count
Definition hook_usercall_win32.h:361
size_t get_param_count() const
Definition hook_usercall_win32.h:383
user_register get_return_register() const
Definition hook_usercall_win32.h:393
bool is_return_type() const
Definition hook_usercall_win32.h:404
user_register get_param_register(int in_index) const
Definition hook_usercall_win32.h:398
size_t get_register_count() const
Definition hook_usercall_win32.h:363
uint64_t registers
Definition hook_usercall_win32.h:360
bool is_stack_param(int in_index) const
Definition hook_usercall_win32.h:409
size_t get_register_param_count() const
Definition hook_usercall_win32.h:368