Skip to content
This repository has been archived by the owner on Oct 19, 2020. It is now read-only.

Commit

Permalink
Merge pull request #103 from iainmckay/vtable-hack-control
Browse files Browse the repository at this point in the history
Improvements to allow control of when parent implementations of vtable hacks are called
  • Loading branch information
pixeltris authored Jul 27, 2019
2 parents 24417e3 + 1d2667b commit 8e6ef70
Show file tree
Hide file tree
Showing 7 changed files with 101 additions and 77 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ public sealed class FLifetimePropertyCollection
private IntPtr nativeClass;
private TArrayUnsafeRef<FLifetimeProperty> dest;

internal IntPtr Address
{
get { return dest.Address; }
}

internal FLifetimePropertyCollection(IntPtr obj, TArrayUnsafeRef<FLifetimeProperty> dest)
{
this.dest = dest;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -644,16 +644,29 @@ public bool IsDefaultSubobject()
return Native_UObjectBaseUtility.IsDefaultSubobject(Address);
}

private VTableHacks.CachedFunctionRedirect<VTableHacks.GetLifetimeReplicatedPropsDel_ThisCall> getLifetimeReplicatedPropsRedirect;
internal virtual void GetLifetimeReplicatedPropsInternal(IntPtr arrayAddress)
{
using (TArrayUnsafeRef<FLifetimeProperty> lifetimePropsUnsafe = new TArrayUnsafeRef<FLifetimeProperty>(arrayAddress))
{
FLifetimePropertyCollection lifetimeProps = new FLifetimePropertyCollection(Address, lifetimePropsUnsafe);
GetLifetimeReplicatedProps(lifetimeProps);
}
}

/// <summary>
/// Returns properties that are replicated for the lifetime of the actor channel
/// </summary>
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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,13 @@ public void UnregisterComponent()
Native_UActorComponent.UnregisterComponent(this.Address);
}

private VTableHacks.CachedFunctionRedirect<VTableHacks.BeginPlayDel_ThisCall> beginPlayRedirect;
internal override void BeginPlayInternal()
{
BeginPlay();
}

