From 1d2667b1062c599a4956b13bef4f36c1a5ea309c Mon Sep 17 00:00:00 2001 From: Iain Mckay Date: Fri, 26 Jul 2019 01:12:28 +0200 Subject: [PATCH] Improvements to allow control of when parent implementations of vtable hacks are called --- .../FLifetimePropertyCollection.cs | 5 + .../CoreUObject/UObject.cs | 17 ++- .../Engine/ActorComponent_Injected.cs | 8 ++ .../InjectedClasses/Engine/Actor_Injected.cs | 10 +- .../InjectedClasses/Engine/Pawn_Injected.cs | 10 +- .../Engine/PlayerController_Injected.cs | 4 + .../Internal/VTableHacks.cs | 124 ++++++++---------- 7 files changed, 101 insertions(+), 77 deletions(-) diff --git a/Managed/UnrealEngine.Runtime/UnrealEngine.Runtime/CoreUObject/FLifetimePropertyCollection.cs b/Managed/UnrealEngine.Runtime/UnrealEngine.Runtime/CoreUObject/FLifetimePropertyCollection.cs index 92f96d6..5792b9b 100644 --- a/Managed/UnrealEngine.Runtime/UnrealEngine.Runtime/CoreUObject/FLifetimePropertyCollection.cs +++ b/Managed/UnrealEngine.Runtime/UnrealEngine.Runtime/CoreUObject/FLifetimePropertyCollection.cs @@ -11,6 +11,11 @@ public sealed class FLifetimePropertyCollection private IntPtr nativeClass; private TArrayUnsafeRef dest; + internal IntPtr Address + { + get { return dest.Address; } + } + internal FLifetimePropertyCollection(IntPtr obj, TArrayUnsafeRef dest) { this.dest = dest; diff --git a/Managed/UnrealEngine.Runtime/UnrealEngine.Runtime/CoreUObject/UObject.cs b/Managed/UnrealEngine.Runtime/UnrealEngine.Runtime/CoreUObject/UObject.cs index f208e5e..53f97af 100644 --- a/Managed/UnrealEngine.Runtime/UnrealEngine.Runtime/CoreUObject/UObject.cs +++ b/Managed/UnrealEngine.Runtime/UnrealEngine.Runtime/CoreUObject/UObject.cs @@ -644,16 +644,29 @@ public bool IsDefaultSubobject() return Native_UObjectBaseUtility.IsDefaultSubobject(Address); } + private VTableHacks.CachedFunctionRedirect getLifetimeReplicatedPropsRedirect; + internal virtual void GetLifetimeReplicatedPropsInternal(IntPtr arrayAddress) + { + using (TArrayUnsafeRef lifetimePropsUnsafe = new TArrayUnsafeRef(arrayAddress)) + { + FLifetimePropertyCollection lifetimeProps = new FLifetimePropertyCollection(Address, lifetimePropsUnsafe); + GetLifetimeReplicatedProps(lifetimeProps); + } + } + /// /// Returns properties that are replicated for the lifetime of the actor channel /// public virtual void GetLifetimeReplicatedProps(FLifetimePropertyCollection lifetimeProps) { + getLifetimeReplicatedPropsRedirect + .Resolve(VTableHacks.GetLifetimeReplicatedProps, this) + .Invoke(Address, lifetimeProps.Address); } - internal virtual void SetupPlayerInputComponent(IntPtr playerInputComponent) + internal virtual void SetupPlayerInputComponentInternal(IntPtr playerInputComponent) { - // This is eventually implemented in APawn + // This is eventually implemented in injected classes } internal virtual void BeginPlayInternal() diff --git a/Managed/UnrealEngine.Runtime/UnrealEngine.Runtime/Internal/InjectedClasses/Engine/ActorComponent_Injected.cs b/Managed/UnrealEngine.Runtime/UnrealEngine.Runtime/Internal/InjectedClasses/Engine/ActorComponent_Injected.cs index 157ba77..62d37aa 100644 --- a/Managed/UnrealEngine.Runtime/UnrealEngine.Runtime/Internal/InjectedClasses/Engine/ActorComponent_Injected.cs +++ b/Managed/UnrealEngine.Runtime/UnrealEngine.Runtime/Internal/InjectedClasses/Engine/ActorComponent_Injected.cs @@ -41,11 +41,13 @@ public void UnregisterComponent() Native_UActorComponent.UnregisterComponent(this.Address); } + private VTableHacks.CachedFunctionRedirect beginPlayRedirect; internal override void BeginPlayInternal() { BeginPlay(); } + private VTableHacks.CachedFunctionRedirect endPlayRedirect; internal override void EndPlayInternal(byte endPlayReason) { EndPlay((EEndPlayReason) endPlayReason); @@ -58,6 +60,9 @@ internal override void EndPlayInternal(byte endPlayReason) /// public virtual void BeginPlay() { + beginPlayRedirect + .Resolve(VTableHacks.ActorComponentBeginPlay, this) + .Invoke(Address); } /// @@ -67,6 +72,9 @@ public virtual void BeginPlay() /// public virtual void EndPlay(EEndPlayReason endPlayReason) { + endPlayRedirect + .Resolve(VTableHacks.ActorComponentEndPlay, this) + .Invoke(Address, (byte) endPlayReason); } } } diff --git a/Managed/UnrealEngine.Runtime/UnrealEngine.Runtime/Internal/InjectedClasses/Engine/Actor_Injected.cs b/Managed/UnrealEngine.Runtime/UnrealEngine.Runtime/Internal/InjectedClasses/Engine/Actor_Injected.cs index 1c35f76..ff5f829 100644 --- a/Managed/UnrealEngine.Runtime/UnrealEngine.Runtime/Internal/InjectedClasses/Engine/Actor_Injected.cs +++ b/Managed/UnrealEngine.Runtime/UnrealEngine.Runtime/Internal/InjectedClasses/Engine/Actor_Injected.cs @@ -71,14 +71,16 @@ static void LoadNativeTypeInjected(IntPtr classAddress) PrimaryActorTick_Offset = NativeReflectionCached.GetPropertyOffset(classAddress, "PrimaryActorTick"); } + private VTableHacks.CachedFunctionRedirect beginPlayRedirect; internal override void BeginPlayInternal() { BeginPlay(); } + private VTableHacks.CachedFunctionRedirect endPlayRedirect; internal override void EndPlayInternal(byte endPlayReason) { - EndPlay((EEndPlayReason)endPlayReason); + EndPlay((EEndPlayReason) endPlayReason); } /// @@ -86,6 +88,9 @@ internal override void EndPlayInternal(byte endPlayReason) /// protected virtual void BeginPlay() { + beginPlayRedirect + .Resolve(VTableHacks.ActorBeginPlay, this) + .Invoke(Address); } /// @@ -94,6 +99,9 @@ protected virtual void BeginPlay() /// public virtual void EndPlay(EEndPlayReason endPlayReason) { + endPlayRedirect + .Resolve(VTableHacks.ActorEndPlay, this) + .Invoke(Address, (byte) endPlayReason); } /// diff --git a/Managed/UnrealEngine.Runtime/UnrealEngine.Runtime/Internal/InjectedClasses/Engine/Pawn_Injected.cs b/Managed/UnrealEngine.Runtime/UnrealEngine.Runtime/Internal/InjectedClasses/Engine/Pawn_Injected.cs index 4cb1ecd..8da191c 100644 --- a/Managed/UnrealEngine.Runtime/UnrealEngine.Runtime/Internal/InjectedClasses/Engine/Pawn_Injected.cs +++ b/Managed/UnrealEngine.Runtime/UnrealEngine.Runtime/Internal/InjectedClasses/Engine/Pawn_Injected.cs @@ -6,16 +6,22 @@ namespace UnrealEngine.Engine { public partial class APawn : UnrealEngine.Engine.AActor { - internal override void SetupPlayerInputComponent(IntPtr playerInputComponentAddress) + private VTableHacks.CachedFunctionRedirect setupPlayerInputComponentRedirect; + internal override void SetupPlayerInputComponentInternal(IntPtr playerInputComponentAddress) { UInputComponent playerInputComponent = GCHelper.Find(playerInputComponentAddress); SetupPlayerInputComponent(playerInputComponent); } + /// + /// Allows a Pawn to set up custom input bindings. Called upon possession by a PlayerController, using the InputComponent created by CreatePlayerInputComponent(). + /// protected virtual void SetupPlayerInputComponent(UInputComponent playerInputComponent) { - + setupPlayerInputComponentRedirect + .Resolve(VTableHacks.PawnSetupPlayerInputComponent, this) + .Invoke(Address, playerInputComponent.Address); } } } diff --git a/Managed/UnrealEngine.Runtime/UnrealEngine.Runtime/Internal/InjectedClasses/Engine/PlayerController_Injected.cs b/Managed/UnrealEngine.Runtime/UnrealEngine.Runtime/Internal/InjectedClasses/Engine/PlayerController_Injected.cs index 54c90a2..3738f52 100644 --- a/Managed/UnrealEngine.Runtime/UnrealEngine.Runtime/Internal/InjectedClasses/Engine/PlayerController_Injected.cs +++ b/Managed/UnrealEngine.Runtime/UnrealEngine.Runtime/Internal/InjectedClasses/Engine/PlayerController_Injected.cs @@ -7,6 +7,7 @@ namespace UnrealEngine.Engine { public partial class APlayerController : AController { + private VTableHacks.CachedFunctionRedirect setupInputComponentRedirect; internal override void SetupInputComponentInternal() { SetupInputComponent(); @@ -17,6 +18,9 @@ internal override void SetupInputComponentInternal() /// protected virtual void SetupInputComponent() { + setupInputComponentRedirect + .Resolve(VTableHacks.PlayerControllerSetupInputComponent, this) + .Invoke(Address); } } } diff --git a/Managed/UnrealEngine.Runtime/UnrealEngine.Runtime/Internal/VTableHacks.cs b/Managed/UnrealEngine.Runtime/UnrealEngine.Runtime/Internal/VTableHacks.cs index acc2cac..8133868 100644 --- a/Managed/UnrealEngine.Runtime/UnrealEngine.Runtime/Internal/VTableHacks.cs +++ b/Managed/UnrealEngine.Runtime/UnrealEngine.Runtime/Internal/VTableHacks.cs @@ -20,13 +20,13 @@ private static void AddVTableRedirects() IntPtr actorComponentClass = Runtime.Classes.UActorComponent; IntPtr playerControllerClass = Runtime.Classes.APlayerController; - repProps = AddVTableRedirect(objectClass, "DummyRepProps", new GetLifetimeReplicatedPropsDel(OnGetLifetimeReplicatedProps)); - setupPlayerInput = AddVTableRedirect(pawnClass, "DummySetupPlayerInput", new SetupPlayerInputComponentDel(OnSetupPlayerInputComponent)); - actorBeginPlay = AddVTableRedirect(actorClass, "DummyActorBeginPlay", new ActorBeginPlayDel(OnActorBeginPlay)); - actorEndPlay = AddVTableRedirect(actorClass, "DummyActorEndPlay", new ActorEndPlayDel(OnActorEndPlay)); - actorComponentBeginPlay = AddVTableRedirect(actorComponentClass, "DummyActorComponentBeginPlay", new ActorComponentBeginPlayDel(OnActorComponentBeginPlay)); - actorComponentEndPlay = AddVTableRedirect(actorComponentClass, "DummyActorComponentEndPlay", new ActorComponentEndPlayDel(OnActorComponentEndPlay)); - playerControllerSetupInputComponent = AddVTableRedirect(playerControllerClass, "DummyPlayerControllerSetupInputComponent", new PlayerControllerSetupInputComponentDel(OnPlayerControllerSetupInputComponent)); + GetLifetimeReplicatedProps = AddVTableRedirect(objectClass, "DummyRepProps", new GetLifetimeReplicatedPropsDel(OnGetLifetimeReplicatedProps)); + PawnSetupPlayerInputComponent = AddVTableRedirect(pawnClass, "DummySetupPlayerInput", new PawnSetupPlayerInputComponentDel(OnPawnSetupPlayerInputComponent)); + ActorBeginPlay = AddVTableRedirect(actorClass, "DummyActorBeginPlay", new BeginPlayDel(OnActorBeginPlay)); + ActorEndPlay = AddVTableRedirect(actorClass, "DummyActorEndPlay", new EndPlayDel(OnActorEndPlay)); + ActorComponentBeginPlay = AddVTableRedirect(actorComponentClass, "DummyActorComponentBeginPlay", new BeginPlayDel(OnActorComponentBeginPlay)); + ActorComponentEndPlay = AddVTableRedirect(actorComponentClass, "DummyActorComponentEndPlay", new EndPlayDel(OnActorComponentEndPlay)); + PlayerControllerSetupInputComponent = AddVTableRedirect(playerControllerClass, "DummyPlayerControllerSetupInputComponent", new PlayerControllerSetupInputComponentDel(OnPlayerControllerSetupInputComponent)); } private static void LogCallbackException(string functionName, Exception e) @@ -34,25 +34,16 @@ private static void LogCallbackException(string functionName, Exception e) FMessage.LogException(e, "vtable func"); } - private static FunctionRedirect repProps; + public static FunctionRedirect GetLifetimeReplicatedProps { get; private set; } delegate void GetLifetimeReplicatedPropsDel(IntPtr address, IntPtr arrayAddress); [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - delegate void GetLifetimeReplicatedPropsDel_ThisCall(IntPtr address, IntPtr arrayAddress); + public delegate void GetLifetimeReplicatedPropsDel_ThisCall(IntPtr address, IntPtr arrayAddress); private static void OnGetLifetimeReplicatedProps(IntPtr address, IntPtr arrayAddress) { try { UObject obj = GCHelper.Find(address); - - GetLifetimeReplicatedPropsDel_ThisCall original = repProps.GetOriginal(obj); - original(address, arrayAddress); - //Native_VTableHacks.CallOriginal_GetLifetimeReplicatedProps(original, address, arrayAddress); - - using (TArrayUnsafeRef lifetimePropsUnsafe = new TArrayUnsafeRef(arrayAddress)) - { - FLifetimePropertyCollection lifetimeProps = new FLifetimePropertyCollection(address, lifetimePropsUnsafe); - obj.GetLifetimeReplicatedProps(lifetimeProps); - } + obj.GetLifetimeReplicatedPropsInternal(arrayAddress); } catch (Exception e) { @@ -60,42 +51,32 @@ private static void OnGetLifetimeReplicatedProps(IntPtr address, IntPtr arrayAdd } } - private static FunctionRedirect setupPlayerInput; - delegate void SetupPlayerInputComponentDel(IntPtr address, IntPtr inputComponentAddress); + public static FunctionRedirect PawnSetupPlayerInputComponent { get; private set; } + delegate void PawnSetupPlayerInputComponentDel(IntPtr address, IntPtr inputComponentAddress); [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - delegate void SetupPlayerInputComponentDel_ThisCall(IntPtr address, IntPtr inputComponentAddress); - private static void OnSetupPlayerInputComponent(IntPtr address, IntPtr inputComponentAddress) + public delegate void PawnSetupPlayerInputComponentDel_ThisCall(IntPtr address, IntPtr inputComponentAddress); + private static void OnPawnSetupPlayerInputComponent(IntPtr address, IntPtr inputComponentAddress) { try { UObject obj = GCHelper.Find(address); - - SetupPlayerInputComponentDel_ThisCall original = setupPlayerInput.GetOriginal(obj); - original(address, inputComponentAddress); - //Native_VTableHacks.CallOriginal_SetupPlayerInputComponent(original, address, inputComponentAddress); - - obj.SetupPlayerInputComponent(inputComponentAddress); + obj.SetupPlayerInputComponentInternal(inputComponentAddress); } catch (Exception e) { - LogCallbackException(nameof(OnSetupPlayerInputComponent), e); + LogCallbackException(nameof(OnPawnSetupPlayerInputComponent), e); } } - private static FunctionRedirect actorBeginPlay; - delegate void ActorBeginPlayDel(IntPtr address); + public static FunctionRedirect ActorBeginPlay { get; private set; } + delegate void BeginPlayDel(IntPtr address); [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - delegate void ActorBeginPlayDel_ThisCall(IntPtr address); + public delegate void BeginPlayDel_ThisCall(IntPtr address); private static void OnActorBeginPlay(IntPtr address) { try { UObject obj = GCHelper.Find(address); - - ActorBeginPlayDel_ThisCall original = actorBeginPlay.GetOriginal(obj); - original(address); - //Native_VTableHacks.CallOriginal_ActorBeginPlay(original, address); - obj.BeginPlayInternal(); } catch (Exception e) @@ -104,20 +85,15 @@ private static void OnActorBeginPlay(IntPtr address) } } - private static FunctionRedirect actorEndPlay; - delegate void ActorEndPlayDel(IntPtr address, byte endPlayReason); + public static FunctionRedirect ActorEndPlay { get; private set; } + delegate void EndPlayDel(IntPtr address, byte endPlayReason); [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - delegate void ActorEndPlayDel_ThisCall(IntPtr address, byte endPlayReason); + public delegate void EndPlayDel_ThisCall(IntPtr address, byte endPlayReason); private static void OnActorEndPlay(IntPtr address, byte endPlayReason) { try { UObject obj = GCHelper.Find(address); - - ActorEndPlayDel_ThisCall original = actorEndPlay.GetOriginal(obj); - original(address, endPlayReason); - //Native_VTableHacks.CallOriginal_ActorEndPlay(original, address, endPlayReason); - obj.EndPlayInternal(endPlayReason); } catch (Exception e) @@ -126,20 +102,12 @@ private static void OnActorEndPlay(IntPtr address, byte endPlayReason) } } - private static FunctionRedirect actorComponentBeginPlay; - delegate void ActorComponentBeginPlayDel(IntPtr address); - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - delegate void ActorComponentBeginPlayDel_ThisCall(IntPtr address); + public static FunctionRedirect ActorComponentBeginPlay { get; private set; } private static void OnActorComponentBeginPlay(IntPtr address) { try { UObject obj = GCHelper.Find(address); - - ActorComponentBeginPlayDel_ThisCall original = actorComponentBeginPlay.GetOriginal(obj); - original(address); - //Native_VTableHacks.CallOriginal_ActorComponentBeginPlay(original, address); - obj.BeginPlayInternal(); } catch (Exception e) @@ -148,20 +116,12 @@ private static void OnActorComponentBeginPlay(IntPtr address) } } - private static FunctionRedirect actorComponentEndPlay; - delegate void ActorComponentEndPlayDel(IntPtr address, byte endPlayReason); - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - delegate void ActorComponentEndPlayDel_ThisCall(IntPtr address, byte endPlayReason); + public static FunctionRedirect ActorComponentEndPlay { get; private set; } private static void OnActorComponentEndPlay(IntPtr address, byte endPlayReason) { try { UObject obj = GCHelper.Find(address); - - ActorComponentEndPlayDel_ThisCall original = actorComponentEndPlay.GetOriginal(obj); - original(address, endPlayReason); - //Native_VTableHacks.CallOriginal_ActorComponentEndPlay(original, - obj.EndPlayInternal(endPlayReason); } catch (Exception e) @@ -170,20 +130,15 @@ private static void OnActorComponentEndPlay(IntPtr address, byte endPlayReason) } } - private static FunctionRedirect playerControllerSetupInputComponent; + public static FunctionRedirect PlayerControllerSetupInputComponent { get; private set; } delegate void PlayerControllerSetupInputComponentDel(IntPtr address); [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - delegate void PlayerControllerSetupInputComponentDel_ThisCall(IntPtr address); + public delegate void PlayerControllerSetupInputComponentDel_ThisCall(IntPtr address); private static void OnPlayerControllerSetupInputComponent(IntPtr address) { try { UObject obj = GCHelper.Find(address); - - PlayerControllerSetupInputComponentDel_ThisCall original = playerControllerSetupInputComponent.GetOriginal(obj); - original(address); - //Native_VTableHacks.CallOriginal_PlayerControllerSetupInputComponent(original) - obj.SetupInputComponentInternal(); } catch (Exception e) @@ -196,7 +151,7 @@ private static void OnPlayerControllerSetupInputComponent(IntPtr address) // Add vtable redirects above this line //////////////////////////////////////////////////////////////////////////////////////// - class FunctionRedirect + public class FunctionRedirect { public IntPtr Class; public int VTableIndex; @@ -434,5 +389,30 @@ private static unsafe IntPtr FindOriginalVTableOwner(IntPtr baseMostClass, IntPt } return originalOwner; } + + public struct CachedFunctionRedirect where T : class + { + private T cachedFunc; + + public CachedFunctionRedirect(UObject obj) + { + cachedFunc = null; + } + + public T Resolve(FunctionRedirect functionRedirect, UObject obj) + { + if (cachedFunc == null) + { + cachedFunc = functionRedirect.GetOriginal(obj); + } + + if (cachedFunc == null) + { + throw new Exception("FunctionRedirect did not result in a function pointer"); + } + + return cachedFunc; + } + } } }