22#include " CoreMinimal.h"
33#include " Reflection/FunctionThunkGenerator.h"
44#include " Traits/MemberFunctionPtrOuter.h"
5+ #include < concepts>
56#include < type_traits>
67
78SML_API DECLARE_LOG_CATEGORY_EXTERN (LogNativeHookManager, Log, Log);
@@ -163,6 +164,9 @@ struct TCallScope;
163164template <typename ReturnType, typename ... ArgTypes>
164165class TCallScopeBase
165166{
167+ template <typename Backend, typename Variant, bool bIsMemberFunction, typename /* ReturnType*/ , typename ... /* ArgTypes*/ >
168+ friend class THookInvokerBase ;
169+
166170 using DerivedCallScope = TCallScope<ReturnType(*)(ArgTypes...)>;
167171 static constexpr bool bHasReturnValue = !std::is_void_v<ReturnType>;
168172
@@ -182,7 +186,9 @@ class TCallScopeBase
182186
183187 ReturnType operator ()(ArgTypes... Args)
184188 {
185- if (FunctionList == nullptr || HandlerIndex >= FunctionList->Num ())
189+ const ArgsPreprocessor<ArgTypes...> ArgsPreprocessor (*this , Args...);
190+
191+ if (FunctionList == nullptr || static_cast <size_t >(HandlerIndex) >= FunctionList->Num ())
186192 {
187193 // Reached the end of the handler list, call the original function.
188194 if constexpr (bHasReturnValue)
@@ -197,8 +203,8 @@ class TCallScopeBase
197203 }
198204 else
199205 {
200- const size_t CurrentHandlerIndex = HandlerIndex++;
201- const size_t NextHandlerIndex = HandlerIndex;
206+ const uint32 CurrentHandlerIndex = HandlerIndex++;
207+ const uint32 NextHandlerIndex = HandlerIndex;
202208
203209 // Call the current handler.
204210 const TSharedPtr<HookCallable>& Handler = (*FunctionList)[CurrentHandlerIndex];
@@ -222,11 +228,61 @@ class TCallScopeBase
222228 {
223229 }
224230
231+ // We need a new variable to store ThisAdjustment, but we can't change the layout of this class
232+ // because we need to maintain compatibility with mods that were compiled before this change. We
233+ // also can't make use of existing padding because that won't necessarily be zero-initialized if an
234+ // old mod creates the scope. Fortunately there's a really hacky way around this: HandlerIndex was
235+ // size_t (64-bits) before, but we're obviously not going to have anywhere near that number of
236+ // handlers, so the upper bits of that variable are always going to be zero. It's now restricted to
237+ // a 32-bit value, which leaves the upper 32-bits for ThisAdjustment, The hacky thing about this is
238+ // that we need to make sure that those upper bits are set back to zero whenever we call into old
239+ // code, as it will be doing full 64-bit operations at that address.
240+ static_assert (PLATFORM_LITTLE_ENDIAN && sizeof (size_t ) == 8 ,
241+ " TCallScope changes aren't backwards-compatible on this platform!" );
242+
225243 const TArray<TSharedPtr<HookCallable>>* FunctionList;
226- size_t HandlerIndex = 0 ;
244+ uint32 HandlerIndex = 0 ; // Used to be size_t
245+ uint32 ThisAdjustment = 0 ;
227246 OrigCallable OrigFunc;
228247 bool bForwardCall = true ;
229248 std::conditional_t <bHasReturnValue, std::remove_cv_t <ReturnType>, std::monostate> ResultData{};
249+
250+ private:
251+ template <typename ... /* ArgTypes */ >
252+ struct ArgsPreprocessor
253+ {
254+ FORCEINLINE ArgsPreprocessor (const TCallScopeBase& Scope, const ArgTypes&...)
255+ {
256+ check (Scope.ThisAdjustment == 0 );
257+ }
258+ };
259+
260+ // Pre-processing for the 'this' pointer in member functions.
261+ // Technically this will also run for non-member functions that have a pointer as their first
262+ // argument, but in those cases ThisAdjustment will be zero so this won't end up doing anything.
263+ template <typename Pointee, typename ... OtherArgTypes>
264+ struct ArgsPreprocessor <Pointee*, OtherArgTypes...>
265+ {
266+ TCallScopeBase& Scope;
267+ const uint32_t SavedThisAdjustment;
268+
269+ FORCEINLINE ArgsPreprocessor (TCallScopeBase& Scope, Pointee*& Ptr, const OtherArgTypes&...)
270+ : Scope(Scope)
271+ , SavedThisAdjustment(Scope.ThisAdjustment)
272+ {
273+ // Handlers un-apply the ThisAdjustment so that they can have sane pointers for their callbacks, but
274+ // we need to re-apply it now as we're about to call into unknown code.
275+ Ptr = reinterpret_cast <Pointee*>(reinterpret_cast <uintptr_t >(Ptr) + SavedThisAdjustment);
276+
277+ // Reset this to zero so it doesn't confuse old mods trying to use HandlerIndex.
278+ Scope.ThisAdjustment = 0 ;
279+ }
280+
281+ FORCEINLINE ~ArgsPreprocessor ()
282+ {
283+ Scope.ThisAdjustment = SavedThisAdjustment;
284+ }
285+ };
230286};
231287
232288// non-void return
@@ -353,6 +409,10 @@ struct FNativeHookResult
353409 // / actual address and not some sort of (member/virtual) function pointer as it will be directly
354410 // / jumped to.
355411 void * OriginalFunctionCode;
412+
413+ // / Offset, in bytes, added to the 'this' pointer before the function was called.
414+ // / Only relevant for non-static member functions, should be set to zero otherwise.
415+ ptrdiff_t ThisAdjustment;
356416};
357417
358418template <typename TCallable, TCallable OriginalFunction>
@@ -378,13 +438,15 @@ struct TStandardHookBackend
378438 static FNativeHookResult RegisterHook (const TCHAR* DebugSymbolName, decltype (GetNullSampleObject()) SampleObjectInstance = nullptr)
379439 {
380440 FNativeHookResult Result;
441+ const FMemberFunctionPointer OriginalFunctionData = ConvertFunctionPointer (OriginalFunction);
381442
382443 Result.Key = FNativeHookManagerInternal::RegisterHookFunction (
383444 DebugSymbolName,
384- ConvertFunctionPointer (OriginalFunction) ,
445+ OriginalFunctionData ,
385446 SampleObjectInstance,
386447 (void *)HookFunction,
387448 &Result.OriginalFunctionCode );
449+ Result.ThisAdjustment = static_cast <ptrdiff_t >(OriginalFunctionData.ThisAdjustment );
388450
389451 return Result;
390452 }
@@ -404,13 +466,15 @@ struct TVtableHookBackend
404466 static FNativeHookResult RegisterHook (const TCHAR* DebugSymbolName, const TMemberFunctionPtrOuter_T<TCallable>* SampleObjectInstance)
405467 {
406468 FNativeHookResult Result;
469+ const FMemberFunctionPointer OriginalFunctionData = ConvertFunctionPointer (OriginalFunction);
407470
408471 Result.Key = FNativeHookManagerInternal::RegisterVtableHook (
409472 DebugSymbolName,
410- ConvertFunctionPointer (OriginalFunction) ,
473+ OriginalFunctionData ,
411474 SampleObjectInstance,
412475 (void *)HookFunction,
413476 &Result.OriginalFunctionCode );
477+ Result.ThisAdjustment = static_cast <ptrdiff_t >(OriginalFunctionData.ThisAdjustment );
414478
415479 return Result;
416480 }
@@ -445,6 +509,7 @@ struct TUFunctionHookBackend
445509 FunctionName,
446510 HookFunction,
447511 (FNativeFuncPtr*)&Result.OriginalFunctionCode );
512+ Result.ThisAdjustment = 0 ;
448513
449514 return Result;
450515 }
@@ -474,19 +539,25 @@ class THookInvokerBase
474539 using HandlerBefore = TFunction<HandlerBeforeSignature>;
475540 using HandlerAfter = TFunction<HandlerAfterSignature>;
476541
477- template <typename ... BackendArgTypes>
478- static auto AddHandlerBefore (HandlerBefore && InHandler, const TCHAR* DebugSymbolName, BackendArgTypes&&... BackendArgs)
542+ template <std::convertible_to<HandlerBefore> InHandlerType, typename ... BackendArgTypes>
543+ static auto AddHandlerBefore (InHandlerType && InHandler, const TCHAR* DebugSymbolName, BackendArgTypes&&... BackendArgs)
479544 {
480545 InstallHook (DebugSymbolName, Forward<BackendArgTypes>(BackendArgs)...);
481- const FDelegateHandle DelegateHandle = InternalAddHandler (MoveTemp (InHandler), *HandlersBefore, *HandlerBeforeReferences);
546+ const FDelegateHandle DelegateHandle = InternalAddHandler (
547+ WrapHandler<HandlerBefore>(Forward<InHandlerType>(InHandler)),
548+ *HandlersBefore,
549+ *HandlerBeforeReferences);
482550 return FNativeHookHandle::TDelayedInit<THookInvokerBase>(DelegateHandle, DebugSymbolName);
483551 }
484552
485- template <typename ... BackendArgTypes>
486- static auto AddHandlerAfter (HandlerAfter && InHandler, const TCHAR* DebugSymbolName, BackendArgTypes&&... BackendArgs)
553+ template <std::convertible_to<HandlerAfter> InHandlerType, typename ... BackendArgTypes>
554+ static auto AddHandlerAfter (InHandlerType && InHandler, const TCHAR* DebugSymbolName, BackendArgTypes&&... BackendArgs)
487555 {
488556 InstallHook (DebugSymbolName, Forward<BackendArgTypes>(BackendArgs)...);
489- const FDelegateHandle DelegateHandle = InternalAddHandler (MoveTemp (InHandler), *HandlersAfter, *HandlerAfterReferences);
557+ const FDelegateHandle DelegateHandle = InternalAddHandler (
558+ WrapHandler<HandlerAfter>(Forward<InHandlerType>(InHandler)),
559+ *HandlersAfter,
560+ *HandlerAfterReferences);
490561 return FNativeHookHandle::TDelayedInit<THookInvokerBase>(DelegateHandle, DebugSymbolName);
491562 }
492563
@@ -522,6 +593,7 @@ class THookInvokerBase
522593 HandlersAfter = &HandlerLists->HandlersAfter ;
523594 HandlerBeforeReferences = &HandlerLists->HandlerBeforeReferences ;
524595 HandlerAfterReferences = &HandlerLists->HandlerAfterReferences ;
596+ ThisAdjustment = Result.ThisAdjustment ;
525597 OriginalFunctionCode = Result.OriginalFunctionCode ;
526598 Key = Result.Key ;
527599 }
@@ -538,6 +610,7 @@ class THookInvokerBase
538610 HandlersAfter = nullptr ;
539611 HandlerBeforeReferences = nullptr ;
540612 HandlerAfterReferences = nullptr ;
613+ ThisAdjustment = 0 ;
541614 OriginalFunctionCode = nullptr ;
542615 Key = nullptr ;
543616 }
@@ -651,10 +724,76 @@ class THookInvokerBase
651724 };
652725#endif
653726
727+ // For some member functions we could end up with a 'this' pointer that has been adjusted to point
728+ // to a base class, which won't be compatible with the derived class pointer that the handler is
729+ // expecting. To work around this, we need to un-adjust the 'this' pointer before calling the
730+ // handler. It's tempting to do this in the hook function itself, we could modify the parameter as
731+ // soon as it's passed in and the new value could be passed on to all of the handlers, but we can't
732+ // do that because we need to be backwards-compatible with old mods; if an old mod is the first to
733+ // register the hook, then they'd define the hook function and they wouldn't know to do that
734+ // adjustment. The only thing that we know we have control over is our own handler, so that's the
735+ // only place where this can be done.
736+ template <typename HandlerType, typename InHandlerType>
737+ static HandlerType WrapHandler (InHandlerType&& InHandler)
738+ {
739+ if constexpr (!bIsMemberFunction)
740+ {
741+ // Non-member functions shouldn't have a ThisAdjustment.
742+ check (ThisAdjustment == 0 );
743+ return Forward<InHandlerType>(InHandler);
744+ }
745+ else if (ThisAdjustment == 0 )
746+ {
747+ // No adjustment, use the input handler as-is.
748+ return Forward<InHandlerType>(InHandler);
749+ }
750+ else if constexpr (std::is_same_v<HandlerType, HandlerBefore>)
751+ {
752+ // HandlerBefore
753+ return [Handler = Forward<InHandlerType>(InHandler)]
754+ <typename Pointee, typename ... OtherArgTypes>
755+ (ScopeType& Scope, Pointee* This, OtherArgTypes&&... OtherArgs)
756+ {
757+ Scope.ThisAdjustment = ThisAdjustment;
758+ This = reinterpret_cast <Pointee*>(reinterpret_cast <uintptr_t >(This) - ThisAdjustment);
759+ Handler (Scope, This, Forward<OtherArgTypes>(OtherArgs)...);
760+ Scope.ThisAdjustment = 0 ;
761+ };
762+ }
763+ else
764+ {
765+ // HandlerAfter
766+ static_assert (std::is_same_v<HandlerType, HandlerAfter>);
767+ if constexpr (std::is_void_v<ReturnType>)
768+ {
769+ // void return
770+ return [Handler = Forward<InHandlerType>(InHandler)]
771+ <typename Pointee, typename ... OtherArgTypes>
772+ (Pointee* This, OtherArgTypes&&... OtherArgs)
773+ {
774+ This = reinterpret_cast <Pointee*>(reinterpret_cast <uintptr_t >(This) - ThisAdjustment);
775+ Handler (This, Forward<OtherArgTypes>(OtherArgs)...);
776+ };
777+ }
778+ else
779+ {
780+ // non-void return
781+ return [Handler = Forward<InHandlerType>(InHandler)]
782+ <typename Pointee, typename ... OtherArgTypes>
783+ (const ReturnType& ReturnValue, Pointee* This, OtherArgTypes&&... OtherArgs)
784+ {
785+ This = reinterpret_cast <Pointee*>(reinterpret_cast <uintptr_t >(This) - ThisAdjustment);
786+ Handler (ReturnValue, This, Forward<OtherArgTypes>(OtherArgs)...);
787+ };
788+ }
789+ }
790+ }
791+
654792 static inline HandlersArray<HandlerBefore>* HandlersBefore;
655793 static inline HandlersArray<HandlerAfter>* HandlersAfter;
656794 static inline HandlersMap<HandlerBefore>* HandlerBeforeReferences;
657795 static inline HandlersMap<HandlerAfter>* HandlerAfterReferences;
796+ static inline ptrdiff_t ThisAdjustment;
658797 static inline void * OriginalFunctionCode;
659798 static inline void * Key;
660799};
0 commit comments