public static unsafe void HackVTable(UObject obj) { // This will swap out the vtable entry and store the old one in our managed UClass if (!Native_UObjectBaseUtility.IsA(obj.Address, Runtime.Classes.UClass)) { UClass unrealClass = obj.GetClass(); if (unrealClass.VTableOriginalFunctions == null) { IntPtr *vtable = *(IntPtr **)obj.Address; unrealClass.VTableOriginalFunctions = new Dictionary <int, UClass.VTableOriginalFunc>(); foreach (FunctionRedirect redirect in vtableRedirects) { if (!Native_UObjectBaseUtility.IsA(obj.Address, redirect.Class)) { continue; } IntPtr originalFunctionAddress = vtable[redirect.VTableIndex]; if (originalFunctionAddress != redirect.NativeCallback) { IntPtr originalOwnerClassAddress = FindOriginalVTableOwner( redirect.Class, unrealClass.Address, originalFunctionAddress, redirect.VTableIndex); if (originalOwnerClassAddress != unrealClass.Address) { UClass originalOwnerClass = GCHelper.Find <UClass>(originalOwnerClassAddress); if (originalOwnerClass.VTableOriginalFunctions == null) { HackVTable(originalOwnerClass.GetDefaultObject()); } } IntPtr pageAlignedPtr = FMemory.PageAlignPointer((IntPtr)(&vtable[redirect.VTableIndex])); FMemory.PageProtect(pageAlignedPtr, (IntPtr)IntPtr.Size, true, true); *(&vtable[redirect.VTableIndex]) = redirect.NativeCallback; } else { // The VTable has already been swapped out. Find the original function address. UClass superClass = unrealClass; while ((superClass = superClass.GetSuperClass()) != null && superClass.VTableOriginalFunctions == null) { } Debug.Assert(superClass != null && superClass.VTableOriginalFunctions != null && superClass.VTableOriginalFunctions.ContainsKey(redirect.VTableIndex)); originalFunctionAddress = superClass.VTableOriginalFunctions[redirect.VTableIndex].FuncAddress; } unrealClass.VTableOriginalFunctions.Add(redirect.VTableIndex, new UClass.VTableOriginalFunc(originalFunctionAddress)); } } } }
public static unsafe void Unload() { foreach (FunctionRedirect redirect in vtableRedirects) { using (FStringUnsafe dummyNameUnsafe = new FStringUnsafe(redirect.DummyName)) { Native_VTableHacks.Set_VTableCallback(ref dummyNameUnsafe.Array, IntPtr.Zero); } } // Restore the old vtable entry on hotreload. This is important as otherwise we would lose the original function address // which is stored in the managed UClass (which gets destroyed on hotreload) foreach (IntPtr objAddress in new NativeReflection.NativeObjectIterator(Runtime.Classes.UObject, EObjectFlags.NoFlags)) { foreach (FunctionRedirect redirect in vtableRedirects) { if (!Native_UObjectBaseUtility.IsA(objAddress, redirect.Class)) { continue; } IntPtr *vtable = *(IntPtr **)objAddress; if (vtable[redirect.VTableIndex] == redirect.NativeCallback) { UObject obj = GCHelper.Find(objAddress); Debug.Assert(obj != null); UClass unrealClass = obj.GetClass(); Debug.Assert(unrealClass != null); UClass.VTableOriginalFunc originalFunc; if (unrealClass.VTableOriginalFunctions != null && unrealClass.VTableOriginalFunctions.TryGetValue(redirect.VTableIndex, out originalFunc)) { IntPtr pageAlignedPtr = FMemory.PageAlignPointer((IntPtr)(&vtable[redirect.VTableIndex])); FMemory.PageProtect(pageAlignedPtr, (IntPtr)IntPtr.Size, true, true); *(&vtable[redirect.VTableIndex]) = originalFunc.FuncAddress; } } } } }