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)); } } } }
private void GenerateCodeForModule(UnrealModuleInfo module) { List <UStruct> structs = new List <UStruct>(); List <UEnum> enums = new List <UEnum>(); List <UFunction> globalFunctions = new List <UFunction>(); foreach (UStruct unrealStruct in new TObjectIterator <UStruct>()) { if (!unrealStruct.IsA <UFunction>() && unrealStruct.IsIn(module.Package) && CanExportStruct(unrealStruct) && IsAvailableType(unrealStruct)) { structs.Add(unrealStruct); } } foreach (UEnum unrealEnum in new TObjectIterator <UEnum>()) { if (unrealEnum.IsIn(module.Package) && CanExportEnum(unrealEnum) && IsAvailableType(unrealEnum)) { enums.Add(unrealEnum); } } UClass packageClass = UClass.GetClass <UPackage>(); foreach (UFunction function in new TObjectIterator <UFunction>()) { UObject outer = function.GetOuter(); if (outer == null || outer.GetClass() != packageClass) { continue; } if (function.HasAnyFunctionFlags(EFunctionFlags.Delegate | EFunctionFlags.MulticastDelegate) && function.IsIn(module.Package)) { globalFunctions.Add(function); } } SlowTaskSetModuleCount(1); SlowTaskBeginModule(FPackageName.GetShortName(module.Package), structs.Count + enums.Count + globalFunctions.Count); GenerateCodeForModule(module, structs.ToArray(), enums.ToArray(), globalFunctions.ToArray()); }
/// <summary> /// Returns the original function address /// </summary> /// <param name="obj">An object which has the target function its vtable</param> /// <returns>The original function address</returns> public UClass.VTableOriginalFunc GetOriginalFunc(UObject obj) { UClass unrealClass = obj.GetClass(); Debug.Assert(unrealClass != null); if (unrealClass.VTableOriginalFunctions == null) { HackVTable(obj); } Debug.Assert(unrealClass.VTableOriginalFunctions != null); UClass.VTableOriginalFunc original; unrealClass.VTableOriginalFunctions.TryGetValue(VTableIndex, out original); Debug.Assert(original.FuncAddress != IntPtr.Zero); return(original); }
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; } } } } }
public void GenerateCodeForModules(UnrealModuleType[] moduleTypes) { BeginGenerateModules(); HashSet <string> moduleNamesWhitelistToLower = new HashSet <string>(); foreach (string moduleName in ModulesNamesWhitelist) { moduleNamesWhitelistToLower.Add(moduleName.ToLower()); } HashSet <string> moduleNamesBlacklistToLower = new HashSet <string>(); foreach (string moduleName in ModulesNamesBlacklist) { moduleNamesBlacklistToLower.Add(moduleName.ToLower()); } Dictionary <UPackage, List <UStruct> > structsByPackage = new Dictionary <UPackage, List <UStruct> >(); Dictionary <UPackage, List <UEnum> > enumsByPackage = new Dictionary <UPackage, List <UEnum> >(); Dictionary <UPackage, List <UFunction> > globalFunctionsByPackage = new Dictionary <UPackage, List <UFunction> >(); foreach (UStruct unrealStruct in new TObjectIterator <UStruct>()) { if (!unrealStruct.IsA <UFunction>() && CanExportStruct(unrealStruct) && IsAvailableType(unrealStruct)) { UPackage package = unrealStruct.GetOutermost(); if (package != null) { List <UStruct> structs; if (!structsByPackage.TryGetValue(package, out structs)) { structsByPackage.Add(package, structs = new List <UStruct>()); } structs.Add(unrealStruct); } } } foreach (UEnum unrealEnum in new TObjectIterator <UEnum>()) { if (CanExportEnum(unrealEnum) && IsAvailableType(unrealEnum)) { UPackage package = unrealEnum.GetOutermost(); if (package != null) { List <UEnum> enums; if (!enumsByPackage.TryGetValue(package, out enums)) { enumsByPackage.Add(package, enums = new List <UEnum>()); } enums.Add(unrealEnum); } } } UClass packageClass = UClass.GetClass <UPackage>(); foreach (UFunction function in new TObjectIterator <UFunction>()) { UObject outer = function.GetOuter(); if (outer == null || outer.GetClass() != packageClass) { continue; } if (function.HasAnyFunctionFlags(EFunctionFlags.Delegate | EFunctionFlags.MulticastDelegate)) { UPackage package = function.GetOutermost(); if (package != null) { List <UFunction> functions; if (!globalFunctionsByPackage.TryGetValue(package, out functions)) { globalFunctionsByPackage.Add(package, functions = new List <UFunction>()); } functions.Add(function); } } else { FMessage.Log(ELogVerbosity.Error, string.Format("Global function which isn't a delegate '{0}'", function.GetName())); } } Dictionary <FName, string> modulePaths = FModulesPaths.FindModulePaths("*"); // Make sure ModulePaths and ModuleNames are the same. Based on comments FindModules may be changed/removed in the // future so we will want to just use FindModulePaths. For now make sure they are synced up. FName[] moduleNames = FModuleManager.Get().FindModules("*"); if (moduleNames.Length != modulePaths.Count) { FMessage.Log(ELogVerbosity.Warning, string.Format("Module count invalid (update FModulePaths). FindModules:{0} FindModulePaths:{1}", moduleNames.Length, modulePaths.Count)); List <FName> additionalNames = new List <FName>(); foreach (KeyValuePair <FName, string> module in modulePaths) { if (moduleNames.Contains(module.Key)) { FMessage.Log(ELogVerbosity.Warning, "Module: " + module.Key + " - " + module.Value); } else { FMessage.Log(ELogVerbosity.Warning, "Additional module: " + module.Key + " - " + module.Value); } } List <FName> missingNames = new List <FName>(); foreach (FName moduleName in moduleNames) { if (!modulePaths.ContainsKey(moduleName)) { FMessage.Log(ELogVerbosity.Warning, "Missing module: " + moduleName); } } } IPlugin[] plugins = IPluginManager.Instance.GetDiscoveredPlugins(); SlowTaskSetModuleCount(modulePaths.Count); foreach (KeyValuePair <FName, string> modulePath in modulePaths) { SlowTaskBeginModule(modulePath.Key.PlainName); string moduleName = modulePath.Key.PlainName; string longPackageName = FPackageName.ConvertToLongScriptPackageName(moduleName); UPackage package = UObject.FindObjectFast <UPackage>(null, new FName(longPackageName), false, false); if (package != null) { UnrealModuleInfo moduleInfo = new UnrealModuleInfo(package, moduleName, modulePath.Value, UnrealModuleInfo.GetModuleType(moduleName, modulePath.Value, plugins)); if (moduleInfo.Type == UnrealModuleType.Unknown) { FMessage.Log(ELogVerbosity.Error, string.Format("Unknown module type on module '{0}' '{1}'", moduleInfo.Name, moduleInfo.Package)); } else if (!moduleTypes.Contains(moduleInfo.Type) || moduleNamesBlacklistToLower.Contains(moduleInfo.Name.ToLower()) || (moduleNamesWhitelistToLower.Count > 0 && !moduleNamesWhitelistToLower.Contains(moduleInfo.Name.ToLower()))) { continue; } List <UStruct> structs; if (!structsByPackage.TryGetValue(package, out structs)) { structs = new List <UStruct>(); } List <UEnum> enums; if (!enumsByPackage.TryGetValue(package, out enums)) { enums = new List <UEnum>(); } List <UFunction> globalFunctions; if (!globalFunctionsByPackage.TryGetValue(package, out globalFunctions)) { globalFunctions = new List <UFunction>(); } SlowTaskUpdateTarget(structs.Count + enums.Count + globalFunctions.Count); GenerateCodeForModule(moduleInfo, structs.ToArray(), enums.ToArray(), globalFunctions.ToArray()); } } IPlugin.Dispose(plugins); EndGenerateModules(); }