private VTableHacks.CachedFunctionRedirect<VTableHacks.EndPlayDel_ThisCall> endPlayRedirect;
internal override void EndPlayInternal(byte endPlayReason)
{
EndPlay((EEndPlayReason) endPlayReason);
Expand All @@ -58,6 +60,9 @@ internal override void EndPlayInternal(byte endPlayReason)
/// </summary>
public virtual void BeginPlay()
{
beginPlayRedirect
.Resolve(VTableHacks.ActorComponentBeginPlay, this)
.Invoke(Address);
}

/// <summary>
Expand All @@ -67,6 +72,9 @@ public virtual void BeginPlay()
/// <param name="endPlayReason"></param>
public virtual void EndPlay(EEndPlayReason endPlayReason)
{
endPlayRedirect
.Resolve(VTableHacks.ActorComponentEndPlay, this)
.Invoke(Address, (byte) endPlayReason);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -71,21 +71,26 @@ static void LoadNativeTypeInjected(IntPtr classAddress)
PrimaryActorTick_Offset = NativeReflectionCached.GetPropertyOffset(classAddress, "PrimaryActorTick");
}

private VTableHacks.CachedFunctionRedirect<VTableHacks.BeginPlayDel_ThisCall> beginPlayRedirect;
internal override void BeginPlayInternal()
{
BeginPlay();
}

private VTableHacks.CachedFunctionRedirect<VTableHacks.EndPlayDel_ThisCall> endPlayRedirect;
internal override void EndPlayInternal(byte endPlayReason)
{
EndPlay((EEndPlayReason)endPlayReason);
EndPlay((EEndPlayReason) endPlayReason);
}

/// <summary>
/// Overridable native event for when play begins for this actor.
/// </summary>
protected virtual void BeginPlay()
{
beginPlayRedirect
.Resolve(VTableHacks.ActorBeginPlay, this)
.Invoke(Address);
}

/// <summary>
Expand All @@ -94,6 +99,9 @@ protected virtual void BeginPlay()
/// <param name="endPlayReason"></param>
public virtual void EndPlay(EEndPlayReason endPlayReason)
{
endPlayRedirect
.Resolve(VTableHacks.ActorEndPlay, this)
.Invoke(Address, (byte) endPlayReason);
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,22 @@ namespace UnrealEngine.Engine
{
public partial class APawn : UnrealEngine.Engine.AActor
{
internal override void SetupPlayerInputComponent(IntPtr playerInputComponentAddress)
private VTableHacks.CachedFunctionRedirect<VTableHacks.PawnSetupPlayerInputComponentDel_ThisCall> setupPlayerInputComponentRedirect;
internal override void SetupPlayerInputComponentInternal(IntPtr playerInputComponentAddress)
{
UInputComponent playerInputComponent = GCHelper.Find<UInputComponent>(playerInputComponentAddress);

SetupPlayerInputComponent(playerInputComponent);
}

/// <summary>
/// Allows a Pawn to set up custom input bindings. Called upon possession by a PlayerController, using the InputComponent created by CreatePlayerInputComponent().
/// </summary>
protected virtual void SetupPlayerInputComponent(UInputComponent playerInputComponent)
{

setupPlayerInputComponentRedirect
.Resolve(VTableHacks.PawnSetupPlayerInputComponent, this)
.Invoke(Address, playerInputComponent.Address);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ namespace UnrealEngine.Engine
{
public partial class APlayerController : AController
{
private VTableHacks.CachedFunctionRedirect<VTableHacks.PlayerControllerSetupInputComponentDel_ThisCall> setupInputComponentRedirect;
internal override void SetupInputComponentInternal()
{
SetupInputComponent();
Expand All @@ -17,6 +18,9 @@ internal override void SetupInputComponentInternal()
/// </summary>
protected virtual void SetupInputComponent()
{
setupInputComponentRedirect
.Resolve(VTableHacks.PlayerControllerSetupInputComponent, this)
.Invoke(Address);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,82 +20,63 @@ 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)
{
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<GetLifetimeReplicatedPropsDel_ThisCall>(obj);
original(address, arrayAddress);
//Native_VTableHacks.CallOriginal_GetLifetimeReplicatedProps(original, address, arrayAddress);

using (TArrayUnsafeRef<FLifetimeProperty> lifetimePropsUnsafe = new TArrayUnsafeRef<FLifetimeProperty>(arrayAddress))
{
FLifetimePropertyCollection lifetimeProps = new FLifetimePropertyCollection(address, lifetimePropsUnsafe);
obj.GetLifetimeReplicatedProps(lifetimeProps);
}
obj.GetLifetimeReplicatedPropsInternal(arrayAddress);
}
catch (Exception e)
{
LogCallbackException(nameof(OnGetLifetimeReplicatedProps), e);
}
}

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<SetupPlayerInputComponentDel_ThisCall>(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<ActorBeginPlayDel_ThisCall>(obj);
original(address);
//Native_VTableHacks.CallOriginal_ActorBeginPlay(original, address);

obj.BeginPlayInternal();
}
catch (Exception e)
Expand All @@ -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<ActorEndPlayDel_ThisCall>(obj);
original(address, endPlayReason);
//Native_VTableHacks.CallOriginal_ActorEndPlay(original, address, endPlayReason);

obj.EndPlayInternal(endPlayReason);
}
catch (Exception e)
Expand All @@ -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<ActorComponentBeginPlayDel_ThisCall>(obj);
original(address);
//Native_VTableHacks.CallOriginal_ActorComponentBeginPlay(original, address);

obj.BeginPlayInternal();
}
catch (Exception e)
Expand All @@ -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<ActorComponentEndPlayDel_ThisCall>(obj);
original(address, endPlayReason);
//Native_VTableHacks.CallOriginal_ActorComponentEndPlay(original,

obj.EndPlayInternal(endPlayReason);
}
catch (Exception e)
Expand All @@ -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<PlayerControllerSetupInputComponentDel_ThisCall>(obj);
original(address);
//Native_VTableHacks.CallOriginal_PlayerControllerSetupInputComponent(original)

obj.SetupInputComponentInternal();
}
catch (Exception e)
Expand All @@ -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;
Expand Down Expand Up @@ -434,5 +389,30 @@ private static unsafe IntPtr FindOriginalVTableOwner(IntPtr baseMostClass, IntPt
}
return originalOwner;
}

public struct CachedFunctionRedirect<T> 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<T>(obj);
}

if (cachedFunc == null)
{
throw new Exception("FunctionRedirect did not result in a function pointer");
}

return cachedFunc;
}
}
}
}

0 comments on commit 8e6ef70

Please sign in to comment.