kthook icon indicating copy to clipboard operation
kthook copied to clipboard

What to do with references?

Open RedHolms opened this issue 3 weeks ago • 8 comments

I might be stupid, but this code doesn't work:

class UFunction : ... {
public:
  // Note the reference here
  void Invoke(UObject* obj, FFrame& stack, void* Z_Param__Result);
};

// Results with an address
#define GET_ADDR(...) ...

kthook::kthook_simple<decltype(&UFunction::Invoke)> FuncInvokeHook;

static void Foo() {
  FuncInvokeHook.set_dest(GET_ADDR("?Invoke@UFunction@@QEAAXPEAVUObject@@AEAUFFrame@@QEAX@Z"));
  FuncInvokeHook.set_cb(
    // !!! The problem is here. Neither "FFrame*" nor "FFrame&" doesn't work
    [](auto& hk, UFunction* _this, UObject* obj, FFrame* stack, void* Z_Param__Result) {
      hk.get_trampoline()(_this, obj, *stack, Z_Param__Result);
    }
  );
}

When trying to compile "FFrame*":

In file included from D:\votv-mp\Source\Client\Main.cpp:4:
In file included from D:\votv-mp\build\Debug\_deps\kthook-src\include\kthook/kthook.hpp:54:
D:\votv-mp\build\Debug\_deps\kthook-src\include\kthook\x86_64/kthook_x86_64_detail.hpp(502,16): error: no matching function for call to object of type 'std::function<void (const kthook::kthook_simple<void (UFunction::*)(UObject *, FFrame &, void *)> &, UFunction *&, UObject *&, FFrame *&, void *&)>'
  502 |         return cb(*this_hook, args...);
      |                ^~
D:\votv-mp\build\Debug\_deps\kthook-src\include\kthook\x64/kthook_detail.hpp(157,16): note: in instantiation of function template specialization 'kthook::detail::common_relay<std::function<void (const kthook::kthook_simple<void (UFunction::*)(UObject *, FFrame &, void *)> &, UFunction *&, UObject *&, FFrame *&, void *&)> &, kthook::kthook_simple<void (UFunction::*)(UObject *, FFrame &, void *)>, void, UFunction *, UObject *, FFrame &, void *>' requested here
  157 |         return common_relay<decltype(cb), HookType, Ret, Args...>(cb, this_hook, head_args..., tail_args...);
      |                ^
D:\votv-mp\build\Debug\_deps\kthook-src\include\kthook\x64/kthook_impl.hpp(545,112): note: in instantiation of member function 'kthook::detail::common_relay_generator<kthook::kthook_simple<void (UFunction::*)(UObject *, FFrame &, void *)>, void, std::tuple<UFunction *, UObject *, FFrame &, void *>, std::tuple<>, std::tuple<UFunction *, UObject *, FFrame &, void *>>::relay' requested here
  545 |                 reinterpret_cast<void*>(&detail::common_relay_generator<kthook_simple, Ret, head, tail, Args>::relay);
      |                                                                                                                ^
D:\votv-mp\build\Debug\_deps\kthook-src\include\kthook\x64/kthook_impl.hpp(635,36): note: in instantiation of member function 'kthook::kthook_simple<void (UFunction::*)(UObject *, FFrame &, void *)>::generate_relay_jump' requested here
  635 |                 this->relay_jump = generate_relay_jump();
      |                                    ^
D:\votv-mp\build\Debug\_deps\kthook-src\include\kthook\x64/kthook_impl.hpp(409,22): note: in instantiation of member function 'kthook::kthook_simple<void (UFunction::*)(UObject *, FFrame &, void *)>::patch_hook' requested here
  409 |         installed = !patch_hook(false);
      |                      ^
D:\votv-mp\build\Debug\_deps\kthook-src\include\kthook\x64/kthook_impl.hpp(392,24): note: in instantiation of member function 'kthook::kthook_simple<void (UFunction::*)(UObject *, FFrame &, void *)>::remove' requested here
  392 |     ~kthook_simple() { remove(); }
      |                        ^
D:\votv-mp\Source\Client\Main.cpp(9,53): note: in instantiation of member function 'kthook::kthook_simple<void (UFunction::*)(UObject *, FFrame &, void *)>::~kthook_simple' requested here
    9 | kthook::kthook_simple<decltype(&UFunction::Invoke)> FuncInvokeHook;
      |                                                     ^
C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.44.35207\include\functional(921,10): note: candidate function not viable: no known conversion from 'FFrame' to 'FFrame *&' for 4th argument
  921 |     _Ret operator()(_Types... _Args) const {
      |          ^          ~~~~~~~~~~~~~~~

When trying to compile with "FFrame&":

D:\votv-mp\Source\Client\Main.cpp(43,5): error: no viable conversion from '(lambda at D:\votv-mp\Source\Client\Main.cpp:43:5)' to 'cb_type' (aka 'function<void (const kthook::kthook_simple<void (UFunction::*)(UObject *, FFrame &, void *), kthook::kNone> &, UFunction *&, UObject *&, FFrame *&, void *&)>')
   43 |     [](auto& hk, UFunction* _this, UObject* obj, FFrame& stack, void* Z_Param__Result) {
      |     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   44 |       hk.get_trampoline()(_this, obj, stack, Z_Param__Result);
      |       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   45 |     }
      |     ~
C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.44.35207\include\functional(1096,5): note: candidate constructor not viable: no known conversion from '(lambda at D:\votv-mp\Source\Client\Main.cpp:43:5)' to 'nullptr_t' (aka 'std::nullptr_t') for 1st argument
 1096 |     function(nullptr_t) noexcept {}
      |     ^        ~~~~~~~~~
C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.44.35207\include\functional(1098,5): note: candidate constructor not viable: no known conversion from '(lambda at D:\votv-mp\Source\Client\Main.cpp:43:5)' to 'const function<void (const kthook_simple<void (UFunction::*)(UObject *, FFrame &, void *)> &, UFunction *&, UObject *&, FFrame *&, void *&)> &' for 1st argument
 1098 |     function(const function& _Right) {
      |     ^        ~~~~~~~~~~~~~~~~~~~~~~
C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.44.35207\include\functional(1149,5): note: candidate constructor not viable: no known conversion from '(lambda at D:\votv-mp\Source\Client\Main.cpp:43:5)' to 'function<void (const kthook_simple<void (UFunction::*)(UObject *, FFrame &, void *)> &, UFunction *&, UObject *&, FFrame *&, void *&)> &&' for 1st argument
 1149 |     function(function&& _Right) noexcept {
      |     ^        ~~~~~~~~~~~~~~~~~
C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.44.35207\include\functional(1103,5): note: candidate template ignored: requirement 'conjunction_v<std::negation<std::is_same<(lambda at D:\votv-mp\Source\Client\Main.cpp:43:5), std::function<void (const kthook::kthook_simple<void (UFunction::*)(UObject *, FFrame &, void *), kthook::kNone> &, UFunction *&, UObject *&, FFrame *&, void *&)>>>, std::_Is_invocable_r<void, (lambda at D:\votv-mp\Source\Client\Main.cpp:43:5) &, const kthook::kthook_simple<void (UFunction::*)(UObject *, FFrame &, void *), kthook::kNone> &, UFunction *&, UObject *&, FFrame *&, void *&>>' was not satisfied [with _Fx = (lambda at D:\votv-mp\Source\Client\Main.cpp:43:5)]
 1103 |     function(_Fx&& _Func) {
      |     ^
D:\votv-mp\Source\Client\Main.cpp(43,5): note: candidate template ignored: could not match 'auto (*)(type-parameter-0-0 &, UFunction *, UObject *, FFrame &, void *)' against 'std::function<void (const kthook::kthook_simple<void (UFunction::*)(UObject *, FFrame &, void *)> &, UFunction *&, UObject *&, FFrame *&, void *&)>'
   43 |     [](auto& hk, UFunction* _this, UObject* obj, FFrame& stack, void* Z_Param__Result) {
      |     ^
D:\votv-mp\build\Debug\_deps\kthook-src\include\kthook\x64/kthook_impl.hpp(425,25): note: passing argument to parameter 'callback_' here
  425 |     void set_cb(cb_type callback_) { callback = std::move(callback_); }
      |                         ^
In file included from D:\votv-mp\Source\Client\Main.cpp:4:
In file included from D:\votv-mp\build\Debug\_deps\kthook-src\include\kthook/kthook.hpp:54:
D:\votv-mp\build\Debug\_deps\kthook-src\include\kthook\x86_64/kthook_x86_64_detail.hpp(502,16): error: no matching function for call to object of type 'std::function<void (const kthook::kthook_simple<void (UFunction::*)(UObject *, FFrame &, void *)> &, UFunction *&, UObject *&, FFrame *&, void *&)>'
  502 |         return cb(*this_hook, args...);
      |                ^~
D:\votv-mp\build\Debug\_deps\kthook-src\include\kthook\x64/kthook_detail.hpp(157,16): note: in instantiation of function template specialization 'kthook::detail::common_relay<std::function<void (const kthook::kthook_simple<void (UFunction::*)(UObject *, FFrame &, void *)> &, UFunction *&, UObject *&, FFrame *&, void *&)> &, kthook::kthook_simple<void (UFunction::*)(UObject *, FFrame &, void *)>, void, UFunction *, UObject *, FFrame &, void *>' requested here
  157 |         return common_relay<decltype(cb), HookType, Ret, Args...>(cb, this_hook, head_args..., tail_args...);
      |                ^
D:\votv-mp\build\Debug\_deps\kthook-src\include\kthook\x64/kthook_impl.hpp(545,112): note: in instantiation of member function 'kthook::detail::common_relay_generator<kthook::kthook_simple<void (UFunction::*)(UObject *, FFrame &, void *)>, void, std::tuple<UFunction *, UObject *, FFrame &, void *>, std::tuple<>, std::tuple<UFunction *, UObject *, FFrame &, void *>>::relay' requested here
  545 |                 reinterpret_cast<void*>(&detail::common_relay_generator<kthook_simple, Ret, head, tail, Args>::relay);
      |                                                                                                                ^
D:\votv-mp\build\Debug\_deps\kthook-src\include\kthook\x64/kthook_impl.hpp(635,36): note: in instantiation of member function 'kthook::kthook_simple<void (UFunction::*)(UObject *, FFrame &, void *)>::generate_relay_jump' requested here
  635 |                 this->relay_jump = generate_relay_jump();
      |                                    ^
D:\votv-mp\build\Debug\_deps\kthook-src\include\kthook\x64/kthook_impl.hpp(409,22): note: in instantiation of member function 'kthook::kthook_simple<void (UFunction::*)(UObject *, FFrame &, void *)>::patch_hook' requested here
  409 |         installed = !patch_hook(false);
      |                      ^
D:\votv-mp\build\Debug\_deps\kthook-src\include\kthook\x64/kthook_impl.hpp(392,24): note: in instantiation of member function 'kthook::kthook_simple<void (UFunction::*)(UObject *, FFrame &, void *)>::remove' requested here
  392 |     ~kthook_simple() { remove(); }
      |                        ^
D:\votv-mp\Source\Client\Main.cpp(9,53): note: in instantiation of member function 'kthook::kthook_simple<void (UFunction::*)(UObject *, FFrame &, void *)>::~kthook_simple' requested here
    9 | kthook::kthook_simple<decltype(&UFunction::Invoke)> FuncInvokeHook;
      |                                                     ^
C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.44.35207\include\functional(921,10): note: candidate function not viable: no known conversion from 'FFrame' to 'FFrame *&' for 4th argument
  921 |     _Ret operator()(_Types... _Args) const {

I assume there's some issue with reference-to-pointer conversion:

// Here
using converted_args = typename detail::traits::add_refs_t<detail::traits::convert_refs_t<Args>>;

Or I can just do things wrong

RedHolms avatar Dec 23 '25 17:12 RedHolms

Seems like issue is present only for kthook_simple.

Simple hook generates relay with NOT converted args, but it should pass converted args instead

RedHolms avatar Dec 23 '25 17:12 RedHolms

Seems like even kthook_signal has issues with references. Same code as above but with kthook_signal and even without any callback just crashes the game.

I could pass pointer instead of reference to hook type, but with current architecture this will be nasty crutch

RedHolms avatar Dec 23 '25 17:12 RedHolms

Here's function assembly if it can help:

.text:00000000012E7680 ; __int64 __fastcall UFunction::Invoke(UFunction *this, struct UObject *InterfaceAddress, struct FFrame *, void *const)
.text:00000000012E7680 ?Invoke@UFunction@@QEAAXPEAVUObject@@AEAUFFrame@@QEAX@Z proc near
.text:00000000012E7680                                         ; CODE XREF: sub_143BC50+20D↓p
.text:00000000012E7680                                         ; sub_144A1C0+2E0↓p ...
.text:00000000012E7680
.text:00000000012E7680 arg_0           = qword ptr  8
.text:00000000012E7680 arg_8           = qword ptr  10h
.text:00000000012E7680 arg_10          = qword ptr  18h
.text:00000000012E7680 arg_18          = qword ptr  20h
.text:00000000012E7680
.text:00000000012E7680                 mov     [rsp+arg_0], rbx
.text:00000000012E7685                 mov     [rsp+arg_8], rbp
.text:00000000012E768A                 mov     [rsp+arg_10], rsi
.text:00000000012E768F                 mov     [rsp+arg_18], rdi
.text:00000000012E7694                 push    r14
.text:00000000012E7696                 sub     rsp, 20h
.text:00000000012E769A                 mov     rbx, [rcx+20h]
.text:00000000012E769E                 mov     r14, r9
.text:00000000012E76A1                 mov     rdi, r8
.text:00000000012E76A4                 mov     rsi, rdx
.text:00000000012E76A7                 mov     rbp, rcx
.text:00000000012E76AA                 call    ?GetPrivateStaticClass@UInterface@@CAPEAVUClass@@XZ ; UInterface::GetPrivateStaticClass(void)
.text:00000000012E76AF                 test    rax, rax
.text:00000000012E76B2                 jz      short loc_12E76DC
.text:00000000012E76B4                 lea     rdx, [rax+30h]
.text:00000000012E76B8                 movsxd  rax, dword ptr [rax+38h]
.text:00000000012E76BC                 cmp     eax, [rbx+38h]
.text:00000000012E76BF                 jg      short loc_12E76DC
.text:00000000012E76C1                 mov     r8, rax
.text:00000000012E76C4                 mov     rax, [rbx+30h]
.text:00000000012E76C8                 cmp     [rax+r8*8], rdx
.text:00000000012E76CC                 jnz     short loc_12E76DC
.text:00000000012E76CE                 mov     rdx, rbx
.text:00000000012E76D1                 mov     rcx, rsi
.text:00000000012E76D4                 call    ?GetInterfaceAddress@UObjectBaseUtility@@QEAAPEAXPEAVUClass@@@Z ; UObjectBaseUtility::GetInterfaceAddress(UClass *)
.text:00000000012E76D9                 mov     rsi, rax
.text:00000000012E76DC
.text:00000000012E76DC loc_12E76DC:                            ; CODE XREF: UFunction::Invoke(UObject *,FFrame &,void * const)+32↑j
.text:00000000012E76DC                                         ; UFunction::Invoke(UObject *,FFrame &,void * const)+3F↑j ...
.text:00000000012E76DC                 mov     rbx, [rdi+88h]
.text:00000000012E76E3                 mov     r8, r14
.text:00000000012E76E6                 mov     rdx, rdi
.text:00000000012E76E9                 mov     [rdi+88h], rbp
.text:00000000012E76F0                 mov     rcx, rsi
.text:00000000012E76F3                 call    qword ptr [rbp+0D8h]
.text:00000000012E76F9                 mov     rbp, [rsp+28h+arg_8]
.text:00000000012E76FE                 mov     rsi, [rsp+28h+arg_10]
.text:00000000012E7703                 mov     [rdi+88h], rbx
.text:00000000012E770A                 mov     rbx, [rsp+28h+arg_0]
.text:00000000012E770F                 mov     rdi, [rsp+28h+arg_18]
.text:00000000012E7714                 add     rsp, 20h
.text:00000000012E7718                 pop     r14
.text:00000000012E771A                 retn
.text:00000000012E771A ?Invoke@UFunction@@QEAAXPEAVUObject@@AEAUFFrame@@QEAX@Z endp

RedHolms avatar Dec 23 '25 17:12 RedHolms

Using references is not well tested, but should be working. Ty for MRE, i’ll look into your issue today

kin4stat avatar Dec 23 '25 19:12 kin4stat

All references are converted to pointers, so try this:

class UFunction : ... {
public:
  // Note the reference here
  void Invoke(UObject* obj, FFrame& stack, void* Z_Param__Result);
};

// Results with an address
#define GET_ADDR(...) ...

kthook::kthook_simple<decltype(&UFunction::Invoke)> FuncInvokeHook;

static void Foo() {
  FuncInvokeHook.set_dest(GET_ADDR("?Invoke@UFunction@@QEAAXPEAVUObject@@AEAUFFrame@@QEAX@Z"));
  FuncInvokeHook.set_cb(
    // !!! The problem is here. Neither "FFrame*" nor "FFrame&" doesn't work
    [](auto& hk, UFunction* _this, UObject* obj, FFrame* stack, void* Z_Param__Result) {
      hk.get_trampoline()(_this, obj, stack, Z_Param__Result);
    }
  );
}

kin4stat avatar Dec 23 '25 20:12 kin4stat

Вы вроде русский, так что для лучшего понимания буду на русском

Проблема в том, что по какой-то причине вроде kthook не думает, что эта функция берёт параметры через регистры (судя по сгенерированному relay jump): Image Image

Еще больше это подтверждает то, что для другой функции всё работает исправно (структура InitializationValues всего 8 байт, следовательно помещается в регистр): Image

Причём такая ошибка с первой функцией не уходит, даже если заменить ссылку на указатель у FFrame. Скорее всего корень ошибки где-то здесь

RedHolms avatar Dec 23 '25 20:12 RedHolms

Скорее всего проблема именно из-за ровно 4-х аргументов. Из-за чего конкретно в коде я понять не смог, но нужно копать в этом направлении

RedHolms avatar Dec 23 '25 21:12 RedHolms

Вероятнее всего ты допустил ошибку с выравниванием стека или shadow space (32 байта после аргументов на стеке), потому что пока я делал свою реализацию именно эти два момента доставили проблем.

Но я смог сделать небольшой работающий аналог, который не крашит. Ниже приложу архив с кодом, там всего 5 файлов. Частично брал твой код (конкретно для генерации трамплинов и аллокации памяти).

https://disk.yandex.ru/d/su00sQ33N45rQA

RedHolms avatar Dec 26 '25 19:12 RedHolms