From e37a6b6399a77d8f781e86a1bdadf4617a322b08 Mon Sep 17 00:00:00 2001 From: Rirurin Date: Mon, 10 Nov 2025 09:34:31 +0800 Subject: [PATCH 01/12] Add marking objects as root set, add IUnrealClasses with struct extension --- .gitignore | 6 +- .../Factories/Interfaces/IUObjectArray.cs | 4 + .../Factories/UE4_27_2/UnrealFactory.cs | 17 +++ .../Unreal/Factories/UE5_4_4/UnrealFactory.cs | 16 +++ .../Unreal/UE5_4_4/EInternalObjectFlags.cs | 22 +++ .../Types/Unreal/UE5_4_4/FUObjectArray.cs | 2 +- UE.Toolkit.Core/UE.Toolkit.Core.csproj | 2 +- UE.Toolkit.Interfaces/IUnrealClasses.cs | 17 +++ .../UE.Toolkit.Interfaces.csproj | 2 +- UE.Toolkit.Reloaded/Common/ResolveAddress.cs | 47 ++++++ UE.Toolkit.Reloaded/Mod.cs | 5 + UE.Toolkit.Reloaded/ModConfig.json | 2 +- .../Project/UE.Toolkit.Reloaded/p3r/scans.ini | 7 + .../Project/UE.Toolkit.Reloaded/scans.ini | 6 + UE.Toolkit.Reloaded/Unreal/UnrealClasses.cs | 134 ++++++++++++++++++ UE.Toolkit.Reloaded/Unreal/UnrealObjects.cs | 15 +- UE.Toolkit.Reloaded/Unreal/UnrealStrings.cs | 18 ++- 17 files changed, 296 insertions(+), 26 deletions(-) create mode 100644 UE.Toolkit.Core/Types/Unreal/UE5_4_4/EInternalObjectFlags.cs create mode 100644 UE.Toolkit.Interfaces/IUnrealClasses.cs create mode 100644 UE.Toolkit.Reloaded/Common/ResolveAddress.cs create mode 100644 UE.Toolkit.Reloaded/Unreal/UnrealClasses.cs diff --git a/.gitignore b/.gitignore index c9e3499..76e50b3 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,8 @@ riderModule.iml /_ReSharper.Caches/ # Visual Studio build files -.vs \ No newline at end of file +.vs +*.sln.DotSettings.user + +# Jetbrains IDE +.idea/* \ No newline at end of file diff --git a/UE.Toolkit.Core/Types/Unreal/Factories/Interfaces/IUObjectArray.cs b/UE.Toolkit.Core/Types/Unreal/Factories/Interfaces/IUObjectArray.cs index af8e649..3a4e623 100644 --- a/UE.Toolkit.Core/Types/Unreal/Factories/Interfaces/IUObjectArray.cs +++ b/UE.Toolkit.Core/Types/Unreal/Factories/Interfaces/IUObjectArray.cs @@ -9,4 +9,8 @@ public interface IUObjectArray IUObject? IndexToObject(int idx); + void AddToRootSet(int idx); + + void RemoveFromRootSet(int idx); + } \ No newline at end of file diff --git a/UE.Toolkit.Core/Types/Unreal/Factories/UE4_27_2/UnrealFactory.cs b/UE.Toolkit.Core/Types/Unreal/Factories/UE4_27_2/UnrealFactory.cs index 366d226..cd9dd48 100644 --- a/UE.Toolkit.Core/Types/Unreal/Factories/UE4_27_2/UnrealFactory.cs +++ b/UE.Toolkit.Core/Types/Unreal/Factories/UE4_27_2/UnrealFactory.cs @@ -9,6 +9,7 @@ using FName = UE.Toolkit.Core.Types.Unreal.UE5_4_4.FName; using FText = UE.Toolkit.Core.Types.Unreal.UE5_4_4.FText; using EStructFlags = UE.Toolkit.Core.Types.Unreal.UE5_4_4.EStructFlags; +using EInternalObjectFlags = UE.Toolkit.Core.Types.Unreal.UE5_4_4.EInternalObjectFlags; using EObjectFlags = UE.Toolkit.Core.Types.Unreal.UE5_4_4.EObjectFlags; // ReSharper disable InconsistentNaming @@ -405,4 +406,20 @@ public unsafe class UObjectArrayUE4_27_2(nint ptr, IUnrealFactory factory) : IUO } public int NumElements => _self->ObjObjects.NumElements; + + public void AddToRootSet(int idx) + { + var objItem = _self->ObjObjects.GetItem(idx); + if (objItem == null || objItem->Object == null) return; + objItem->Flags |= EInternalObjectFlags.RootSet; + objItem->Object->ObjectFlags |= EObjectFlags.RF_MarkAsRootSet; + } + + public void RemoveFromRootSet(int idx) + { + var objItem = _self->ObjObjects.GetItem(idx); + if (objItem == null || objItem->Object == null) return; + objItem->Flags &= ~EInternalObjectFlags.RootSet; + objItem->Object->ObjectFlags &= ~EObjectFlags.RF_MarkAsRootSet; + } } diff --git a/UE.Toolkit.Core/Types/Unreal/Factories/UE5_4_4/UnrealFactory.cs b/UE.Toolkit.Core/Types/Unreal/Factories/UE5_4_4/UnrealFactory.cs index fc6bb06..f0663e6 100644 --- a/UE.Toolkit.Core/Types/Unreal/Factories/UE5_4_4/UnrealFactory.cs +++ b/UE.Toolkit.Core/Types/Unreal/Factories/UE5_4_4/UnrealFactory.cs @@ -417,4 +417,20 @@ public unsafe class UObjectArray_UE5_4_4(nint ptr, IUnrealFactory factory) : IUO return factory.CreateUObject((nint)objItem->Object); } + + public void AddToRootSet(int idx) + { + var objItem = _self->ObjObjects.GetItem(idx); + if (objItem == null || objItem->Object == null) return; + objItem->Flags |= EInternalObjectFlags.RootSet; + objItem->Object->ObjectFlags |= EObjectFlags.RF_MarkAsRootSet; + } + + public void RemoveFromRootSet(int idx) + { + var objItem = _self->ObjObjects.GetItem(idx); + if (objItem == null || objItem->Object == null) return; + objItem->Flags &= ~EInternalObjectFlags.RootSet; + objItem->Object->ObjectFlags &= ~EObjectFlags.RF_MarkAsRootSet; + } } diff --git a/UE.Toolkit.Core/Types/Unreal/UE5_4_4/EInternalObjectFlags.cs b/UE.Toolkit.Core/Types/Unreal/UE5_4_4/EInternalObjectFlags.cs new file mode 100644 index 0000000..ff382d1 --- /dev/null +++ b/UE.Toolkit.Core/Types/Unreal/UE5_4_4/EInternalObjectFlags.cs @@ -0,0 +1,22 @@ +// ReSharper disable InconsistentNaming +// ReSharper disable GrammarMistakeInComment + +namespace UE.Toolkit.Core.Types.Unreal.UE5_4_4; + +/** + * Internal flags stored in the global object table, used by the garbage collector. +*/ +[Flags] +public enum EInternalObjectFlags : uint +{ + None = 0, + ReachableInCluster = 0x800000, ///< External reference to object in cluster exists + ClusterRoot = 0x1000000, ///< Root of a cluster + Native = 0x2000000, ///< Native (UClass only). + Async = 0x4000000, ///< Object exists only on a different thread than the game thread. + AsyncLoading = 0x8000000, ///< Object is being asynchronously loaded. + Unreachable = 0x10000000, ///< Object is not reachable on the object graph. + PendingKill = 0x20000000, ///< Objects that are pending destruction (invalid for gameplay but valid objects) + RootSet = 0x40000000, ///< Object will not be garbage collected, even if unreferenced. + PendingConstruction = 0x80000000, ///< Object didn't have its class constructor called yet (only the UObjectBase one to initialize its most basic members) +} \ No newline at end of file diff --git a/UE.Toolkit.Core/Types/Unreal/UE5_4_4/FUObjectArray.cs b/UE.Toolkit.Core/Types/Unreal/UE5_4_4/FUObjectArray.cs index 2c7f516..6fa2c2d 100644 --- a/UE.Toolkit.Core/Types/Unreal/UE5_4_4/FUObjectArray.cs +++ b/UE.Toolkit.Core/Types/Unreal/UE5_4_4/FUObjectArray.cs @@ -115,7 +115,7 @@ public unsafe struct FUObjectItem public UObjectBase* Object; // Internal flags. These can only be changed via Set* and Clear* functions - public int Flags; + public EInternalObjectFlags Flags; // UObject Owner Cluster Index public int ClusterRootIndex; diff --git a/UE.Toolkit.Core/UE.Toolkit.Core.csproj b/UE.Toolkit.Core/UE.Toolkit.Core.csproj index 33d0f3c..102f925 100644 --- a/UE.Toolkit.Core/UE.Toolkit.Core.csproj +++ b/UE.Toolkit.Core/UE.Toolkit.Core.csproj @@ -6,7 +6,7 @@ enable true https://github.com/RyoTune/UE.Toolkit - 1.5.0 + 1.6.0 diff --git a/UE.Toolkit.Interfaces/IUnrealClasses.cs b/UE.Toolkit.Interfaces/IUnrealClasses.cs new file mode 100644 index 0000000..c95ae8e --- /dev/null +++ b/UE.Toolkit.Interfaces/IUnrealClasses.cs @@ -0,0 +1,17 @@ +namespace UE.Toolkit.Interfaces; + +public interface IUnrealClasses +{ + /// + /// Listen for the creation of an object's class, then extend it's allocation size and call a custom constructor. + /// + /// Number of bytes to increase the allocation size of object instances by. + /// A custom constructor to call after the original constructor + /// Object type. + /// + /// The extension is only created for the specific object type. If a struct/class inherits from this class, it + /// won't receive an extension. + /// + public void AddExtension(uint ExtraSize, Action> callback) + where TObject: unmanaged; +} \ No newline at end of file diff --git a/UE.Toolkit.Interfaces/UE.Toolkit.Interfaces.csproj b/UE.Toolkit.Interfaces/UE.Toolkit.Interfaces.csproj index 102b1a5..7fdd0e6 100644 --- a/UE.Toolkit.Interfaces/UE.Toolkit.Interfaces.csproj +++ b/UE.Toolkit.Interfaces/UE.Toolkit.Interfaces.csproj @@ -6,7 +6,7 @@ enable true https://github.com/RyoTune/UE.Toolkit - 1.4.5 + 1.6.0 diff --git a/UE.Toolkit.Reloaded/Common/ResolveAddress.cs b/UE.Toolkit.Reloaded/Common/ResolveAddress.cs new file mode 100644 index 0000000..70e5a1e --- /dev/null +++ b/UE.Toolkit.Reloaded/Common/ResolveAddress.cs @@ -0,0 +1,47 @@ +using System.Diagnostics; + +namespace UE.Toolkit.Reloaded.Common; + +public class ResolveAddress +{ + + public nuint GetDirectAddress(int offset) => (nuint)(BaseAddress + offset); + public unsafe static nuint GetGlobalAddress(nint ptrAddress) => (nuint)(*(int*)ptrAddress + ptrAddress + 4); + private unsafe nuint TryDerefInstructionPointer(nuint ptr) + { + if (ptr < (nuint)BaseAddress) + { + return 0; + } + + return *(byte*)ptr switch + { + 235 => DerefInstructionPointerShort(ptr), + 233 => DerefInstructionPointerNear(ptr), + _ => ptr, + }; + } + private unsafe nuint DerefInstructionPointerShort(nuint ptr) + { + nuint ptr2 = ptr + (nuint)(*(sbyte*)(ptr + 1) + 2); + return TryDerefInstructionPointer(ptr2); + } + + private unsafe nuint DerefInstructionPointerNear(nuint ptr) + { + nuint ptr2 = ptr + (nuint)(*(int*)(ptr + 1) + 5); + return TryDerefInstructionPointer(ptr2); + } + public nuint GetAddressMayThunk(int offset) => TryDerefInstructionPointer(GetDirectAddress(offset)); + public nuint GetIndirectAddressShort(int offset) => GetGlobalAddress((nint)BaseAddress + offset + 1); + public nuint GetIndirectAddressShort2(int offset) => GetGlobalAddress((nint)BaseAddress + offset + 2); + public nuint GetIndirectAddressLong(int offset) => GetGlobalAddress((nint)BaseAddress + offset + 3); + public nuint GetIndirectAddressLong4(int offset) => GetGlobalAddress((nint)BaseAddress + offset + 4); + + private nint BaseAddress; + public ResolveAddress() + { + var Process = System.Diagnostics.Process.GetCurrentProcess(); + BaseAddress = Process.MainModule!.BaseAddress; + } +} \ No newline at end of file diff --git a/UE.Toolkit.Reloaded/Mod.cs b/UE.Toolkit.Reloaded/Mod.cs index e894660..831e309 100644 --- a/UE.Toolkit.Reloaded/Mod.cs +++ b/UE.Toolkit.Reloaded/Mod.cs @@ -39,6 +39,8 @@ public class Mod : ModBase, IExports private readonly ObjectWriterService _writer; private readonly ToolkitApi _toolkit; private readonly UnrealStrings _strings; + private readonly UnrealClasses _classes; + private readonly Common.ResolveAddress _address; public Mod(ModContext context) { @@ -66,6 +68,8 @@ public Mod(ModContext context) _writer = new(_typeRegistry, _objects, _tables); _toolkit = new(_writer); _strings = new(); + _address = new(); + _classes = new(_hooks, _address); _modLoader.AddOrReplaceController(_owner, _memory); _modLoader.AddOrReplaceController(_owner, _tables); @@ -74,6 +78,7 @@ public Mod(ModContext context) _modLoader.AddOrReplaceController(_owner, _toolkit); _modLoader.AddOrReplaceController(_owner, _names); _modLoader.AddOrReplaceController(_owner, _strings); + _modLoader.AddOrReplaceController(_owner, _classes); _modLoader.AddOrReplaceController(_owner, _factory); _modLoader.ModLoaded += OnModLoaded; diff --git a/UE.Toolkit.Reloaded/ModConfig.json b/UE.Toolkit.Reloaded/ModConfig.json index 6cb6ed9..2afedd7 100644 --- a/UE.Toolkit.Reloaded/ModConfig.json +++ b/UE.Toolkit.Reloaded/ModConfig.json @@ -2,7 +2,7 @@ "ModId": "UE.Toolkit.Reloaded", "ModName": "Unreal Toolkit", "ModAuthor": "RyoTune, Rirurin", - "ModVersion": "1.5.0", + "ModVersion": "1.6.0", "ModDescription": "Modding toolkit for Unreal Engine games.\r\n\r\nSupported:\r\n- UE 5.4.4 (Clair Obscur)\r\n- UE 4.27.2 (P3R)", "ModDll": "UE.Toolkit.Reloaded.dll", "ModIcon": "Preview.png", diff --git a/UE.Toolkit.Reloaded/Project/UE.Toolkit.Reloaded/p3r/scans.ini b/UE.Toolkit.Reloaded/Project/UE.Toolkit.Reloaded/p3r/scans.ini index aa88cd2..1ccb638 100644 --- a/UE.Toolkit.Reloaded/Project/UE.Toolkit.Reloaded/p3r/scans.ini +++ b/UE.Toolkit.Reloaded/Project/UE.Toolkit.Reloaded/p3r/scans.ini @@ -32,3 +32,10 @@ UDataTable_HandleDataTableChanged=48 89 54 24 ?? 55 53 56 57 48 8D 6C 24 ?? 48 8 ;GMalloc= ;GMalloc_RESULT= + + +GetPrivateStaticClassBody_UE4=48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 57 41 54 41 55 41 56 41 57 48 83 EC 60 45 33 ED +;GetPrivateStaticClassBody_RESULT= + +GetPrivateStaticClassBody_UE5=DISABLED +;GetPrivateStaticClassBody_RESULT= \ No newline at end of file diff --git a/UE.Toolkit.Reloaded/Project/UE.Toolkit.Reloaded/scans.ini b/UE.Toolkit.Reloaded/Project/UE.Toolkit.Reloaded/scans.ini index 854b568..0b4a9e9 100644 --- a/UE.Toolkit.Reloaded/Project/UE.Toolkit.Reloaded/scans.ini +++ b/UE.Toolkit.Reloaded/Project/UE.Toolkit.Reloaded/scans.ini @@ -35,3 +35,9 @@ GMalloc=48 8B 0D ?? ?? ?? ?? 48 85 C9 75 ?? E8 ?? ?? ?? ?? 48 8B 0D ?? ?? ?? ?? GMalloc_RESULT=GetGlobalAddress(result + 3) FTextInspector_GetTableIdAndKey=48 89 5C 24 ?? 48 89 74 24 ?? 48 89 7C 24 ?? 41 56 48 83 EC 30 48 8B F9 48 8D 1D + +GetPrivateStaticClassBody_UE4=DISABLED +;GetPrivateStaticClassBody_RESULT= + +GetPrivateStaticClassBody_UE5=48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 57 41 54 41 55 41 56 41 57 48 83 EC 60 45 33 ED +;GetPrivateStaticClassBody_RESULT= \ No newline at end of file diff --git a/UE.Toolkit.Reloaded/Unreal/UnrealClasses.cs b/UE.Toolkit.Reloaded/Unreal/UnrealClasses.cs new file mode 100644 index 0000000..4c322f7 --- /dev/null +++ b/UE.Toolkit.Reloaded/Unreal/UnrealClasses.cs @@ -0,0 +1,134 @@ +using System.Collections.Concurrent; +using System.Runtime.InteropServices; +using Reloaded.Hooks.Definitions; +// using UE.Toolkit.Core.Types.Unreal.Factories; +using UE.Toolkit.Core.Types.Unreal.UE5_4_4; +using UE.Toolkit.Interfaces; +using UE.Toolkit.Reloaded.Common; + +namespace UE.Toolkit.Reloaded.Unreal; + +public unsafe class UnrealClasses : IUnrealClasses +{ + // Used in Engine version 4.27 + private delegate void GetPrivateStaticClassBody_UE4(nint packageName, nint name, UClass** returnClass, + nint registerNativeFunc, uint size, uint align, uint flags, ulong castFlags, nint config, nint inClassCtor, + nint vtableHelperCtorCaller, nint addRefObjects, nint superFn, nint withinFn, byte isDynamic, nint dynamicFn); + private SHFunction _GetPrivateStaticClassBodyUE4; + + private void GetPrivateStaticClassBodyUE4( + nint packageName, + nint name, + UClass** returnClass, + nint registerNativeFunc, + uint size, + uint align, + uint flags, + ulong castFlags, + nint config, + nint inClassCtor, + nint vtableHelperCtorCaller, // xor eax,eax, ret + nint addRefObjects, // ret + nint superFn, // [superType]::StaticClass + nint withinFn, // usually UObject::StaticClass + byte isDynamic, + nint dynamicFn) + { + TryExtendClass(name, inClassCtor, ref size); + _GetPrivateStaticClassBodyUE4!.Hook!.OriginalFunction(packageName, name, returnClass, registerNativeFunc, size, align, + flags, castFlags, config, inClassCtor, vtableHelperCtorCaller, addRefObjects, superFn, withinFn, isDynamic, dynamicFn); + } + + // NOTE: 5.0 has it's own signature + + // Used in Engine versions 5.1+ + private delegate void GetPrivateStaticClassBody_UE5(nint packageName, nint name, UClass** returnClass, + nint registerNativeFunc, uint size, uint align, uint flags, ulong castFlags, nint config, nint inClassCtor, + nint vtableHelperCtorCaller, nint staticFn, nint superFn, nint withinFn); + private SHFunction _GetPrivateStaticClassBodyUE5; + private void GetPrivateStaticClassBodyUE5( + nint packageName, + nint name, + UClass** returnClass, + nint registerNativeFunc, + uint size, + uint align, + uint flags, + ulong castFlags, + nint config, + nint inClassCtor, + nint vtableHelperCtorCaller, // xor eax,eax, ret + nint staticFn, + nint superFn, // [superType]::StaticClass + nint withinFn) // usually UObject::StaticClass + { + TryExtendClass(name, inClassCtor, ref size); + _GetPrivateStaticClassBodyUE5!.Hook!.OriginalFunction(packageName, name, returnClass, registerNativeFunc, size, + align, flags, castFlags, config, inClassCtor, vtableHelperCtorCaller, staticFn, superFn, withinFn); + } + + private void TryExtendClass(nint pClassName, nint classCtor, ref uint Size) + { + var ClassName = Marshal.PtrToStringUni(pClassName); + if (ClassName == null || !ClassExtensions.TryGetValue(ClassName, out var Extension)) return; + var OldSize = Size; + Size += Extension.ExtraSize; + if (classCtor != nint.Zero) + { + Extension.ConstructorHook = FollowThunkToGetAppropriateHook(classCtor, Extension.Constructor); + } + Log.Debug($"{ClassName} size: {OldSize} -> {Size}"); + } + + private IHook FollowThunkToGetAppropriateHook + (nint addr, ClassExtension.ExtensionConstructor ctorHook) + { + // build a new multicast delegate by injecting the native function, followed by custom code + // this reference will live for program's lifetime so there's no need to store hook in the caller + IHook? retHook = null; + ClassExtension.ExtensionConstructor ctorHookReal = x => + { + if (retHook == null) + { + Log.Error("retHook is null. Game will crash."); + return; + } + retHook.OriginalFunction(x); + }; + var Process = System.Diagnostics.Process.GetCurrentProcess(); + var BaseAddress = Process.MainModule!.BaseAddress; + ctorHookReal += ctorHook; + var offset = (int)(addr - BaseAddress); + var transformed = Address.GetAddressMayThunk(offset); + Log.Debug($"Transformed address: {addr:X} -> {transformed:X}"); + retHook = Hooks.CreateHook(ctorHookReal, (long)transformed).Activate(); + return retHook; + } + + public void AddExtension(uint ExtraSize, Action> callback) + where TObject : unmanaged + => ClassExtensions.GetOrAdd(typeof(TObject).Name[1..], new ClassExtension(ExtraSize, + obj => callback(new((TObject*)*(nint*)obj)))); + + private ConcurrentDictionary ClassExtensions = new(); + private IReloadedHooks Hooks; + private ResolveAddress Address; + + public UnrealClasses(IReloadedHooks _Hooks, ResolveAddress _Address) + { + Hooks = _Hooks; + Address = _Address; + _GetPrivateStaticClassBodyUE4 = new(GetPrivateStaticClassBodyUE4); + _GetPrivateStaticClassBodyUE5 = new(GetPrivateStaticClassBodyUE5); + } +} + +public class ClassExtension(uint extraSize, ClassExtension.ExtensionConstructor constructor) +{ + public uint ExtraSize { get; init; } = extraSize; + + // public unsafe delegate void ExtensionConstructor(T* Self) where T: unmanaged; + public delegate void ExtensionConstructor(nint Self); + public ExtensionConstructor Constructor { get; init; } = constructor; + internal IHook? ConstructorHook = null; +} \ No newline at end of file diff --git a/UE.Toolkit.Reloaded/Unreal/UnrealObjects.cs b/UE.Toolkit.Reloaded/Unreal/UnrealObjects.cs index 6c3019d..3e7e798 100644 --- a/UE.Toolkit.Reloaded/Unreal/UnrealObjects.cs +++ b/UE.Toolkit.Reloaded/Unreal/UnrealObjects.cs @@ -93,20 +93,7 @@ public void OnObjectLoadedByClass(Action> callb public string FTextToString(FText* text) => _FText_ToString.Wrapper(text)->ToString(); - public FString* CreateFString(string content) - { - content += '\0'; - - var fstring = (FString*)UnrealMemory._FMemory!.Malloc(sizeof(FString)); - fstring->Data.ArrayNum = content.Length; - fstring->Data.ArrayMax = content.Length; - - var strBytes = Encoding.Unicode.GetBytes(content); - fstring->Data.AllocatorInstance = (char*)UnrealMemory._FMemory.Malloc(strBytes.Length); - Marshal.Copy(strBytes, 0, (nint)fstring->Data.AllocatorInstance, strBytes.Length); - - return fstring; - } + public FString* CreateFString(string content) => UnrealStringsStatic.CreateFString(content); #pragma warning disable CS0649 // Field is never assigned to, and will always have its default value private struct PostLoadSubobjectsFunction diff --git a/UE.Toolkit.Reloaded/Unreal/UnrealStrings.cs b/UE.Toolkit.Reloaded/Unreal/UnrealStrings.cs index 4451f2c..4a951eb 100644 --- a/UE.Toolkit.Reloaded/Unreal/UnrealStrings.cs +++ b/UE.Toolkit.Reloaded/Unreal/UnrealStrings.cs @@ -54,16 +54,20 @@ public string UEnumGetDisplayNameTextByIndex(nint userEnum, int index) } public string FTextToString(FText* text) => _FText_ToString.Wrapper(text)->ToString(); - - public FString* CreateFString(string? content = null) + + public FString* CreateFString(string? content = null) => UnrealStringsStatic.CreateFString(content); +} + +public static unsafe class UnrealStringsStatic +{ + public static FString* CreateFString(string? content = null) { - content ??= string.Empty; - - var fstring = (FString*)UnrealMemory._FMemory.Malloc(sizeof(FString)); + content = (content ?? string.Empty) + "\0"; + var fstring = (FString*)UnrealMemory._FMemory!.Malloc(sizeof(FString)); + fstring->Data.ArrayNum = content.Length; + fstring->Data.ArrayMax = content.Length; var strBytes = Encoding.Unicode.GetBytes(content); - fstring->Data.ArrayNum = strBytes.Length; - fstring->Data.ArrayMax = strBytes.Length; fstring->Data.AllocatorInstance = (char*)UnrealMemory._FMemory.Malloc(strBytes.Length); Marshal.Copy(strBytes, 0, (nint)fstring->Data.AllocatorInstance, strBytes.Length); From 876262ff951d7182982a0aa897b6667523bc99f4 Mon Sep 17 00:00:00 2001 From: Rirurin Date: Tue, 11 Nov 2025 14:27:26 +0800 Subject: [PATCH 02/12] Add new blueprint/Object XML fields for basic types --- .../Interfaces/IUnrealClassesInternal.cs | 15 ++ .../Unreal/Factories/BaseUnrealFactory.cs | 5 + .../Types/Unreal/Factories/IUnrealFactory.cs | 2 + .../Factories/Interfaces/IFBoolProperty.cs | 9 ++ .../Factories/Interfaces/IFFieldClass.cs | 2 +- .../Interfaces/PropertyVisibility.cs | 8 + .../Factories/UE4_27_2/UnrealFactory.cs | 52 ++++++- .../Unreal/Factories/UE5_4_4/UnrealFactory.cs | 44 +++++- .../Types/Unreal/UE4_27_2/Unreal.cs | 3 + .../Types/Unreal/UE5_4_4/EPropertyFlags.cs | 1 + .../Types/Unreal/UE5_4_4/FBoolProperty.cs | 13 ++ UE.Toolkit.Interfaces/IUnrealClasses.cs | 125 +++++++++++++++- UE.Toolkit.Reloaded/Mod.cs | 2 +- .../UE.Toolkit.Reloaded/p3r/unreal.ini | 3 + .../Project/UE.Toolkit.Reloaded/unreal.ini | 3 + .../Reflection/PropertyFactory.cs | 115 +++++++++++++++ .../Reflection/UE4_27_2/PropertyFactory.cs | 138 ++++++++++++++++++ .../Reflection/UE5_4_4/PropertyFactory.cs | 119 +++++++++++++++ UE.Toolkit.Reloaded/Unreal/UnrealClasses.cs | 112 +++++++++++++- UE.Toolkit.Reloaded/Unreal/UnrealObjects.cs | 8 +- 20 files changed, 758 insertions(+), 21 deletions(-) create mode 100644 UE.Toolkit.Core/Types/Interfaces/IUnrealClassesInternal.cs create mode 100644 UE.Toolkit.Core/Types/Unreal/Factories/Interfaces/IFBoolProperty.cs create mode 100644 UE.Toolkit.Core/Types/Unreal/Factories/Interfaces/PropertyVisibility.cs create mode 100644 UE.Toolkit.Core/Types/Unreal/UE5_4_4/FBoolProperty.cs create mode 100644 UE.Toolkit.Reloaded/Reflection/PropertyFactory.cs create mode 100644 UE.Toolkit.Reloaded/Reflection/UE4_27_2/PropertyFactory.cs create mode 100644 UE.Toolkit.Reloaded/Reflection/UE5_4_4/PropertyFactory.cs diff --git a/UE.Toolkit.Core/Types/Interfaces/IUnrealClassesInternal.cs b/UE.Toolkit.Core/Types/Interfaces/IUnrealClassesInternal.cs new file mode 100644 index 0000000..a170b1a --- /dev/null +++ b/UE.Toolkit.Core/Types/Interfaces/IUnrealClassesInternal.cs @@ -0,0 +1,15 @@ +using UE.Toolkit.Core.Types.Unreal.Factories.Interfaces; +using UE.Toolkit.Core.Types.Unreal.UE5_4_4; + +namespace UE.Toolkit.Core.Types.Interfaces; + +public class FieldClassGlobal(nint vtable, IFFieldClass param) +{ + public readonly nint Vtable = vtable; + public readonly IFFieldClass Params = param; +} + +public interface IUnrealClassesInternal +{ + public bool GetFieldClassGlobal(FName Name, out FieldClassGlobal FieldClass); +} \ No newline at end of file diff --git a/UE.Toolkit.Core/Types/Unreal/Factories/BaseUnrealFactory.cs b/UE.Toolkit.Core/Types/Unreal/Factories/BaseUnrealFactory.cs index ad2c9a7..6df32ae 100644 --- a/UE.Toolkit.Core/Types/Unreal/Factories/BaseUnrealFactory.cs +++ b/UE.Toolkit.Core/Types/Unreal/Factories/BaseUnrealFactory.cs @@ -1,3 +1,4 @@ +using System.Runtime.InteropServices; using UE.Toolkit.Core.Types.Unreal.Factories.Interfaces; namespace UE.Toolkit.Core.Types.Unreal.Factories; @@ -21,6 +22,8 @@ public T Cast(IPtr obj) return (T)CreateUUserDefinedEnum(obj.Ptr); case nameof(IFByteProperty): return (T)CreateFByteProperty(obj.Ptr); + case nameof(IFBoolProperty): + return (T)CreateFBoolProperty(obj.Ptr); case nameof(IFEnumProperty): return (T)CreateFEnumProperty(obj.Ptr); case nameof(IFObjectProperty): @@ -45,8 +48,10 @@ public T Cast(IPtr obj) throw new NotSupportedException(typeName); } } + public abstract nint SizeOf(); public abstract IFProperty CreateFProperty(nint ptr); + public abstract IFBoolProperty CreateFBoolProperty(nint ptr); public abstract IFByteProperty CreateFByteProperty(nint ptr); public abstract IFEnumProperty CreateFEnumProperty(nint ptr); public abstract IFObjectProperty CreateFObjectProperty(nint ptr); diff --git a/UE.Toolkit.Core/Types/Unreal/Factories/IUnrealFactory.cs b/UE.Toolkit.Core/Types/Unreal/Factories/IUnrealFactory.cs index 53cd4ff..0c71c5c 100644 --- a/UE.Toolkit.Core/Types/Unreal/Factories/IUnrealFactory.cs +++ b/UE.Toolkit.Core/Types/Unreal/Factories/IUnrealFactory.cs @@ -8,7 +8,9 @@ namespace UE.Toolkit.Core.Types.Unreal.Factories; public interface IUnrealFactory { T Cast(IPtr obj); + nint SizeOf(); IFProperty CreateFProperty(nint ptr); + IFBoolProperty CreateFBoolProperty(nint ptr); IFByteProperty CreateFByteProperty(nint ptr); IFEnumProperty CreateFEnumProperty(nint ptr); IFObjectProperty CreateFObjectProperty(nint ptr); diff --git a/UE.Toolkit.Core/Types/Unreal/Factories/Interfaces/IFBoolProperty.cs b/UE.Toolkit.Core/Types/Unreal/Factories/Interfaces/IFBoolProperty.cs new file mode 100644 index 0000000..d12ee99 --- /dev/null +++ b/UE.Toolkit.Core/Types/Unreal/Factories/Interfaces/IFBoolProperty.cs @@ -0,0 +1,9 @@ +namespace UE.Toolkit.Core.Types.Unreal.Factories.Interfaces; + +public interface IFBoolProperty : IFProperty +{ + byte FieldSize { get; } + byte ByteOffset { get; } + byte ByteMask { get; } + byte FieldMask { get; } +} \ No newline at end of file diff --git a/UE.Toolkit.Core/Types/Unreal/Factories/Interfaces/IFFieldClass.cs b/UE.Toolkit.Core/Types/Unreal/Factories/Interfaces/IFFieldClass.cs index 65df2e5..1845f1a 100644 --- a/UE.Toolkit.Core/Types/Unreal/Factories/Interfaces/IFFieldClass.cs +++ b/UE.Toolkit.Core/Types/Unreal/Factories/Interfaces/IFFieldClass.cs @@ -2,7 +2,7 @@ namespace UE.Toolkit.Core.Types.Unreal.Factories.Interfaces; -public interface IFFieldClass +public interface IFFieldClass : IPtr { string Name { get; } ulong Id { get; } diff --git a/UE.Toolkit.Core/Types/Unreal/Factories/Interfaces/PropertyVisibility.cs b/UE.Toolkit.Core/Types/Unreal/Factories/Interfaces/PropertyVisibility.cs new file mode 100644 index 0000000..5d701dc --- /dev/null +++ b/UE.Toolkit.Core/Types/Unreal/Factories/Interfaces/PropertyVisibility.cs @@ -0,0 +1,8 @@ +namespace UE.Toolkit.Core.Types.Unreal.Factories.Interfaces; + +public enum PropertyVisibility +{ + Public, // CPF_NativeAccessSpecifierPublic + Protected, // CPF_NativeAccessSpecifierProtected + Private // CPF_NativeAccessSpecifierPrivate +} \ No newline at end of file diff --git a/UE.Toolkit.Core/Types/Unreal/Factories/UE4_27_2/UnrealFactory.cs b/UE.Toolkit.Core/Types/Unreal/Factories/UE4_27_2/UnrealFactory.cs index cd9dd48..c2da32c 100644 --- a/UE.Toolkit.Core/Types/Unreal/Factories/UE4_27_2/UnrealFactory.cs +++ b/UE.Toolkit.Core/Types/Unreal/Factories/UE4_27_2/UnrealFactory.cs @@ -17,10 +17,42 @@ namespace UE.Toolkit.Core.Types.Unreal.Factories.UE4_27_2; public class UnrealFactory : BaseUnrealFactory -{ +{ + public override nint SizeOf() + { + var TypeName = typeof(T).Name; + unsafe + { + return TypeName switch + { + nameof(IUObject) => sizeof(UObjectBase), + nameof(IUClass) => sizeof(UClass), + nameof(IUScriptStruct) => sizeof(UScriptStruct), + nameof(IUEnum) => sizeof(UEnum), + nameof(IUUserDefinedEnum) => sizeof(UUserDefinedEnum), + nameof(IFProperty) => sizeof(FProperty), + nameof(IFByteProperty) => sizeof(FByteProperty), + nameof(IFBoolProperty) => sizeof(FBoolProperty), + nameof(IFEnumProperty) => sizeof(FEnumProperty), + nameof(IFObjectProperty) => sizeof(FObjectProperty), + nameof(IFSoftClassProperty) => sizeof(UE.Toolkit.Core.Types.Unreal.UE5_4_4.FSoftClassProperty), + nameof(IFClassProperty) => sizeof(FClassProperty), + nameof(IFStructProperty) => sizeof(FStructProperty), + nameof(IFMapProperty) => sizeof(FMapProperty), + nameof(IFInterfaceProperty) => sizeof(UE.Toolkit.Core.Types.Unreal.UE5_4_4.FInterfaceProperty), + nameof(IFArrayProperty) => sizeof(FArrayProperty), + nameof(IFSetProperty) => sizeof(FSetProperty), + nameof(IFOptionalProperty) => sizeof(UE.Toolkit.Core.Types.Unreal.UE5_4_4.FOptionalProperty), + _ => throw new NotSupportedException(TypeName) + }; + } + } + public override IFProperty CreateFProperty(nint ptr) => new FPropertyUE4_27_2(ptr, this); public override IFByteProperty CreateFByteProperty(nint ptr) => new FBytePropertyUE4_27_2(ptr, this); + + public override IFBoolProperty CreateFBoolProperty(IntPtr ptr) => new FBoolPropertyUE4_27_2(ptr, this); public override IFEnumProperty CreateFEnumProperty(nint ptr) => new FEnumPropertyUE4_27_2(ptr, this); @@ -126,11 +158,12 @@ public unsafe class FFieldClassUE4_27_2(nint ptr, IUnrealFactory factory) : IFFieldClass { private readonly FFieldClass* _self = (FFieldClass*)ptr; - + + public nint Ptr => ptr; public string Name => _self->name.ToString(); - public ulong Id => throw new NotImplementedException(); - public ulong CastFlags => throw new NotImplementedException(); - public EClassFlags ClassFlags => throw new NotImplementedException(); + public ulong Id => _self->Id; + public ulong CastFlags => _self->CastFlags; + public EClassFlags ClassFlags => _self->ClassFlags; public IFFieldClass SuperClass => factory.CreateFFieldClass((nint)_self->super); public IFField DefaultObject => factory.CreateFField((nint)_self->default_obj); public nint FieldConstructor => _self->ctor; @@ -226,6 +259,15 @@ public void Dispose() { } IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } +public unsafe class FBoolPropertyUE4_27_2(nint ptr, IUnrealFactory factory) + : FPropertyUE4_27_2(ptr, factory), IFBoolProperty +{ + public byte FieldSize => ((FBoolProperty*)Ptr)->field_size; + public byte ByteOffset => ((FBoolProperty*)Ptr)->byte_offset; + public byte ByteMask => ((FBoolProperty*)Ptr)->byte_mask; + public byte FieldMask => ((FBoolProperty*)Ptr)->field_mask; +} + public unsafe class FBytePropertyUE4_27_2(nint ptr, IUnrealFactory factory) : FPropertyUE4_27_2(ptr, factory), IFByteProperty { diff --git a/UE.Toolkit.Core/Types/Unreal/Factories/UE5_4_4/UnrealFactory.cs b/UE.Toolkit.Core/Types/Unreal/Factories/UE5_4_4/UnrealFactory.cs index f0663e6..210c2d7 100644 --- a/UE.Toolkit.Core/Types/Unreal/Factories/UE5_4_4/UnrealFactory.cs +++ b/UE.Toolkit.Core/Types/Unreal/Factories/UE5_4_4/UnrealFactory.cs @@ -9,8 +9,40 @@ namespace UE.Toolkit.Core.Types.Unreal.Factories.UE5_4_4; public class UnrealFactory : BaseUnrealFactory { + + public override nint SizeOf() + { + var TypeName = typeof(T).Name; + unsafe + { + return TypeName switch + { + nameof(IUObject) => sizeof(UObjectBase), + nameof(IUClass) => sizeof(UClass), + nameof(IUScriptStruct) => sizeof(UScriptStruct), + nameof(IUEnum) => sizeof(UEnum), + nameof(IUUserDefinedEnum) => sizeof(UUserDefinedEnum), + nameof(IFProperty) => sizeof(FProperty), + nameof(IFByteProperty) => sizeof(FByteProperty), + nameof(IFBoolProperty) => sizeof(FBoolProperty), + nameof(IFEnumProperty) => sizeof(FEnumProperty), + nameof(IFObjectProperty) => sizeof(FObjectProperty), + nameof(IFSoftClassProperty) => sizeof(FSoftClassProperty), + nameof(IFClassProperty) => sizeof(FClassProperty), + nameof(IFStructProperty) => sizeof(FStructProperty), + nameof(IFMapProperty) => sizeof(FMapProperty), + nameof(IFInterfaceProperty) => sizeof(FInterfaceProperty), + nameof(IFArrayProperty) => sizeof(FArrayProperty), + nameof(IFSetProperty) => sizeof(FSetProperty), + nameof(IFOptionalProperty) => sizeof(FOptionalProperty), + _ => throw new NotSupportedException(TypeName) + }; + } + } + public override IFProperty CreateFProperty(nint ptr) => new FProperty_UE5_4_4(ptr, this); public override IFByteProperty CreateFByteProperty(nint ptr) => new FByteProperty_UE5_4_4(ptr, this); + public override IFBoolProperty CreateFBoolProperty(nint ptr) => new FBoolProperty_UE5_4_4(ptr, this); public override IFEnumProperty CreateFEnumProperty(nint ptr) => new FEnumProperty_UE5_4_4(ptr, this); public override IFObjectProperty CreateFObjectProperty(nint ptr) => new FObjectProperty_UE5_4_4(ptr, this); public override IFSoftClassProperty CreateFSoftClassProperty(nint ptr) => new FSoftClassProperty_UE5_4_4(ptr, this); @@ -103,6 +135,7 @@ public unsafe class FFieldClass_UE5_4_4(nint ptr, IUnrealFactory factory) { private readonly FFieldClass* _self = (FFieldClass*)ptr; + public nint Ptr => ptr; public string Name => _self->Name.ToString(); public ulong Id => _self->Id; public ulong CastFlags => _self->CastFlags; @@ -228,12 +261,13 @@ public void Dispose() { } IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } -public enum PropertyType +public unsafe class FBoolProperty_UE5_4_4(nint ptr, IUnrealFactory factory) + : FProperty_UE5_4_4(ptr, factory), IFBoolProperty { - PropertyLink, - NextRef, - DestructorLink, - PostConstructLink, + public byte FieldSize => ((FBoolProperty*)ptr)->FieldSize; + public byte ByteOffset => ((FBoolProperty*)ptr)->ByteOffset; + public byte ByteMask => ((FBoolProperty*)ptr)->ByteMask; + public byte FieldMask => ((FBoolProperty*)ptr)->FieldMask; } public unsafe class FByteProperty_UE5_4_4(nint ptr, IUnrealFactory factory) diff --git a/UE.Toolkit.Core/Types/Unreal/UE4_27_2/Unreal.cs b/UE.Toolkit.Core/Types/Unreal/UE4_27_2/Unreal.cs index 93ea598..cf44a61 100644 --- a/UE.Toolkit.Core/Types/Unreal/UE4_27_2/Unreal.cs +++ b/UE.Toolkit.Core/Types/Unreal/UE4_27_2/Unreal.cs @@ -181,6 +181,9 @@ public struct UEnum public unsafe struct FFieldClass { [FieldOffset(0x0)] public FName name; + [FieldOffset(0x8)] public ulong Id; + [FieldOffset(0x10)] public ulong CastFlags; + [FieldOffset(0x18)] public EClassFlags ClassFlags; [FieldOffset(0x20)] public FFieldClass* super; [FieldOffset(0x28)] public FField* default_obj; [FieldOffset(0x30)] public IntPtr ctor; // [PropertyName]::Construct (e..g for ArrayProperty, this would be FArrayProperty::Construct) diff --git a/UE.Toolkit.Core/Types/Unreal/UE5_4_4/EPropertyFlags.cs b/UE.Toolkit.Core/Types/Unreal/UE5_4_4/EPropertyFlags.cs index 0f14b4a..1c5cdf4 100644 --- a/UE.Toolkit.Core/Types/Unreal/UE5_4_4/EPropertyFlags.cs +++ b/UE.Toolkit.Core/Types/Unreal/UE5_4_4/EPropertyFlags.cs @@ -3,6 +3,7 @@ // ReSharper disable CommentTypo namespace UE.Toolkit.Core.Types.Unreal.UE5_4_4; +[Flags] public enum EPropertyFlags : ulong { CPF_None = 0, diff --git a/UE.Toolkit.Core/Types/Unreal/UE5_4_4/FBoolProperty.cs b/UE.Toolkit.Core/Types/Unreal/UE5_4_4/FBoolProperty.cs new file mode 100644 index 0000000..626856e --- /dev/null +++ b/UE.Toolkit.Core/Types/Unreal/UE5_4_4/FBoolProperty.cs @@ -0,0 +1,13 @@ +using System.Runtime.InteropServices; + +namespace UE.Toolkit.Core.Types.Unreal.UE5_4_4; + +[StructLayout(LayoutKind.Sequential)] +public struct FBoolProperty +{ + public FProperty Super; + public byte FieldSize; + public byte ByteOffset; + public byte ByteMask; + public byte FieldMask; +} \ No newline at end of file diff --git a/UE.Toolkit.Interfaces/IUnrealClasses.cs b/UE.Toolkit.Interfaces/IUnrealClasses.cs index c95ae8e..98464e4 100644 --- a/UE.Toolkit.Interfaces/IUnrealClasses.cs +++ b/UE.Toolkit.Interfaces/IUnrealClasses.cs @@ -1,9 +1,13 @@ -namespace UE.Toolkit.Interfaces; +using UE.Toolkit.Core.Types.Interfaces; +using UE.Toolkit.Core.Types.Unreal.Factories.Interfaces; -public interface IUnrealClasses +namespace UE.Toolkit.Interfaces; + +public interface IUnrealClasses : IUnrealClassesInternal { /// - /// Listen for the creation of an object's class, then extend it's allocation size and call a custom constructor. + /// Listen for the creation of an object's class, then extend it's allocation size and call a custom constructor + /// after the existing constructor has executed. /// /// Number of bytes to increase the allocation size of object instances by. /// A custom constructor to call after the original constructor @@ -14,4 +18,119 @@ public interface IUnrealClasses /// public void AddExtension(uint ExtraSize, Action> callback) where TObject: unmanaged; + + /// + /// Listen for the creation of an object's class, then call a custom constructor after the existing constructor + /// has executed. + /// + /// A custom constructor to call after the original constructor + /// Object type. + public void AddConstructor(Action> callback) + where TObject : unmanaged => AddExtension(0, callback); + + /// + /// Get the type information for a specified object. If this class has no type info, this will return null. + /// + /// Type information for the specified object type. + /// Object type. + /// If the type information exists. + public bool GetClassInfoFromClass(out IUClass? Value) where TObject : unmanaged; + + /// + /// Get the type information for a specified object. If this class has no type info, this will return null. + /// + /// Name of the object type. + /// Type information for the object type with the given name. + /// If the type information exists. + public bool GetClassInfoFromName(string Name, out IUClass? Value); + + /// + /// Add an Int8 property to the object's class with the specified name and offset. This will make the field + /// exposable to blueprints and Object XML. + /// + /// Name of the new field. + /// Offset of the new field. + /// Object type. + public void AddI8Property(string Name, int Offset) where TObject : unmanaged; + + /// + /// Add a Int16 property to the object's class with the specified name and offset. This will make the field + /// exposable to blueprints and Object XML. + /// + /// Name of the new field. + /// Offset of the new field. + /// Object type. + public void AddI16Property(string Name, int Offset) where TObject : unmanaged; + + /// + /// Add a Int32 property to the object's class with the specified name and offset. This will make the field + /// exposable to blueprints and Object XML. + /// + /// Name of the new field. + /// Offset of the new field. + /// Object type. + public void AddI32Property(string Name, int Offset) where TObject : unmanaged; + + /// + /// Add a Int64 property to the object's class with the specified name and offset. This will make the field + /// exposable to blueprints and Object XML. + /// + /// Name of the new field. + /// Offset of the new field. + /// Object type. + public void AddI64Property(string Name, int Offset) where TObject : unmanaged; + + /// + /// Add a UInt8 property to the object's class with the specified name and offset. This will make the field + /// exposable to blueprints and Object XML. + /// + /// Name of the new field. + /// Offset of the new field. + /// Object type. + public void AddU8Property(string Name, int Offset) where TObject : unmanaged; + + /// + /// Add a UInt16 property to the object's class with the specified name and offset. This will make the field + /// exposable to blueprints and Object XML. + /// + /// Name of the new field. + /// Offset of the new field. + /// Object type. + public void AddU16Property(string Name, int Offset) where TObject : unmanaged; + + /// + /// Add a UInt32 property to the object's class with the specified name and offset. This will make the field + /// exposable to blueprints and Object XML. + /// + /// Name of the new field. + /// Offset of the new field. + /// Object type. + public void AddU32Property(string Name, int Offset) where TObject : unmanaged; + + /// + /// Add a UInt64 property to the object's class with the specified name and offset. This will make the field + /// exposable to blueprints and Object XML. + /// + /// Name of the new field. + /// Offset of the new field. + /// Object type. + public void AddU64Property(string Name, int Offset) where TObject : unmanaged; + + /// + /// Add a float property to the object's class with the specified name and offset. This will make the field + /// exposable to blueprints and Object XML. + /// + /// Name of the new field. + /// Offset of the new field. + /// Object type. + public void AddF32Property(string Name, int Offset) where TObject : unmanaged; + + /// + /// Add a double property to the object's class with the specified name and offset. This will make the field + /// exposable to blueprints and Object XML. + /// + /// Name of the new field. + /// Offset of the new field. + /// Object type. + public void AddF64Property(string Name, int Offset) where TObject : unmanaged; } \ No newline at end of file diff --git a/UE.Toolkit.Reloaded/Mod.cs b/UE.Toolkit.Reloaded/Mod.cs index 831e309..e7eb1a2 100644 --- a/UE.Toolkit.Reloaded/Mod.cs +++ b/UE.Toolkit.Reloaded/Mod.cs @@ -69,7 +69,7 @@ public Mod(ModContext context) _toolkit = new(_writer); _strings = new(); _address = new(); - _classes = new(_hooks, _address); + _classes = new(_factory, _memory, _hooks, _address); _modLoader.AddOrReplaceController(_owner, _memory); _modLoader.AddOrReplaceController(_owner, _tables); diff --git a/UE.Toolkit.Reloaded/Project/UE.Toolkit.Reloaded/p3r/unreal.ini b/UE.Toolkit.Reloaded/Project/UE.Toolkit.Reloaded/p3r/unreal.ini index 49a24bc..34c51b8 100644 --- a/UE.Toolkit.Reloaded/Project/UE.Toolkit.Reloaded/p3r/unreal.ini +++ b/UE.Toolkit.Reloaded/Project/UE.Toolkit.Reloaded/p3r/unreal.ini @@ -4,3 +4,6 @@ GetAllocSize=0x40 Malloc=0x10 Realloc=0x20 QuantizeSize=0x38 + +[UClass] +CreateDefaultObject=0x3a0 \ No newline at end of file diff --git a/UE.Toolkit.Reloaded/Project/UE.Toolkit.Reloaded/unreal.ini b/UE.Toolkit.Reloaded/Project/UE.Toolkit.Reloaded/unreal.ini index 277ceaf..d7817ae 100644 --- a/UE.Toolkit.Reloaded/Project/UE.Toolkit.Reloaded/unreal.ini +++ b/UE.Toolkit.Reloaded/Project/UE.Toolkit.Reloaded/unreal.ini @@ -6,3 +6,6 @@ GetAllocSize=0x58 Malloc=0x28 Realloc=0x38 QuantizeSize=0x50 + +[UClass] +CreateDefaultObject=0x3f0 \ No newline at end of file diff --git a/UE.Toolkit.Reloaded/Reflection/PropertyFactory.cs b/UE.Toolkit.Reloaded/Reflection/PropertyFactory.cs new file mode 100644 index 0000000..146e6ea --- /dev/null +++ b/UE.Toolkit.Reloaded/Reflection/PropertyFactory.cs @@ -0,0 +1,115 @@ +using UE.Toolkit.Core.Types.Interfaces; +using UE.Toolkit.Core.Types.Unreal.Factories; +using UE.Toolkit.Core.Types.Unreal.Factories.Interfaces; +using UE.Toolkit.Core.Types.Unreal.UE5_4_4; +using UE.Toolkit.Interfaces; + +namespace UE.Toolkit.Reloaded.Reflection; + +public abstract class BasePropertyFactory(IUnrealFactory factory, IUnrealMemory memory, IUnrealClasses classes) +{ + private Dictionary? PropertyNames = null; + + // Lazy load property name cache, we need to do this to ensure that FName::FName() is valid + private static Dictionary InitializePropertyNames() => new() + { + { "ByteProperty", new FName("ByteProperty", EFindName.FNAME_Find) }, + { "IntProperty", new FName("IntProperty", EFindName.FNAME_Find) }, + { "BoolProperty", new FName("BoolProperty", EFindName.FNAME_Find) }, + { "FloatProperty", new FName("FloatProperty", EFindName.FNAME_Find) }, + { "ObjectProperty", new FName("ObjectProperty", EFindName.FNAME_Find) }, + { "NameProperty", new FName("NameProperty", EFindName.FNAME_Find) }, + { "DelegateProperty", new FName("DelegateProperty", EFindName.FNAME_Find) }, + { "DoubleProperty", new FName("DoubleProperty", EFindName.FNAME_Find) }, + { "ArrayProperty", new FName("ArrayProperty", EFindName.FNAME_Find) }, + { "StructProperty", new FName("StructProperty", EFindName.FNAME_Find) }, + { "VectorProperty", new FName("VectorProperty", EFindName.FNAME_Find) }, + { "RotatorProperty", new FName("RotatorProperty", EFindName.FNAME_Find) }, + { "StrProperty", new FName("StrProperty", EFindName.FNAME_Find) }, + { "TextProperty", new FName("TextProperty", EFindName.FNAME_Find) }, + { "InterfaceProperty", new FName("InterfaceProperty", EFindName.FNAME_Find) }, + { "MulticastDelegateProperty", new FName("MulticastDelegateProperty", EFindName.FNAME_Find) }, + { "LazyObjectProperty", new FName("LazyObjectProperty", EFindName.FNAME_Find) }, + { "SoftObjectProperty", new FName("SoftObjectProperty", EFindName.FNAME_Find) }, + { "Int64Property", new FName("Int64Property", EFindName.FNAME_Find) }, + { "Int32Property", new FName("Int32Property", EFindName.FNAME_Find) }, + { "Int16Property", new FName("Int16Property", EFindName.FNAME_Find) }, + { "Int8Property", new FName("Int8Property", EFindName.FNAME_Find) }, + { "UInt64Property", new FName("UInt64Property", EFindName.FNAME_Find) }, + { "UInt32Property", new FName("UInt32Property", EFindName.FNAME_Find) }, + { "UInt16Property", new FName("UInt16Property", EFindName.FNAME_Find) }, + { "MapProperty", new FName("MapProperty", EFindName.FNAME_Find) }, + { "SetProperty", new FName("SetProperty", EFindName.FNAME_Find) }, + }; + + private bool GetProperty(string Name, out FieldClassGlobal? FieldClass) + { + FieldClass = null; + PropertyNames ??= InitializePropertyNames(); + return PropertyNames!.TryGetValue(Name, out var SerialName) + && Classes.GetFieldClassGlobal(SerialName, out FieldClass); + } + + protected bool TryGetClassAndProperty(string PropertyName, out IUClass? ClassReflection, + out FieldClassGlobal? PropertyClass) where TOwner: unmanaged + { + ClassReflection = null; + PropertyClass = null; + return Classes.GetClassInfoFromClass(out ClassReflection) + && GetProperty(PropertyName, out PropertyClass); + } + + #region INTERNAL INTERFACE + + protected abstract void LinkToPropertyList(IFProperty Property, IUClass Reflect); + + protected abstract void SetPropertySuperFields(IFField Field, string Name, IUClass ClassReflection, + FieldClassGlobal PropertyClass); + + protected abstract EPropertyFlags CreatePropertyFlags(PropertyVisibility Visibility, PropertyBuilderFlags InFlags); + + protected abstract void SetCopyPropertyFields(IFProperty Property, int Offset, PropertyVisibility Visibility) + where T : unmanaged; + + #endregion + + #region PUBLIC INTERFACE + + public abstract bool CreateI8(out IFProperty? NewProperty, string Name, int Offset, + PropertyVisibility Visibility) where TOwner: unmanaged; + public abstract bool CreateI16(out IFProperty? NewProperty, string Name, int Offset, + PropertyVisibility Visibility) where TOwner: unmanaged; + public abstract bool CreateI32(out IFProperty? NewProperty, string Name, int Offset, + PropertyVisibility Visibility) where TOwner: unmanaged; + public abstract bool CreateI64(out IFProperty? NewProperty, string Name, int Offset, + PropertyVisibility Visibility) where TOwner: unmanaged; + public abstract bool CreateU8(out IFProperty? NewProperty, string Name, int Offset, + PropertyVisibility Visibility) where TOwner: unmanaged; + public abstract bool CreateU16(out IFProperty? NewProperty, string Name, int Offset, + PropertyVisibility Visibility) where TOwner: unmanaged; + public abstract bool CreateU32(out IFProperty? NewProperty, string Name, int Offset, + PropertyVisibility Visibility) where TOwner: unmanaged; + public abstract bool CreateU64(out IFProperty? NewProperty, string Name, int Offset, + PropertyVisibility Visibility) where TOwner: unmanaged; + public abstract bool CreateF32(out IFProperty? NewProperty, string Name, int Offset, + PropertyVisibility Visibility) where TOwner: unmanaged; + public abstract bool CreateF64(out IFProperty? NewProperty, string Name, int Offset, + PropertyVisibility Visibility) where TOwner: unmanaged; + + #endregion + + protected readonly IUnrealFactory Factory = factory; + protected readonly IUnrealMemory Memory = memory; + protected readonly IUnrealClasses Classes = classes; + + protected const int FIELD_ALIGNMENT = 0x80; +} + +[Flags] +public enum PropertyBuilderFlags +{ + NoCtor = 1 << 0, // ZeroConstructor, can just be memset + Copy = 1 << 1, // If property can be memcpy'd. + NoDtor = 1 << 2, // No destructor + Hash = 1 << 3, // Can be hashed +} \ No newline at end of file diff --git a/UE.Toolkit.Reloaded/Reflection/UE4_27_2/PropertyFactory.cs b/UE.Toolkit.Reloaded/Reflection/UE4_27_2/PropertyFactory.cs new file mode 100644 index 0000000..90afafd --- /dev/null +++ b/UE.Toolkit.Reloaded/Reflection/UE4_27_2/PropertyFactory.cs @@ -0,0 +1,138 @@ +using System.Runtime.InteropServices; +using UE.Toolkit.Core.Types.Interfaces; +using UE.Toolkit.Core.Types.Unreal.Factories; +using UE.Toolkit.Core.Types.Unreal.Factories.Interfaces; +using UE.Toolkit.Core.Types.Unreal.UE4_27_2; +using UE.Toolkit.Interfaces; +using FField = UE.Toolkit.Core.Types.Unreal.UE4_27_2.FField; +using FName = UE.Toolkit.Core.Types.Unreal.UE5_4_4.FName; +using EFindName = UE.Toolkit.Core.Types.Unreal.UE5_4_4.EFindName; +using EObjectFlags = UE.Toolkit.Core.Types.Unreal.UE5_4_4.EObjectFlags; +using EPropertyFlags = UE.Toolkit.Core.Types.Unreal.UE5_4_4.EPropertyFlags; +using FFieldClass = UE.Toolkit.Core.Types.Unreal.UE4_27_2.FFieldClass; +using FProperty = UE.Toolkit.Core.Types.Unreal.UE4_27_2.FProperty; +using UObjectBase = UE.Toolkit.Core.Types.Unreal.UE4_27_2.UObjectBase; + +namespace UE.Toolkit.Reloaded.Reflection.UE4_27_2; + +public class PropertyFactory(IUnrealFactory factory, IUnrealMemory memory, IUnrealClasses classes) + : BasePropertyFactory(factory, memory, classes) +{ + + protected override EPropertyFlags CreatePropertyFlags(PropertyVisibility Visibility, PropertyBuilderFlags InFlags) + { + var Flags = EPropertyFlags.CPF_Edit | EPropertyFlags.CPF_BlueprintVisible; + if (InFlags.HasFlag(PropertyBuilderFlags.NoCtor)) Flags |= EPropertyFlags.CPF_ZeroConstructor; + if (InFlags.HasFlag(PropertyBuilderFlags.Copy)) Flags |= EPropertyFlags.CPF_IsPlainOldData; + if (InFlags.HasFlag(PropertyBuilderFlags.NoDtor)) Flags |= EPropertyFlags.CPF_NoDestructor; + if (InFlags.HasFlag(PropertyBuilderFlags.Hash)) Flags |= EPropertyFlags.CPF_HasGetValueTypeHash; + Flags |= Visibility switch + { + PropertyVisibility.Public => EPropertyFlags.CPF_NativeAccessSpecifierPublic, + PropertyVisibility.Protected => EPropertyFlags.CPF_NativeAccessSpecifierProtected, + PropertyVisibility.Private => EPropertyFlags.CPF_NativeAccessSpecifierPrivate, + }; + return Flags; + } + + protected override unsafe void LinkToPropertyList(IFProperty Property, IUClass Reflect) + { + var pProperty = (FProperty*)Property.Ptr; + pProperty->prop_link_next = null; + pProperty->next_ref = null; + pProperty->dtor_link_next = null; + pProperty->post_ct_link_next = null; + + var pClass = (UClass*)Reflect.Ptr; + if (((UStruct*)pClass)->prop_link == null) + { + ((UStruct*)pClass)->prop_link = pProperty; + } + else + { + var LastProp = Reflect.PropertyLink.Last(); + var LastFProp = (FProperty*)LastProp.Ptr; + LastFProp->prop_link_next = pProperty; + ((FField*)LastFProp)->next = (FField*)pProperty; + /* + var FirstProp = Reflect.PropertyLink.First(); + var FirstFProp = (FProperty*)FirstProp.Ptr; + var NextProp = FirstFProp->prop_link_next; + if (NextProp != null) + pProperty->prop_link_next = NextProp; + FirstFProp->prop_link_next = pProperty; + */ + } + } + + protected override unsafe void SetPropertySuperFields(IFField Field, string Name, IUClass ClassReflection, + FieldClassGlobal PropertyClass) + { + var pField = (FField*)Field.Ptr; + pField->_vtable = PropertyClass.Vtable; + pField->class_private = (FFieldClass*)PropertyClass.Params.Ptr; + pField->owner.Object = (UObjectBase*)ClassReflection.Ptr; // UClass* + pField->next = null; + pField->name_private = new FName(Name); + pField->flags_private = EObjectFlags.RF_Public | EObjectFlags.RF_MarkAsNative | EObjectFlags.RF_Transient; + } + + protected override unsafe void SetCopyPropertyFields(IFProperty Property, int Offset, PropertyVisibility Visibility) + { + var pProperty = (FProperty*)Property.Ptr; + pProperty->array_dim = 1; + pProperty->element_size = Marshal.SizeOf(); + var PropertyFlags = PropertyBuilderFlags.NoCtor | PropertyBuilderFlags.Copy | PropertyBuilderFlags.NoDtor; + pProperty->property_flags = CreatePropertyFlags(Visibility, PropertyFlags); + pProperty->rep_index = 0; + pProperty->blueprint_rep_cond = 0; + pProperty->offset_internal = Offset; + pProperty->rep_notify_func = new(); + } + + private unsafe bool CreateCopyPropertyInner(out IFProperty? NewProperty, + string Name, int Offset, string PropertyName, PropertyVisibility Visibility) + where TOwner : unmanaged + where TField : unmanaged + { + NewProperty = null; + if (!TryGetClassAndProperty(PropertyName, out var ClassReflection, out var PropertyClass)) + return false; + var NewFProperty = (FProperty*)Memory.Malloc(Marshal.SizeOf(), FIELD_ALIGNMENT); + SetPropertySuperFields(Factory.CreateFField((nint)NewFProperty), Name, ClassReflection, PropertyClass); + NewProperty = Factory.CreateFProperty((nint)NewFProperty); + SetCopyPropertyFields(NewProperty, Offset, Visibility); + LinkToPropertyList(NewProperty, ClassReflection); + return true; + } + + public override bool CreateI8(out IFProperty? NewProperty, string Name, int Offset, PropertyVisibility Visibility) + => CreateCopyPropertyInner(out NewProperty, Name, Offset, "Int8Property", Visibility); + + public override bool CreateI16(out IFProperty? NewProperty, string Name, int Offset, PropertyVisibility Visibility) + => CreateCopyPropertyInner(out NewProperty, Name, Offset, "Int16Property", Visibility); + + public override bool CreateI32(out IFProperty? NewProperty, string Name, int Offset, PropertyVisibility Visibility) + => CreateCopyPropertyInner(out NewProperty, Name, Offset, "IntProperty", Visibility); + + public override bool CreateI64(out IFProperty? NewProperty, string Name, int Offset, PropertyVisibility Visibility) + => CreateCopyPropertyInner(out NewProperty, Name, Offset, "Int64Property", Visibility); + + public override bool CreateU8(out IFProperty? NewProperty, string Name, int Offset, PropertyVisibility Visibility) + => CreateCopyPropertyInner(out NewProperty, Name, Offset, "UInt8Property", Visibility); + + public override bool CreateU16(out IFProperty? NewProperty, string Name, int Offset, PropertyVisibility Visibility) + => CreateCopyPropertyInner(out NewProperty, Name, Offset, "UInt16Property", Visibility); + + public override bool CreateU32(out IFProperty? NewProperty, string Name, int Offset, PropertyVisibility Visibility) + => CreateCopyPropertyInner(out NewProperty, Name, Offset, "UInt32Property", Visibility); + + public override bool CreateU64(out IFProperty? NewProperty, string Name, int Offset, PropertyVisibility Visibility) + => CreateCopyPropertyInner(out NewProperty, Name, Offset, "UInt64Property", Visibility); + + public override bool CreateF32(out IFProperty? NewProperty, string Name, int Offset, PropertyVisibility Visibility) + => CreateCopyPropertyInner(out NewProperty, Name, Offset, "FFloatProperty", Visibility); + + public override bool CreateF64(out IFProperty? NewProperty, string Name, int Offset, PropertyVisibility Visibility) + => CreateCopyPropertyInner(out NewProperty, Name, Offset, "FDoubleProperty", Visibility); +} \ No newline at end of file diff --git a/UE.Toolkit.Reloaded/Reflection/UE5_4_4/PropertyFactory.cs b/UE.Toolkit.Reloaded/Reflection/UE5_4_4/PropertyFactory.cs new file mode 100644 index 0000000..ca0ec13 --- /dev/null +++ b/UE.Toolkit.Reloaded/Reflection/UE5_4_4/PropertyFactory.cs @@ -0,0 +1,119 @@ +using System.Runtime.InteropServices; +using UE.Toolkit.Core.Types.Interfaces; +using UE.Toolkit.Core.Types.Unreal.Factories; +using UE.Toolkit.Core.Types.Unreal.Factories.Interfaces; +using UE.Toolkit.Core.Types.Unreal.UE5_4_4; +using UE.Toolkit.Interfaces; + +namespace UE.Toolkit.Reloaded.Reflection.UE5_4_4; + +public class PropertyFactory(IUnrealFactory factory, IUnrealMemory memory, IUnrealClasses classes) + : BasePropertyFactory(factory, memory, classes) +{ + protected override EPropertyFlags CreatePropertyFlags(PropertyVisibility Visibility, PropertyBuilderFlags InFlags) + { + var Flags = EPropertyFlags.CPF_Edit | EPropertyFlags.CPF_BlueprintVisible; + if (InFlags.HasFlag(PropertyBuilderFlags.NoCtor)) Flags |= EPropertyFlags.CPF_ZeroConstructor; + if (InFlags.HasFlag(PropertyBuilderFlags.Copy)) Flags |= EPropertyFlags.CPF_IsPlainOldData; + if (InFlags.HasFlag(PropertyBuilderFlags.NoDtor)) Flags |= EPropertyFlags.CPF_NoDestructor; + if (InFlags.HasFlag(PropertyBuilderFlags.Hash)) Flags |= EPropertyFlags.CPF_HasGetValueTypeHash; + Flags |= Visibility switch + { + PropertyVisibility.Public => EPropertyFlags.CPF_NativeAccessSpecifierPublic, + PropertyVisibility.Protected => EPropertyFlags.CPF_NativeAccessSpecifierProtected, + PropertyVisibility.Private => EPropertyFlags.CPF_NativeAccessSpecifierPrivate, + }; + return Flags; + } + + protected override unsafe void LinkToPropertyList(IFProperty Property, IUClass Reflect) + { + var pProperty = (FProperty*)Property.Ptr; + pProperty->PropertyLinkNext = null; + pProperty->NextRef = null; + pProperty->DestructorLinkNext = null; + pProperty->PostConstructLinkNext = null; + + var pClass = (UClass*)Reflect.Ptr; + if (((UStruct*)pClass)->PropertyLink == null) + { + ((UStruct*)pClass)->PropertyLink = pProperty; + } + else + { + var LastProp = Reflect.PropertyLink.Last(); + var LastFProp = (FProperty*)LastProp.Ptr; + LastFProp->PropertyLinkNext = pProperty; + } + } + + protected override unsafe void SetPropertySuperFields(IFField Field, string Name, IUClass ClassReflection, + FieldClassGlobal PropertyClass) + { + var pField = (FField*)Field.Ptr; + pField->VTable = PropertyClass.Vtable; + pField->ClassPrivate = (FFieldClass*)PropertyClass.Params.Ptr; + pField->Owner.Object = (UObjectBase*)ClassReflection.Ptr; // UClass* + pField->Next = null; + pField->NamePrivate = new FName(Name); + pField->FlagsPrivate = EObjectFlags.RF_Public | EObjectFlags.RF_MarkAsNative | EObjectFlags.RF_Transient; + } + + protected override unsafe void SetCopyPropertyFields(IFProperty Property, int Offset, PropertyVisibility Visibility) + { + var pProperty = (FProperty*)Property.Ptr; + pProperty->ArrayDim = 1; + pProperty->ElementSize = Marshal.SizeOf(); + var PropertyFlags = PropertyBuilderFlags.NoCtor | PropertyBuilderFlags.Copy | PropertyBuilderFlags.NoDtor; + pProperty->PropertyFlags = CreatePropertyFlags(Visibility, PropertyFlags); + pProperty->RepIndex = 0; + pProperty->BlueprintReplicationCondition = 0; + pProperty->Offset_Internal = Offset; + } + + private unsafe bool CreateCopyPropertyInner(out IFProperty? NewProperty, + string Name, int Offset, string PropertyName, PropertyVisibility Visibility) + where TOwner : unmanaged + where TField : unmanaged + { + NewProperty = null; + if (!TryGetClassAndProperty(PropertyName, out var ClassReflection, out var PropertyClass)) + return false; + var NewFProperty = (FProperty*)Memory.Malloc(Marshal.SizeOf(), FIELD_ALIGNMENT); + SetPropertySuperFields(Factory.CreateFField((nint)NewFProperty), Name, ClassReflection, PropertyClass); + NewProperty = Factory.CreateFProperty((nint)NewFProperty); + SetCopyPropertyFields(NewProperty, Offset, Visibility); + LinkToPropertyList(NewProperty, ClassReflection); + return true; + } + + public override bool CreateI8(out IFProperty? NewProperty, string Name, int Offset, PropertyVisibility Visibility) + => CreateCopyPropertyInner(out NewProperty, Name, Offset, "Int8Property", Visibility); + + public override bool CreateI16(out IFProperty? NewProperty, string Name, int Offset, PropertyVisibility Visibility) + => CreateCopyPropertyInner(out NewProperty, Name, Offset, "Int16Property", Visibility); + + public override bool CreateI32(out IFProperty? NewProperty, string Name, int Offset, PropertyVisibility Visibility) + => CreateCopyPropertyInner(out NewProperty, Name, Offset, "IntProperty", Visibility); + + public override bool CreateI64(out IFProperty? NewProperty, string Name, int Offset, PropertyVisibility Visibility) + => CreateCopyPropertyInner(out NewProperty, Name, Offset, "Int64Property", Visibility); + + public override bool CreateU8(out IFProperty? NewProperty, string Name, int Offset, PropertyVisibility Visibility) + => CreateCopyPropertyInner(out NewProperty, Name, Offset, "UInt8Property", Visibility); + + public override bool CreateU16(out IFProperty? NewProperty, string Name, int Offset, PropertyVisibility Visibility) + => CreateCopyPropertyInner(out NewProperty, Name, Offset, "UInt16Property", Visibility); + + public override bool CreateU32(out IFProperty? NewProperty, string Name, int Offset, PropertyVisibility Visibility) + => CreateCopyPropertyInner(out NewProperty, Name, Offset, "UInt32Property", Visibility); + + public override bool CreateU64(out IFProperty? NewProperty, string Name, int Offset, PropertyVisibility Visibility) + => CreateCopyPropertyInner(out NewProperty, Name, Offset, "UInt64Property", Visibility); + + public override bool CreateF32(out IFProperty? NewProperty, string Name, int Offset, PropertyVisibility Visibility) + => CreateCopyPropertyInner(out NewProperty, Name, Offset, "FFloatProperty", Visibility); + + public override bool CreateF64(out IFProperty? NewProperty, string Name, int Offset, PropertyVisibility Visibility) + => CreateCopyPropertyInner(out NewProperty, Name, Offset, "FDoubleProperty", Visibility); +} \ No newline at end of file diff --git a/UE.Toolkit.Reloaded/Unreal/UnrealClasses.cs b/UE.Toolkit.Reloaded/Unreal/UnrealClasses.cs index 4c322f7..41c59a3 100644 --- a/UE.Toolkit.Reloaded/Unreal/UnrealClasses.cs +++ b/UE.Toolkit.Reloaded/Unreal/UnrealClasses.cs @@ -1,10 +1,14 @@ using System.Collections.Concurrent; using System.Runtime.InteropServices; using Reloaded.Hooks.Definitions; -// using UE.Toolkit.Core.Types.Unreal.Factories; +using UE.Toolkit.Core.Types.Interfaces; +using UE.Toolkit.Core.Types.Unreal.Factories; +using UE.Toolkit.Core.Types.Unreal.Factories.Interfaces; using UE.Toolkit.Core.Types.Unreal.UE5_4_4; using UE.Toolkit.Interfaces; using UE.Toolkit.Reloaded.Common; +using UE.Toolkit.Reloaded.Common.GameConfigs; +using UE.Toolkit.Reloaded.Reflection; namespace UE.Toolkit.Reloaded.Unreal; @@ -37,6 +41,8 @@ private void GetPrivateStaticClassBodyUE4( TryExtendClass(name, inClassCtor, ref size); _GetPrivateStaticClassBodyUE4!.Hook!.OriginalFunction(packageName, name, returnClass, registerNativeFunc, size, align, flags, castFlags, config, inClassCtor, vtableHelperCtorCaller, addRefObjects, superFn, withinFn, isDynamic, dynamicFn); + AddToClassList(name, returnClass); + TryHookCDO(returnClass); } // NOTE: 5.0 has it's own signature @@ -65,11 +71,14 @@ private void GetPrivateStaticClassBodyUE5( TryExtendClass(name, inClassCtor, ref size); _GetPrivateStaticClassBodyUE5!.Hook!.OriginalFunction(packageName, name, returnClass, registerNativeFunc, size, align, flags, castFlags, config, inClassCtor, vtableHelperCtorCaller, staticFn, superFn, withinFn); + AddToClassList(name, returnClass); + TryHookCDO(returnClass); } private void TryExtendClass(nint pClassName, nint classCtor, ref uint Size) { var ClassName = Marshal.PtrToStringUni(pClassName); + if (ClassName == null) return; if (ClassName == null || !ClassExtensions.TryGetValue(ClassName, out var Extension)) return; var OldSize = Size; Size += Extension.ExtraSize; @@ -77,7 +86,18 @@ private void TryExtendClass(nint pClassName, nint classCtor, ref uint Size) { Extension.ConstructorHook = FollowThunkToGetAppropriateHook(classCtor, Extension.Constructor); } - Log.Debug($"{ClassName} size: {OldSize} -> {Size}"); + + if (OldSize != Size) + { + Log.Debug($"Extended {ClassName}: new size ({OldSize} -> {Size})"); + } + } + + private void AddToClassList(nint name, UClass** returnClass) + { + var ClassName = Marshal.PtrToStringUni(name); + if (ClassName == null) return; + Classes.GetOrAdd(ClassName, Factory.CreateUClass((nint)(*returnClass))); } private IHook FollowThunkToGetAppropriateHook @@ -100,24 +120,106 @@ private IHook FollowThunkToGetAppropriateHo ctorHookReal += ctorHook; var offset = (int)(addr - BaseAddress); var transformed = Address.GetAddressMayThunk(offset); - Log.Debug($"Transformed address: {addr:X} -> {transformed:X}"); + // Log.Debug($"Transformed address: {addr:X} -> {transformed:X}"); retHook = Hooks.CreateHook(ctorHookReal, (long)transformed).Activate(); return retHook; } + + private void TryHookCDO(UClass** ppClass) + { + if (_CreateDefaultObject != null) return; + Log.Debug($"0x{*(nint*)(*ppClass) + CreateDefaultObjectOffset:x}"); + _CreateDefaultObject = Hooks.CreateHook(UClass_CreateDefaultObjectImpl, + *(nint*)(*(nint*)(*ppClass) + CreateDefaultObjectOffset)).Activate(); + } + + private delegate nint UClass_CreateDefaultObject(nint pClass); + private IHook? _CreateDefaultObject = null; + private uint CreateDefaultObjectOffset; + + private nint UClass_CreateDefaultObjectImpl(nint pClass) + { + var ClassDefaultObject = _CreateDefaultObject!.OriginalFunction(pClass); + var Class = Factory.CreateUClass(pClass); + foreach (var Property in Class.PropertyLink) + FieldClasses.TryAdd(((FFieldClass*)Property.ClassPrivate.Ptr)->Name.ComparisonIndex.Value, + new(Property.VTable, Property.ClassPrivate)); + return ClassDefaultObject; + } + + #region IUnrealClasses implementation public void AddExtension(uint ExtraSize, Action> callback) where TObject : unmanaged => ClassExtensions.GetOrAdd(typeof(TObject).Name[1..], new ClassExtension(ExtraSize, - obj => callback(new((TObject*)*(nint*)obj)))); + obj => callback(new((TObject*)*(nint*)obj)))); + + public bool GetClassInfoFromClass(out IUClass? Value) where TObject : unmanaged + => GetClassInfoFromName(typeof(TObject).Name, out Value); + + public bool GetClassInfoFromName(string Name, out IUClass? Value) + { + Value = Classes.GetValueOrDefault(Name[1..]); + return Value != null; + } + + public void AddI8Property(string Name, int Offset) where TObject : unmanaged + => PropertyFactory.CreateI8(out _, Name, Offset, PropertyVisibility.Public); + public void AddI16Property(string Name, int Offset) where TObject : unmanaged + => PropertyFactory.CreateI16(out _, Name, Offset, PropertyVisibility.Public); + public void AddI32Property(string Name, int Offset) where TObject : unmanaged + => PropertyFactory.CreateI32(out _, Name, Offset, PropertyVisibility.Public); + public void AddI64Property(string Name, int Offset) where TObject : unmanaged + => PropertyFactory.CreateI64(out _, Name, Offset, PropertyVisibility.Public); + public void AddU8Property(string Name, int Offset) where TObject : unmanaged + => PropertyFactory.CreateU8(out _, Name, Offset, PropertyVisibility.Public); + public void AddU16Property(string Name, int Offset) where TObject : unmanaged + => PropertyFactory.CreateU16(out _, Name, Offset, PropertyVisibility.Public); + public void AddU32Property(string Name, int Offset) where TObject : unmanaged + => PropertyFactory.CreateU32(out _, Name, Offset, PropertyVisibility.Public); + public void AddU64Property(string Name, int Offset) where TObject : unmanaged + => PropertyFactory.CreateU64(out _, Name, Offset, PropertyVisibility.Public); + public void AddF32Property(string Name, int Offset) where TObject : unmanaged + => PropertyFactory.CreateF32(out _, Name, Offset, PropertyVisibility.Public); + public void AddF64Property(string Name, int Offset) where TObject : unmanaged + => PropertyFactory.CreateF64(out _, Name, Offset, PropertyVisibility.Public); + + #endregion + + #region IUnrealClassesInternal implementation + + public bool GetFieldClassGlobal(FName Name, out FieldClassGlobal FieldClass) + => FieldClasses.TryGetValue(Name.ComparisonIndex.Value, out FieldClass); + + #endregion private ConcurrentDictionary ClassExtensions = new(); + private ConcurrentDictionary Classes = new(); + private ConcurrentDictionary FieldClasses = new(); + + private IUnrealFactory Factory; + private IUnrealMemory Memory; private IReloadedHooks Hooks; private ResolveAddress Address; - public UnrealClasses(IReloadedHooks _Hooks, ResolveAddress _Address) + private BasePropertyFactory PropertyFactory; + + public UnrealClasses(IUnrealFactory _Factory, IUnrealMemory _Memory, IReloadedHooks _Hooks, ResolveAddress _Address) { + Factory = _Factory; + Memory = _Memory; Hooks = _Hooks; Address = _Address; + + PropertyFactory = GameConfig.Instance.Id switch + { + "P3R" => new Reflection.UE4_27_2.PropertyFactory(Factory, Memory, this), + _ => new Reflection.UE5_4_4.PropertyFactory(Factory, Memory, this), + }; + + Project.Inis.UsingSetting(Constants.UnrealIniId, "CreateDefaultObject", nameof(UClass), + value => CreateDefaultObjectOffset = value); + _GetPrivateStaticClassBodyUE4 = new(GetPrivateStaticClassBodyUE4); _GetPrivateStaticClassBodyUE5 = new(GetPrivateStaticClassBodyUE5); } diff --git a/UE.Toolkit.Reloaded/Unreal/UnrealObjects.cs b/UE.Toolkit.Reloaded/Unreal/UnrealObjects.cs index 3e7e798..42fcbc1 100644 --- a/UE.Toolkit.Reloaded/Unreal/UnrealObjects.cs +++ b/UE.Toolkit.Reloaded/Unreal/UnrealObjects.cs @@ -1,6 +1,6 @@ +using System.Collections.Concurrent; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using System.Text; using Reloaded.Hooks.Definitions; using Reloaded.Hooks.Definitions.Structs; using UE.Toolkit.Core.Common; @@ -24,6 +24,8 @@ public unsafe class UnrealObjects : IUnrealObjects private static IHook? _UObject_PostLoadSubobjects; private static Action? _onObjectLoaded; + + internal ConcurrentDictionary> OnCDOLoaded = new(); [UnmanagedCallersOnly(CallConvs = [typeof(CallConvStdcall)])] private static void UObject_PostLoadSubobjects(nint self, nint outerInstanceGraph) @@ -35,8 +37,12 @@ private static void UObject_PostLoadSubobjects(nint self, nint outerInstanceGrap _onObjectLoaded?.Invoke(self); } + private IUnrealFactory Factory; + public UnrealObjects(IUnrealFactory factory) { + Factory = factory; + Project.Scans.AddScanHook(nameof(UObject_PostLoadSubobjects), (result, hooks) => _UObject_PostLoadSubobjects = hooks.CreateHook((delegate* unmanaged[Stdcall])&UObject_PostLoadSubobjects, result).Activate()); From ec62b6b26d6b8525da5ac1964753edf12d1df747 Mon Sep 17 00:00:00 2001 From: Rirurin Date: Wed, 12 Nov 2025 13:15:35 +0800 Subject: [PATCH 03/12] Implement more FProperty registration types (Struct, Object, Array) --- .../Interfaces/IUnrealClassesInternal.cs | 8 + UE.Toolkit.Interfaces/IUnrealClasses.cs | 132 +++++++++- .../UE.Toolkit.Reloaded/p3r/unreal.ini | 4 +- .../Project/UE.Toolkit.Reloaded/unreal.ini | 4 +- .../Reflection/PropertyFactory.cs | 144 +++++++++- .../Reflection/UE4_27_2/PropertyFactory.cs | 158 ++++++++--- .../Reflection/UE5_4_4/PropertyFactory.cs | 148 +++++++++-- UE.Toolkit.Reloaded/Unreal/UnrealClasses.cs | 249 ++++++++++++++---- 8 files changed, 720 insertions(+), 127 deletions(-) diff --git a/UE.Toolkit.Core/Types/Interfaces/IUnrealClassesInternal.cs b/UE.Toolkit.Core/Types/Interfaces/IUnrealClassesInternal.cs index a170b1a..74f824a 100644 --- a/UE.Toolkit.Core/Types/Interfaces/IUnrealClassesInternal.cs +++ b/UE.Toolkit.Core/Types/Interfaces/IUnrealClassesInternal.cs @@ -12,4 +12,12 @@ public class FieldClassGlobal(nint vtable, IFFieldClass param) public interface IUnrealClassesInternal { public bool GetFieldClassGlobal(FName Name, out FieldClassGlobal FieldClass); +} + +[Flags] +public enum StructType +{ + None = 0, + Class = 1 << 0, + ScriptStruct = 1 << 1 } \ No newline at end of file diff --git a/UE.Toolkit.Interfaces/IUnrealClasses.cs b/UE.Toolkit.Interfaces/IUnrealClasses.cs index 98464e4..4d0fa24 100644 --- a/UE.Toolkit.Interfaces/IUnrealClasses.cs +++ b/UE.Toolkit.Interfaces/IUnrealClasses.cs @@ -30,6 +30,7 @@ public void AddConstructor(Action> callback) /// /// Get the type information for a specified object. If this class has no type info, this will return null. + /// This method works any type derived from UObject, which will be any type prefixed with U or A. /// /// Type information for the specified object type. /// Object type. @@ -38,11 +39,30 @@ public void AddConstructor(Action> callback) /// /// Get the type information for a specified object. If this class has no type info, this will return null. + /// This method works any type derived from UObject, which will be any type prefixed with U or A. /// /// Name of the object type. /// Type information for the object type with the given name. /// If the type information exists. public bool GetClassInfoFromName(string Name, out IUClass? Value); + + /// + /// Get the type information for a specified object. If this struct has no type info, this will return null. + /// This method works on types that are prefixed with F. + /// + /// Type information for the specified object type. + /// Object type. + /// If the type information exists. + public bool GetScriptStructInfoFromType(out IUScriptStruct? Value) where TObject : unmanaged; + + /// + /// Get the type information for a specified object. If this struct has no type info, this will return null. + /// This method works on types that are prefixed with F. + /// + /// Name of the object type. + /// Type information for the object type with the given name. + /// If the type information exists. + public bool GetScriptStructInfoFromName(string Name, out IUScriptStruct? Value); /// /// Add an Int8 property to the object's class with the specified name and offset. This will make the field @@ -51,7 +71,7 @@ public void AddConstructor(Action> callback) /// Name of the new field. /// Offset of the new field. /// Object type. - public void AddI8Property(string Name, int Offset) where TObject : unmanaged; + public bool AddI8Property(string Name, int Offset, out IFProperty? Out) where TObject : unmanaged; /// /// Add a Int16 property to the object's class with the specified name and offset. This will make the field @@ -60,7 +80,7 @@ public void AddConstructor(Action> callback) /// Name of the new field. /// Offset of the new field. /// Object type. - public void AddI16Property(string Name, int Offset) where TObject : unmanaged; + public bool AddI16Property(string Name, int Offset, out IFProperty? Out) where TObject : unmanaged; /// /// Add a Int32 property to the object's class with the specified name and offset. This will make the field @@ -69,7 +89,7 @@ public void AddConstructor(Action> callback) /// Name of the new field. /// Offset of the new field. /// Object type. - public void AddI32Property(string Name, int Offset) where TObject : unmanaged; + public bool AddI32Property(string Name, int Offset, out IFProperty? Out) where TObject : unmanaged; /// /// Add a Int64 property to the object's class with the specified name and offset. This will make the field @@ -78,7 +98,7 @@ public void AddConstructor(Action> callback) /// Name of the new field. /// Offset of the new field. /// Object type. - public void AddI64Property(string Name, int Offset) where TObject : unmanaged; + public bool AddI64Property(string Name, int Offset, out IFProperty? Out) where TObject : unmanaged; /// /// Add a UInt8 property to the object's class with the specified name and offset. This will make the field @@ -87,7 +107,7 @@ public void AddConstructor(Action> callback) /// Name of the new field. /// Offset of the new field. /// Object type. - public void AddU8Property(string Name, int Offset) where TObject : unmanaged; + public bool AddU8Property(string Name, int Offset, out IFProperty? Out) where TObject : unmanaged; /// /// Add a UInt16 property to the object's class with the specified name and offset. This will make the field @@ -96,7 +116,7 @@ public void AddConstructor(Action> callback) /// Name of the new field. /// Offset of the new field. /// Object type. - public void AddU16Property(string Name, int Offset) where TObject : unmanaged; + public bool AddU16Property(string Name, int Offset, out IFProperty? Out) where TObject : unmanaged; /// /// Add a UInt32 property to the object's class with the specified name and offset. This will make the field @@ -105,7 +125,7 @@ public void AddConstructor(Action> callback) /// Name of the new field. /// Offset of the new field. /// Object type. - public void AddU32Property(string Name, int Offset) where TObject : unmanaged; + public bool AddU32Property(string Name, int Offset, out IFProperty? Out) where TObject : unmanaged; /// /// Add a UInt64 property to the object's class with the specified name and offset. This will make the field @@ -114,7 +134,7 @@ public void AddConstructor(Action> callback) /// Name of the new field. /// Offset of the new field. /// Object type. - public void AddU64Property(string Name, int Offset) where TObject : unmanaged; + public bool AddU64Property(string Name, int Offset, out IFProperty? Out) where TObject : unmanaged; /// /// Add a float property to the object's class with the specified name and offset. This will make the field @@ -123,7 +143,7 @@ public void AddConstructor(Action> callback) /// Name of the new field. /// Offset of the new field. /// Object type. - public void AddF32Property(string Name, int Offset) where TObject : unmanaged; + public bool AddF32Property(string Name, int Offset, out IFProperty? Out) where TObject : unmanaged; /// /// Add a double property to the object's class with the specified name and offset. This will make the field @@ -132,5 +152,97 @@ public void AddConstructor(Action> callback) /// Name of the new field. /// Offset of the new field. /// Object type. - public void AddF64Property(string Name, int Offset) where TObject : unmanaged; + public bool AddF64Property(string Name, int Offset, out IFProperty? Out) where TObject : unmanaged; + + /// + /// Add a C-style boolean to the object's class with the specified name and offset. This will make the field + /// exposable to blueprints and Object XML. + /// + /// Name of the new field. + /// Offset of the new field. + /// Object type. + public bool AddCBoolProperty(string Name, int Offset, out IFBoolProperty? Out) where TObject : unmanaged; + + /// + /// Add a bitflag-style boolean to the object's class with the specified name and offset. This will make the field + /// exposable to blueprints and Object XML. + /// + /// Name of the new field. + /// Byte offset of the new field. + /// Bit offset of the new field. + /// Object type. + public bool AddBitBoolProperty(string Name, int Offset, int Bit, out IFBoolProperty? Out) where TObject : unmanaged; + + /// + /// Add a by-value struct to the object's class with the specified name and offset. This will make the field + /// exposable to blueprints and Object XML. + /// + /// Name of the new field. + /// Offset of the new field. + /// Object type. + /// Field type. + public bool AddStructProperty(string Name, int Offset, out IFStructProperty? Out) + where TObject : unmanaged + where TField : unmanaged; + + /// + /// Add a by-reference class in the form of a raw pointer to the object's class with the specified name and offset. + /// This will make the field exposable to blueprints and Object XML. + /// + /// Name of the new field. + /// Offset of the new field. + /// Object type. + /// Field type. + public bool AddObjectProperty(string Name, int Offset, out IFObjectProperty? Out) + where TObject : unmanaged + where TField : unmanaged; + + /// + /// Add a by-reference class in the form of a type-safe pointer to the object's class with the specified name and offset. + /// This will make the field exposable to blueprints and Object XML. + /// + /// Name of the new field. + /// Offset of the new field. + /// Object type. + /// Field type. + public bool AddClassProperty(string Name, int Offset, out IFClassProperty? Out) + where TObject : unmanaged + where TField : unmanaged; + + /// + /// Add a FName to the object's class with the specified name and offset. This will make the field + /// exposable to blueprints and Object XML. + /// + /// Name of the new field. + /// Offset of the new field. + /// Object type. + public bool AddNameProperty(string Name, int Offset, out IFProperty? Out) where TObject : unmanaged; + + /// + /// Add a FString to the object's class with the specified name and offset. This will make the field + /// exposable to blueprints and Object XML. + /// + /// Name of the new field. + /// Offset of the new field. + /// Object type. + public bool AddStringProperty(string Name, int Offset, out IFProperty? Out) where TObject : unmanaged; + + /// + /// Add a FText to the object's class with the specified name and offset. This will make the field + /// exposable to blueprints and Object XML. + /// + /// Name of the new field. + /// Offset of the new field. + /// Object type. + public bool AddTextProperty(string Name, int Offset, out IFProperty? Out) where TObject : unmanaged; + + /// + /// Add a Array (TArray) containing elememts of the property defined in Inner to the object's class with the + /// specified name and offset. This will make the field exposable to blueprints and Object XML. + /// + /// Name of the new field. + /// Offset of the new field. + /// Object type. + public bool AddArrayProperty(string Name, int Offset, IFProperty Inner, out IFArrayProperty? Property) + where TObject : unmanaged; } \ No newline at end of file diff --git a/UE.Toolkit.Reloaded/Project/UE.Toolkit.Reloaded/p3r/unreal.ini b/UE.Toolkit.Reloaded/Project/UE.Toolkit.Reloaded/p3r/unreal.ini index 34c51b8..542b7b4 100644 --- a/UE.Toolkit.Reloaded/Project/UE.Toolkit.Reloaded/p3r/unreal.ini +++ b/UE.Toolkit.Reloaded/Project/UE.Toolkit.Reloaded/p3r/unreal.ini @@ -5,5 +5,5 @@ Malloc=0x10 Realloc=0x20 QuantizeSize=0x38 -[UClass] -CreateDefaultObject=0x3a0 \ No newline at end of file +[UStruct] +Link=0x288 \ No newline at end of file diff --git a/UE.Toolkit.Reloaded/Project/UE.Toolkit.Reloaded/unreal.ini b/UE.Toolkit.Reloaded/Project/UE.Toolkit.Reloaded/unreal.ini index d7817ae..a8282f9 100644 --- a/UE.Toolkit.Reloaded/Project/UE.Toolkit.Reloaded/unreal.ini +++ b/UE.Toolkit.Reloaded/Project/UE.Toolkit.Reloaded/unreal.ini @@ -7,5 +7,5 @@ Malloc=0x28 Realloc=0x38 QuantizeSize=0x50 -[UClass] -CreateDefaultObject=0x3f0 \ No newline at end of file +[UStruct] +Link=0x2d0 \ No newline at end of file diff --git a/UE.Toolkit.Reloaded/Reflection/PropertyFactory.cs b/UE.Toolkit.Reloaded/Reflection/PropertyFactory.cs index 146e6ea..3573abc 100644 --- a/UE.Toolkit.Reloaded/Reflection/PropertyFactory.cs +++ b/UE.Toolkit.Reloaded/Reflection/PropertyFactory.cs @@ -1,4 +1,5 @@ -using UE.Toolkit.Core.Types.Interfaces; +using System.Runtime.InteropServices; +using UE.Toolkit.Core.Types.Interfaces; using UE.Toolkit.Core.Types.Unreal.Factories; using UE.Toolkit.Core.Types.Unreal.Factories.Interfaces; using UE.Toolkit.Core.Types.Unreal.UE5_4_4; @@ -42,6 +43,14 @@ public abstract class BasePropertyFactory(IUnrealFactory factory, IUnrealMemory { "SetProperty", new FName("SetProperty", EFindName.FNAME_Find) }, }; + public bool CheckPropertyEquality(string Name, uint OtherValue) + { + PropertyNames ??= InitializePropertyNames(); + if (!PropertyNames.TryGetValue(Name, out var Value)) + return false; + return Value.ComparisonIndex.Value == OtherValue; + } + private bool GetProperty(string Name, out FieldClassGlobal? FieldClass) { FieldClass = null; @@ -71,31 +80,157 @@ protected abstract void SetPropertySuperFields(IFField Field, string Name, IUCla protected abstract void SetCopyPropertyFields(IFProperty Property, int Offset, PropertyVisibility Visibility) where T : unmanaged; + protected abstract void SetStringPropertyFields(IFProperty Property, int Offset, PropertyVisibility Visibility) + where T : unmanaged; + + protected abstract void SetTextPropertyFields(IFProperty Property, int Offset, PropertyVisibility Visibility) + where T : unmanaged; + + private bool CreatePropertyInner(out IFProperty? NewProperty, + string Name, int Offset, string PropertyName, PropertyVisibility Visibility, + Action Callback) + where TOwner : unmanaged + where TField : unmanaged + where TProperty : unmanaged + { + NewProperty = null; + if (!TryGetClassAndProperty(PropertyName, out var ClassReflection, out var PropertyClass)) + return false; + var Alloc = Memory.Malloc(Marshal.SizeOf(), FIELD_ALIGNMENT); + SetPropertySuperFields(Factory.CreateFField(Alloc), Name, ClassReflection, PropertyClass); + NewProperty = Factory.CreateFProperty(Alloc); + Callback(NewProperty, Offset, Visibility); + LinkToPropertyList(NewProperty, ClassReflection); + return true; + } + + protected bool CreateCopyPropertyInner(out IFProperty? NewProperty, + string Name, int Offset, string PropertyName, PropertyVisibility Visibility) + where TOwner : unmanaged + where TField : unmanaged + where TProperty : unmanaged + => CreatePropertyInner(out NewProperty, Name, + Offset, PropertyName, Visibility, SetCopyPropertyFields); + + protected bool CreateStringPropertyInner(out IFProperty? NewProperty, + string Name, int Offset, string PropertyName, PropertyVisibility Visibility) + where TOwner : unmanaged + where TField : unmanaged + where TProperty : unmanaged + => CreatePropertyInner(out NewProperty, Name, + Offset, PropertyName, Visibility, SetStringPropertyFields); + + protected bool CreateTextPropertyInner(out IFProperty? NewProperty, + string Name, int Offset, string PropertyName, PropertyVisibility Visibility) + where TOwner : unmanaged + where TField : unmanaged + where TProperty : unmanaged + => CreatePropertyInner(out NewProperty, Name, + Offset, PropertyName, Visibility, SetTextPropertyFields); + + protected abstract void SetBoolPropertyFields(IFBoolProperty Property, BooleanMask Mask); + + protected bool CreateBoolPropertyInner(out IFBoolProperty? NewProperty, string Name, int Offset, + PropertyVisibility Visibility, BooleanMask Mask) where TOwner: unmanaged + { + NewProperty = null; + if (!CreateCopyPropertyInner(out var BaseProperty, Name, Offset, + "BoolProperty", Visibility)) + return false; + NewProperty = Factory.CreateFBoolProperty(BaseProperty.Ptr); + SetBoolPropertyFields(NewProperty, Mask); + return true; + } + #endregion #region PUBLIC INTERFACE public abstract bool CreateI8(out IFProperty? NewProperty, string Name, int Offset, PropertyVisibility Visibility) where TOwner: unmanaged; + public abstract bool CreateI16(out IFProperty? NewProperty, string Name, int Offset, PropertyVisibility Visibility) where TOwner: unmanaged; + public abstract bool CreateI32(out IFProperty? NewProperty, string Name, int Offset, PropertyVisibility Visibility) where TOwner: unmanaged; + public abstract bool CreateI64(out IFProperty? NewProperty, string Name, int Offset, PropertyVisibility Visibility) where TOwner: unmanaged; + public abstract bool CreateU8(out IFProperty? NewProperty, string Name, int Offset, PropertyVisibility Visibility) where TOwner: unmanaged; + public abstract bool CreateU16(out IFProperty? NewProperty, string Name, int Offset, PropertyVisibility Visibility) where TOwner: unmanaged; + public abstract bool CreateU32(out IFProperty? NewProperty, string Name, int Offset, PropertyVisibility Visibility) where TOwner: unmanaged; + public abstract bool CreateU64(out IFProperty? NewProperty, string Name, int Offset, PropertyVisibility Visibility) where TOwner: unmanaged; + public abstract bool CreateF32(out IFProperty? NewProperty, string Name, int Offset, PropertyVisibility Visibility) where TOwner: unmanaged; + public abstract bool CreateF64(out IFProperty? NewProperty, string Name, int Offset, PropertyVisibility Visibility) where TOwner: unmanaged; + public bool CreateCBool(out IFBoolProperty? NewProperty, string Name, int Offset, + PropertyVisibility Visibility) where TOwner: unmanaged + => CreateBoolPropertyInner(out NewProperty, Name, Offset, Visibility, new(1, 255)); + + public bool CreateBitBool(out IFBoolProperty? NewProperty, string Name, int Offset, + int Bit, PropertyVisibility Visibility) where TOwner: unmanaged + { + NewProperty = null; + if (Bit > 7) return false; + var Mask = (byte)(1 << Bit); + return CreateBoolPropertyInner(out NewProperty, Name, Offset, Visibility, new(Mask, Mask)); + } + + /* + public abstract bool CreateCBool(out IFBoolProperty? NewProperty, string Name, int Offset, + PropertyVisibility Visibility) where TOwner : unmanaged; + + public abstract bool CreateBitBool(out IFBoolProperty? NewProperty, string Name, int Offset, + int Bit, PropertyVisibility Visibility) where TOwner : unmanaged; + */ + + public abstract bool CreateStruct(out IFStructProperty? NewProperty, + string Name, int Offset, PropertyVisibility Visibility) + where TOwner : unmanaged + where TField : unmanaged; + + public abstract bool CreateObject(out IFObjectProperty? NewProperty, + string Name, int Offset, PropertyVisibility Visibility) + where TOwner : unmanaged + where TField : unmanaged; + + public bool CreateClass(out IFClassProperty? NewProperty, + string Name, int Offset, PropertyVisibility Visibility) + where TOwner : unmanaged + where TField : unmanaged + { + NewProperty = null; + if (!CreateObject(out var ObjectProperty, Name, Offset, Visibility)) + return false; + NewProperty = Factory.CreateFClassProperty(ObjectProperty.Ptr); + return true; + } + + public abstract bool CreateName(out IFProperty? NewProperty, string Name, int Offset, + PropertyVisibility Visibility) where TOwner: unmanaged; + + public abstract bool CreateString(out IFProperty? NewProperty, string Name, int Offset, + PropertyVisibility Visibility) where TOwner: unmanaged; + + public abstract bool CreateText(out IFProperty? NewProperty, string Name, int Offset, + PropertyVisibility Visibility) where TOwner: unmanaged; + + public abstract bool CreateArray(out IFArrayProperty? NewProperty, string Name, int Offset, + PropertyVisibility Visibility, IFProperty Inner) where TObject : unmanaged; + #endregion protected readonly IUnrealFactory Factory = factory; @@ -108,8 +243,15 @@ public abstract bool CreateF64(out IFProperty? NewProperty, string Name, [Flags] public enum PropertyBuilderFlags { + None = 0, NoCtor = 1 << 0, // ZeroConstructor, can just be memset Copy = 1 << 1, // If property can be memcpy'd. NoDtor = 1 << 2, // No destructor Hash = 1 << 3, // Can be hashed +} + +public struct BooleanMask(byte byteMask, byte fieldMask) +{ + public byte ByteMask { get; } = byteMask; + public byte FieldMask { get; } = fieldMask; } \ No newline at end of file diff --git a/UE.Toolkit.Reloaded/Reflection/UE4_27_2/PropertyFactory.cs b/UE.Toolkit.Reloaded/Reflection/UE4_27_2/PropertyFactory.cs index 90afafd..bdb5bd7 100644 --- a/UE.Toolkit.Reloaded/Reflection/UE4_27_2/PropertyFactory.cs +++ b/UE.Toolkit.Reloaded/Reflection/UE4_27_2/PropertyFactory.cs @@ -54,14 +54,6 @@ protected override unsafe void LinkToPropertyList(IFProperty Property, IUClass R var LastFProp = (FProperty*)LastProp.Ptr; LastFProp->prop_link_next = pProperty; ((FField*)LastFProp)->next = (FField*)pProperty; - /* - var FirstProp = Reflect.PropertyLink.First(); - var FirstFProp = (FProperty*)FirstProp.Ptr; - var NextProp = FirstFProp->prop_link_next; - if (NextProp != null) - pProperty->prop_link_next = NextProp; - FirstFProp->prop_link_next = pProperty; - */ } } @@ -77,62 +69,154 @@ protected override unsafe void SetPropertySuperFields(IFField Field, string Name pField->flags_private = EObjectFlags.RF_Public | EObjectFlags.RF_MarkAsNative | EObjectFlags.RF_Transient; } - protected override unsafe void SetCopyPropertyFields(IFProperty Property, int Offset, PropertyVisibility Visibility) + private unsafe void SetPropertyFieldDefaults(FProperty* pProperty, int Offset) { - var pProperty = (FProperty*)Property.Ptr; - pProperty->array_dim = 1; - pProperty->element_size = Marshal.SizeOf(); - var PropertyFlags = PropertyBuilderFlags.NoCtor | PropertyBuilderFlags.Copy | PropertyBuilderFlags.NoDtor; - pProperty->property_flags = CreatePropertyFlags(Visibility, PropertyFlags); pProperty->rep_index = 0; pProperty->blueprint_rep_cond = 0; pProperty->offset_internal = Offset; pProperty->rep_notify_func = new(); } - private unsafe bool CreateCopyPropertyInner(out IFProperty? NewProperty, - string Name, int Offset, string PropertyName, PropertyVisibility Visibility) - where TOwner : unmanaged - where TField : unmanaged + private unsafe void SetPropertyFieldsInner(IFProperty Property, int Offset, + PropertyVisibility Visibility, PropertyBuilderFlags PropertyFlags) { - NewProperty = null; - if (!TryGetClassAndProperty(PropertyName, out var ClassReflection, out var PropertyClass)) - return false; - var NewFProperty = (FProperty*)Memory.Malloc(Marshal.SizeOf(), FIELD_ALIGNMENT); - SetPropertySuperFields(Factory.CreateFField((nint)NewFProperty), Name, ClassReflection, PropertyClass); - NewProperty = Factory.CreateFProperty((nint)NewFProperty); - SetCopyPropertyFields(NewProperty, Offset, Visibility); - LinkToPropertyList(NewProperty, ClassReflection); - return true; + var pProperty = (FProperty*)Property.Ptr; + pProperty->array_dim = 1; + pProperty->element_size = Marshal.SizeOf(); + pProperty->property_flags = CreatePropertyFlags(Visibility, PropertyFlags); + SetPropertyFieldDefaults(pProperty, Offset); + } + + protected override unsafe void SetCopyPropertyFields(IFProperty Property, int Offset, + PropertyVisibility Visibility) + { + var PropertyFlags = PropertyBuilderFlags.NoCtor | PropertyBuilderFlags.Copy | PropertyBuilderFlags.NoDtor; + SetPropertyFieldsInner(Property, Offset, Visibility, PropertyFlags); + } + + protected override void SetStringPropertyFields(IFProperty Property, int Offset, + PropertyVisibility Visibility) + => SetPropertyFieldsInner(Property, Offset, Visibility, PropertyBuilderFlags.NoCtor); + + protected override void SetTextPropertyFields(IFProperty Property, int Offset, + PropertyVisibility Visibility) + => SetPropertyFieldsInner(Property, Offset, Visibility, PropertyBuilderFlags.None); + + protected override unsafe void SetBoolPropertyFields(IFBoolProperty Property, BooleanMask Mask) + { + var pBoolProperty = (FBoolProperty*)Property.Ptr; + pBoolProperty->field_size = (byte)Marshal.SizeOf(); + pBoolProperty->byte_offset = 0; + pBoolProperty->byte_mask = Mask.ByteMask; + pBoolProperty->field_mask = Mask.FieldMask; } public override bool CreateI8(out IFProperty? NewProperty, string Name, int Offset, PropertyVisibility Visibility) - => CreateCopyPropertyInner(out NewProperty, Name, Offset, "Int8Property", Visibility); + => CreateCopyPropertyInner(out NewProperty, Name, Offset, "Int8Property", Visibility); public override bool CreateI16(out IFProperty? NewProperty, string Name, int Offset, PropertyVisibility Visibility) - => CreateCopyPropertyInner(out NewProperty, Name, Offset, "Int16Property", Visibility); + => CreateCopyPropertyInner(out NewProperty, Name, Offset, "Int16Property", Visibility); public override bool CreateI32(out IFProperty? NewProperty, string Name, int Offset, PropertyVisibility Visibility) - => CreateCopyPropertyInner(out NewProperty, Name, Offset, "IntProperty", Visibility); + => CreateCopyPropertyInner(out NewProperty, Name, Offset, "IntProperty", Visibility); public override bool CreateI64(out IFProperty? NewProperty, string Name, int Offset, PropertyVisibility Visibility) - => CreateCopyPropertyInner(out NewProperty, Name, Offset, "Int64Property", Visibility); + => CreateCopyPropertyInner(out NewProperty, Name, Offset, "Int64Property", Visibility); public override bool CreateU8(out IFProperty? NewProperty, string Name, int Offset, PropertyVisibility Visibility) - => CreateCopyPropertyInner(out NewProperty, Name, Offset, "UInt8Property", Visibility); + => CreateCopyPropertyInner(out NewProperty, Name, Offset, "UInt8Property", Visibility); public override bool CreateU16(out IFProperty? NewProperty, string Name, int Offset, PropertyVisibility Visibility) - => CreateCopyPropertyInner(out NewProperty, Name, Offset, "UInt16Property", Visibility); + => CreateCopyPropertyInner(out NewProperty, Name, Offset, "UInt16Property", Visibility); public override bool CreateU32(out IFProperty? NewProperty, string Name, int Offset, PropertyVisibility Visibility) - => CreateCopyPropertyInner(out NewProperty, Name, Offset, "UInt32Property", Visibility); + => CreateCopyPropertyInner(out NewProperty, Name, Offset, "UInt32Property", Visibility); public override bool CreateU64(out IFProperty? NewProperty, string Name, int Offset, PropertyVisibility Visibility) - => CreateCopyPropertyInner(out NewProperty, Name, Offset, "UInt64Property", Visibility); + => CreateCopyPropertyInner(out NewProperty, Name, Offset, "UInt64Property", Visibility); public override bool CreateF32(out IFProperty? NewProperty, string Name, int Offset, PropertyVisibility Visibility) - => CreateCopyPropertyInner(out NewProperty, Name, Offset, "FFloatProperty", Visibility); + => CreateCopyPropertyInner(out NewProperty, Name, Offset, "FloatProperty", Visibility); public override bool CreateF64(out IFProperty? NewProperty, string Name, int Offset, PropertyVisibility Visibility) - => CreateCopyPropertyInner(out NewProperty, Name, Offset, "FDoubleProperty", Visibility); + => CreateCopyPropertyInner(out NewProperty, Name, Offset, "DoubleProperty", Visibility); + + public override bool CreateStruct(out IFStructProperty? NewProperty, string Name, int Offset, + PropertyVisibility Visibility) + { + NewProperty = null; + if (!TryGetClassAndProperty("StructProperty", out var ClassReflection, out var PropertyClass) + || !Classes.GetScriptStructInfoFromType(out var ScriptStruct)) + return false; + var Alloc = Memory.Malloc(Marshal.SizeOf(), FIELD_ALIGNMENT); + NewProperty = Factory.CreateFStructProperty(Alloc); + SetPropertySuperFields(Factory.CreateFField(Alloc), Name, ClassReflection, PropertyClass); + unsafe + { + var pProperty = (FProperty*)Alloc; + pProperty->array_dim = 1; + pProperty->element_size = ScriptStruct.PropertiesSize; // FExampleStruct mExampleField; + pProperty->property_flags = CreatePropertyFlags(Visibility, PropertyBuilderFlags.None); + SetPropertyFieldDefaults(pProperty, Offset); + } + LinkToPropertyList(NewProperty, ClassReflection); + unsafe { ((FStructProperty*)NewProperty.Ptr)->struct_data = (UScriptStruct*)ScriptStruct.Ptr; } + return true; + } + + public override bool CreateObject(out IFObjectProperty? NewProperty, string Name, int Offset, + PropertyVisibility Visibility) + { + NewProperty = null; + if (!TryGetClassAndProperty("ObjectProperty", out var ClassReflection, out var PropertyClass) + || !Classes.GetClassInfoFromClass(out var FieldClass)) + return false; + var Alloc = Memory.Malloc(Marshal.SizeOf(), FIELD_ALIGNMENT); + NewProperty = Factory.CreateFObjectProperty(Alloc); + SetPropertySuperFields(Factory.CreateFField(Alloc), Name, ClassReflection, PropertyClass); + unsafe + { + var pProperty = (FProperty*)Alloc; + pProperty->array_dim = 1; + pProperty->element_size = Marshal.SizeOf(); + // For ObjectProperty: UExampleClass* pExampleObject; + // For ClassProperty: TSubclassOf pExampleClass; + pProperty->property_flags = CreatePropertyFlags(Visibility, PropertyBuilderFlags.None); + SetPropertyFieldDefaults(pProperty, Offset); + } + LinkToPropertyList(NewProperty, ClassReflection); + unsafe { ((FObjectProperty*)NewProperty.Ptr)->prop_class = (UClass*)FieldClass.Ptr; } + return true; + } + + public override bool CreateName(out IFProperty? NewProperty, string Name, int Offset, PropertyVisibility Visibility) + => CreateCopyPropertyInner(out NewProperty, Name, Offset, "NameProperty", Visibility); + + public override bool CreateString(out IFProperty? NewProperty, string Name, int Offset, PropertyVisibility Visibility) + => CreateStringPropertyInner(out NewProperty, Name, Offset, "StringProperty", Visibility); + + public override bool CreateText(out IFProperty? NewProperty, string Name, int Offset, PropertyVisibility Visibility) + => CreateTextPropertyInner(out NewProperty, Name, Offset, "TextProperty", Visibility); + + public override bool CreateArray(out IFArrayProperty? NewProperty, string Name, int Offset, PropertyVisibility Visibility, + IFProperty Inner) + { + NewProperty = null; + if (!TryGetClassAndProperty("ArrayProperty", out var ClassReflection, out var PropertyClass)) + return false; + var Alloc = Memory.Malloc(Marshal.SizeOf(), FIELD_ALIGNMENT); + NewProperty = Factory.CreateFArrayProperty(Alloc); + SetPropertySuperFields(Factory.CreateFField(Alloc), Name, ClassReflection, PropertyClass); + unsafe + { + var pProperty = (FProperty*)Alloc; + pProperty->array_dim = 1; + pProperty->element_size = 0x10; // sizeof(TArray) + pProperty->property_flags = CreatePropertyFlags(Visibility, PropertyBuilderFlags.NoCtor); + SetPropertyFieldDefaults(pProperty, Offset); + } + LinkToPropertyList(NewProperty, ClassReflection); + unsafe { ((FArrayProperty*)Alloc)->inner = (FProperty*)Inner.Ptr; } + return true; + } } \ No newline at end of file diff --git a/UE.Toolkit.Reloaded/Reflection/UE5_4_4/PropertyFactory.cs b/UE.Toolkit.Reloaded/Reflection/UE5_4_4/PropertyFactory.cs index ca0ec13..6e1474e 100644 --- a/UE.Toolkit.Reloaded/Reflection/UE5_4_4/PropertyFactory.cs +++ b/UE.Toolkit.Reloaded/Reflection/UE5_4_4/PropertyFactory.cs @@ -59,61 +59,153 @@ protected override unsafe void SetPropertySuperFields(IFField Field, string Name pField->FlagsPrivate = EObjectFlags.RF_Public | EObjectFlags.RF_MarkAsNative | EObjectFlags.RF_Transient; } - protected override unsafe void SetCopyPropertyFields(IFProperty Property, int Offset, PropertyVisibility Visibility) + private unsafe void SetPropertyFieldDefaults(FProperty* pProperty, int Offset) + { + pProperty->RepIndex = 0; + pProperty->BlueprintReplicationCondition = 0; + pProperty->Offset_Internal = Offset; + } + + private unsafe void SetPropertyFieldsInner(IFProperty Property, int Offset, + PropertyVisibility Visibility, PropertyBuilderFlags PropertyFlags) { var pProperty = (FProperty*)Property.Ptr; pProperty->ArrayDim = 1; pProperty->ElementSize = Marshal.SizeOf(); - var PropertyFlags = PropertyBuilderFlags.NoCtor | PropertyBuilderFlags.Copy | PropertyBuilderFlags.NoDtor; pProperty->PropertyFlags = CreatePropertyFlags(Visibility, PropertyFlags); - pProperty->RepIndex = 0; - pProperty->BlueprintReplicationCondition = 0; - pProperty->Offset_Internal = Offset; + SetPropertyFieldDefaults(pProperty, Offset); } - private unsafe bool CreateCopyPropertyInner(out IFProperty? NewProperty, - string Name, int Offset, string PropertyName, PropertyVisibility Visibility) - where TOwner : unmanaged - where TField : unmanaged + protected override unsafe void SetCopyPropertyFields(IFProperty Property, int Offset, + PropertyVisibility Visibility) { - NewProperty = null; - if (!TryGetClassAndProperty(PropertyName, out var ClassReflection, out var PropertyClass)) - return false; - var NewFProperty = (FProperty*)Memory.Malloc(Marshal.SizeOf(), FIELD_ALIGNMENT); - SetPropertySuperFields(Factory.CreateFField((nint)NewFProperty), Name, ClassReflection, PropertyClass); - NewProperty = Factory.CreateFProperty((nint)NewFProperty); - SetCopyPropertyFields(NewProperty, Offset, Visibility); - LinkToPropertyList(NewProperty, ClassReflection); - return true; + var PropertyFlags = PropertyBuilderFlags.NoCtor | PropertyBuilderFlags.Copy | PropertyBuilderFlags.NoDtor; + SetPropertyFieldsInner(Property, Offset, Visibility, PropertyFlags); + } + + protected override void SetStringPropertyFields(IFProperty Property, int Offset, + PropertyVisibility Visibility) + => SetPropertyFieldsInner(Property, Offset, Visibility, PropertyBuilderFlags.NoCtor); + + protected override void SetTextPropertyFields(IFProperty Property, int Offset, + PropertyVisibility Visibility) + => SetPropertyFieldsInner(Property, Offset, Visibility, PropertyBuilderFlags.None); + + protected override unsafe void SetBoolPropertyFields(IFBoolProperty Property, BooleanMask Mask) + { + var pBoolProperty = (FBoolProperty*)Property.Ptr; + pBoolProperty->FieldSize = (byte)Marshal.SizeOf(); + pBoolProperty->ByteOffset = 0; + pBoolProperty->ByteMask = Mask.ByteMask; + pBoolProperty->FieldMask = Mask.FieldMask; } public override bool CreateI8(out IFProperty? NewProperty, string Name, int Offset, PropertyVisibility Visibility) - => CreateCopyPropertyInner(out NewProperty, Name, Offset, "Int8Property", Visibility); + => CreateCopyPropertyInner(out NewProperty, Name, Offset, "Int8Property", Visibility); public override bool CreateI16(out IFProperty? NewProperty, string Name, int Offset, PropertyVisibility Visibility) - => CreateCopyPropertyInner(out NewProperty, Name, Offset, "Int16Property", Visibility); + => CreateCopyPropertyInner(out NewProperty, Name, Offset, "Int16Property", Visibility); public override bool CreateI32(out IFProperty? NewProperty, string Name, int Offset, PropertyVisibility Visibility) - => CreateCopyPropertyInner(out NewProperty, Name, Offset, "IntProperty", Visibility); + => CreateCopyPropertyInner(out NewProperty, Name, Offset, "IntProperty", Visibility); public override bool CreateI64(out IFProperty? NewProperty, string Name, int Offset, PropertyVisibility Visibility) - => CreateCopyPropertyInner(out NewProperty, Name, Offset, "Int64Property", Visibility); + => CreateCopyPropertyInner(out NewProperty, Name, Offset, "Int64Property", Visibility); public override bool CreateU8(out IFProperty? NewProperty, string Name, int Offset, PropertyVisibility Visibility) - => CreateCopyPropertyInner(out NewProperty, Name, Offset, "UInt8Property", Visibility); + => CreateCopyPropertyInner(out NewProperty, Name, Offset, "UInt8Property", Visibility); public override bool CreateU16(out IFProperty? NewProperty, string Name, int Offset, PropertyVisibility Visibility) - => CreateCopyPropertyInner(out NewProperty, Name, Offset, "UInt16Property", Visibility); + => CreateCopyPropertyInner(out NewProperty, Name, Offset, "UInt16Property", Visibility); public override bool CreateU32(out IFProperty? NewProperty, string Name, int Offset, PropertyVisibility Visibility) - => CreateCopyPropertyInner(out NewProperty, Name, Offset, "UInt32Property", Visibility); + => CreateCopyPropertyInner(out NewProperty, Name, Offset, "UInt32Property", Visibility); public override bool CreateU64(out IFProperty? NewProperty, string Name, int Offset, PropertyVisibility Visibility) - => CreateCopyPropertyInner(out NewProperty, Name, Offset, "UInt64Property", Visibility); + => CreateCopyPropertyInner(out NewProperty, Name, Offset, "UInt64Property", Visibility); public override bool CreateF32(out IFProperty? NewProperty, string Name, int Offset, PropertyVisibility Visibility) - => CreateCopyPropertyInner(out NewProperty, Name, Offset, "FFloatProperty", Visibility); + => CreateCopyPropertyInner(out NewProperty, Name, Offset, "FFloatProperty", Visibility); public override bool CreateF64(out IFProperty? NewProperty, string Name, int Offset, PropertyVisibility Visibility) - => CreateCopyPropertyInner(out NewProperty, Name, Offset, "FDoubleProperty", Visibility); + => CreateCopyPropertyInner(out NewProperty, Name, Offset, "FDoubleProperty", Visibility); + + public override bool CreateStruct(out IFStructProperty? NewProperty, string Name, int Offset, + PropertyVisibility Visibility) + { + NewProperty = null; + if (!TryGetClassAndProperty("StructProperty", out var ClassReflection, out var PropertyClass) + || !Classes.GetScriptStructInfoFromType(out var ScriptStruct)) + return false; + var Alloc = Memory.Malloc(Marshal.SizeOf(), FIELD_ALIGNMENT); + NewProperty = Factory.CreateFStructProperty(Alloc); + SetPropertySuperFields(Factory.CreateFField(Alloc), Name, ClassReflection, PropertyClass); + unsafe + { + var pProperty = (FProperty*)Alloc; + pProperty->ArrayDim = 1; + pProperty->ElementSize = ScriptStruct.PropertiesSize; // FExampleStruct mExampleField; + pProperty->PropertyFlags = CreatePropertyFlags(Visibility, PropertyBuilderFlags.None); + SetPropertyFieldDefaults(pProperty, Offset); + } + LinkToPropertyList(NewProperty, ClassReflection); + unsafe { ((FStructProperty*)NewProperty.Ptr)->Struct = (UScriptStruct*)ScriptStruct.Ptr; } + return true; + } + + public override bool CreateObject(out IFObjectProperty? NewProperty, string Name, int Offset, + PropertyVisibility Visibility) + { + NewProperty = null; + if (!TryGetClassAndProperty("ObjectProperty", out var ClassReflection, out var PropertyClass) + || !Classes.GetClassInfoFromClass(out var FieldClass)) + return false; + var Alloc = Memory.Malloc(Marshal.SizeOf(), FIELD_ALIGNMENT); + NewProperty = Factory.CreateFObjectProperty(Alloc); + SetPropertySuperFields(Factory.CreateFField(Alloc), Name, ClassReflection, PropertyClass); + unsafe + { + var pProperty = (FProperty*)Alloc; + pProperty->ArrayDim = 1; + pProperty->ElementSize = Marshal.SizeOf(); + // For ObjectProperty: UExampleClass* pExampleObject; + // For ClassProperty: TSubclassOf pExampleClass; + pProperty->PropertyFlags = CreatePropertyFlags(Visibility, PropertyBuilderFlags.None); + SetPropertyFieldDefaults(pProperty, Offset); + } + LinkToPropertyList(NewProperty, ClassReflection); + unsafe { ((FObjectProperty*)NewProperty.Ptr)->PropertyClass = (UClass*)FieldClass.Ptr; } + return true; + } + + public override bool CreateName(out IFProperty? NewProperty, string Name, int Offset, PropertyVisibility Visibility) + => CreateCopyPropertyInner(out NewProperty, Name, Offset, "NameProperty", Visibility); + + public override bool CreateString(out IFProperty? NewProperty, string Name, int Offset, PropertyVisibility Visibility) + => CreateStringPropertyInner(out NewProperty, Name, Offset, "StringProperty", Visibility); + + public override bool CreateText(out IFProperty? NewProperty, string Name, int Offset, PropertyVisibility Visibility) + => CreateTextPropertyInner(out NewProperty, Name, Offset, "TextProperty", Visibility); + + public override bool CreateArray(out IFArrayProperty? NewProperty, string Name, int Offset, PropertyVisibility Visibility, + IFProperty Inner) + { + NewProperty = null; + if (!TryGetClassAndProperty("ArrayProperty", out var ClassReflection, out var PropertyClass)) + return false; + var Alloc = Memory.Malloc(Marshal.SizeOf(), FIELD_ALIGNMENT); + NewProperty = Factory.CreateFArrayProperty(Alloc); + SetPropertySuperFields(Factory.CreateFField(Alloc), Name, ClassReflection, PropertyClass); + unsafe + { + var pProperty = (FProperty*)Alloc; + pProperty->ArrayDim = 1; + pProperty->ElementSize= 0x10; // sizeof(TArray) + pProperty->PropertyFlags = CreatePropertyFlags(Visibility, PropertyBuilderFlags.NoCtor); + SetPropertyFieldDefaults(pProperty, Offset); + } + LinkToPropertyList(NewProperty, ClassReflection); + unsafe { ((FArrayProperty*)Alloc)->Inner = (FProperty*)Inner.Ptr; } + return true; + } } \ No newline at end of file diff --git a/UE.Toolkit.Reloaded/Unreal/UnrealClasses.cs b/UE.Toolkit.Reloaded/Unreal/UnrealClasses.cs index 41c59a3..a441500 100644 --- a/UE.Toolkit.Reloaded/Unreal/UnrealClasses.cs +++ b/UE.Toolkit.Reloaded/Unreal/UnrealClasses.cs @@ -42,7 +42,7 @@ private void GetPrivateStaticClassBodyUE4( _GetPrivateStaticClassBodyUE4!.Hook!.OriginalFunction(packageName, name, returnClass, registerNativeFunc, size, align, flags, castFlags, config, inClassCtor, vtableHelperCtorCaller, addRefObjects, superFn, withinFn, isDynamic, dynamicFn); AddToClassList(name, returnClass); - TryHookCDO(returnClass); + TryHookStructLink(returnClass); } // NOTE: 5.0 has it's own signature @@ -72,7 +72,7 @@ private void GetPrivateStaticClassBodyUE5( _GetPrivateStaticClassBodyUE5!.Hook!.OriginalFunction(packageName, name, returnClass, registerNativeFunc, size, align, flags, castFlags, config, inClassCtor, vtableHelperCtorCaller, staticFn, superFn, withinFn); AddToClassList(name, returnClass); - TryHookCDO(returnClass); + TryHookStructLink(returnClass); } private void TryExtendClass(nint pClassName, nint classCtor, ref uint Size) @@ -100,6 +100,7 @@ private void AddToClassList(nint name, UClass** returnClass) Classes.GetOrAdd(ClassName, Factory.CreateUClass((nint)(*returnClass))); } + private IHook FollowThunkToGetAppropriateHook (nint addr, ClassExtension.ExtensionConstructor ctorHook) { @@ -125,26 +126,61 @@ private IHook FollowThunkToGetAppropriateHo return retHook; } - private void TryHookCDO(UClass** ppClass) + private void TryHookStructLink(UClass** ppClass) { - if (_CreateDefaultObject != null) return; - Log.Debug($"0x{*(nint*)(*ppClass) + CreateDefaultObjectOffset:x}"); - _CreateDefaultObject = Hooks.CreateHook(UClass_CreateDefaultObjectImpl, - *(nint*)(*(nint*)(*ppClass) + CreateDefaultObjectOffset)).Activate(); + if (_StructLink != null) return; + var LinkAddr = *(nint*)(*(nint*)(*ppClass) + UStructLinkOffset); + var Process = System.Diagnostics.Process.GetCurrentProcess(); + var Offset = (int)(LinkAddr - Process.MainModule!.BaseAddress); + _StructLink = Hooks.CreateHook(UStruct_LinkImpl, (long)Address.GetIndirectAddressShort(Offset)).Activate(); } + + private delegate void UStruct_Link(UStruct* pThis, nint Ar, byte bRelinkExistingProperties); + private IHook? _StructLink = null; + private uint UStructLinkOffset; + private Dictionary? ObjectWithLinkTypes; - private delegate nint UClass_CreateDefaultObject(nint pClass); - private IHook? _CreateDefaultObject = null; - private uint CreateDefaultObjectOffset; - - private nint UClass_CreateDefaultObjectImpl(nint pClass) + private void UStruct_LinkImpl(UStruct* pThis, nint Ar, byte bRelinkExistingProperties) { - var ClassDefaultObject = _CreateDefaultObject!.OriginalFunction(pClass); - var Class = Factory.CreateUClass(pClass); - foreach (var Property in Class.PropertyLink) - FieldClasses.TryAdd(((FFieldClass*)Property.ClassPrivate.Ptr)->Name.ComparisonIndex.Value, - new(Property.VTable, Property.ClassPrivate)); - return ClassDefaultObject; + ObjectWithLinkTypes ??= new() + { + { "Class", new FName("Class", EFindName.FNAME_Find) }, + { "ScriptStruct", new FName("ScriptStruct", EFindName.FNAME_Find) }, + { "Function", new FName("Function", EFindName.FNAME_Find) }, + }; + _StructLink!.OriginalFunction(pThis, Ar, bRelinkExistingProperties); + var This = Factory.CreateUStruct((nint)pThis); + var TypeEnum = StructType.None; + if (This.ClassPrivate.NamePrivate.Equals(ObjectWithLinkTypes["Class"])) TypeEnum |= StructType.Class; + else if (This.ClassPrivate.NamePrivate.Equals(ObjectWithLinkTypes["ScriptStruct"])) TypeEnum |= StructType.ScriptStruct; + if (TypeEnum.HasFlag(StructType.ScriptStruct)) // Add to ScriptStruct list + ScriptStructs.TryAdd(This.NamePrivate.ToString(), Factory.CreateUScriptStruct(This.Ptr)); + if (TypeEnum != StructType.None) + { + var Class = Factory.CreateUClass(This.Ptr); + foreach (var Property in Class.PropertyLink) + { + var FieldTypeName = ((FFieldClass*)Property.ClassPrivate.Ptr)->Name; + FieldTypes.TryAdd(FieldTypeName.ComparisonIndex.Value, + new(Property.VTable, Property.ClassPrivate)); + /* + if (PropertyFactory.CheckPropertyEquality("ClassProperty", FieldTypeName.ComparisonIndex.Value)) + { + var Prop = Factory.CreateFClassProperty(Property.Ptr); + var Object = Factory.CreateUObject(This.Ptr); + Log.Debug($"{Object.NamePrivate}->{Prop.NamePrivate}: (offset 0x{Prop.Offset_Internal:x}, size 0x{Prop.ElementSize:x}): {Prop.PropertyClass.NamePrivate} (size: 0x{Prop.PropertyClass.PropertiesSize:x})"); + } + */ + /* + if (PropertyFactory.CheckPropertyEquality("ArrayProperty", FieldTypeName.ComparisonIndex.Value)) + { + var Prop = Factory.CreateFArrayProperty(Property.Ptr); + var Object = Factory.CreateUObject(This.Ptr); + Log.Debug($"{Object.NamePrivate}->{Prop.NamePrivate}: (offset 0x{Prop.Offset_Internal:x}, size 0x{Prop.ElementSize:x}): TArray<{Prop.Inner.ClassPrivate.Name} {Prop.Inner.NamePrivate}>"); + } + */ + } + } } #region IUnrealClasses implementation @@ -158,44 +194,163 @@ public bool GetClassInfoFromClass(out IUClass? Value) where TObject : u => GetClassInfoFromName(typeof(TObject).Name, out Value); public bool GetClassInfoFromName(string Name, out IUClass? Value) + => Classes.TryGetValue(Name[1..], out Value); + + public bool GetScriptStructInfoFromType(out IUScriptStruct? Value) where TObject : unmanaged + => GetScriptStructInfoFromName(typeof(TObject).Name, out Value); + + public bool GetScriptStructInfoFromName(string Name, out IUScriptStruct? Value) + => ScriptStructs.TryGetValue(Name[1..], out Value); + + public bool AddI8Property(string Name, int Offset, out IFProperty? Property) + where TObject : unmanaged + { + Property = null; + return PropertyFactory.CreateI8(out Property, Name, Offset, PropertyVisibility.Public); + } + + public bool AddI16Property(string Name, int Offset, out IFProperty? Property) where TObject : unmanaged + { + Property = null; + return PropertyFactory.CreateI16(out Property, Name, Offset, PropertyVisibility.Public); + } + + public bool AddI32Property(string Name, int Offset, out IFProperty? Property) + where TObject : unmanaged + { + Property = null; + return PropertyFactory.CreateI32(out Property, Name, Offset, PropertyVisibility.Public); + } + + public bool AddI64Property(string Name, int Offset, out IFProperty? Property) + where TObject : unmanaged + { + Property = null; + return PropertyFactory.CreateI64(out Property, Name, Offset, PropertyVisibility.Public); + } + + public bool AddU8Property(string Name, int Offset, out IFProperty? Property) + where TObject : unmanaged + { + Property = null; + return PropertyFactory.CreateU8(out Property, Name, Offset, PropertyVisibility.Public); + } + + public bool AddU16Property(string Name, int Offset, out IFProperty? Property) + where TObject : unmanaged { - Value = Classes.GetValueOrDefault(Name[1..]); - return Value != null; - } - - public void AddI8Property(string Name, int Offset) where TObject : unmanaged - => PropertyFactory.CreateI8(out _, Name, Offset, PropertyVisibility.Public); - public void AddI16Property(string Name, int Offset) where TObject : unmanaged - => PropertyFactory.CreateI16(out _, Name, Offset, PropertyVisibility.Public); - public void AddI32Property(string Name, int Offset) where TObject : unmanaged - => PropertyFactory.CreateI32(out _, Name, Offset, PropertyVisibility.Public); - public void AddI64Property(string Name, int Offset) where TObject : unmanaged - => PropertyFactory.CreateI64(out _, Name, Offset, PropertyVisibility.Public); - public void AddU8Property(string Name, int Offset) where TObject : unmanaged - => PropertyFactory.CreateU8(out _, Name, Offset, PropertyVisibility.Public); - public void AddU16Property(string Name, int Offset) where TObject : unmanaged - => PropertyFactory.CreateU16(out _, Name, Offset, PropertyVisibility.Public); - public void AddU32Property(string Name, int Offset) where TObject : unmanaged - => PropertyFactory.CreateU32(out _, Name, Offset, PropertyVisibility.Public); - public void AddU64Property(string Name, int Offset) where TObject : unmanaged - => PropertyFactory.CreateU64(out _, Name, Offset, PropertyVisibility.Public); - public void AddF32Property(string Name, int Offset) where TObject : unmanaged - => PropertyFactory.CreateF32(out _, Name, Offset, PropertyVisibility.Public); - public void AddF64Property(string Name, int Offset) where TObject : unmanaged - => PropertyFactory.CreateF64(out _, Name, Offset, PropertyVisibility.Public); + Property = null; + return PropertyFactory.CreateU16(out Property, Name, Offset, PropertyVisibility.Public); + } + + public bool AddU32Property(string Name, int Offset, out IFProperty? Property) + where TObject : unmanaged + { + Property = null; + return PropertyFactory.CreateU32(out Property, Name, Offset, PropertyVisibility.Public); + } + + public bool AddU64Property(string Name, int Offset, out IFProperty? Property) + where TObject : unmanaged + { + Property = null; + return PropertyFactory.CreateU64(out Property, Name, Offset, PropertyVisibility.Public); + } + + public bool AddF32Property(string Name, int Offset, out IFProperty? Property) + where TObject : unmanaged + { + Property = null; + return PropertyFactory.CreateF32(out Property, Name, Offset, PropertyVisibility.Public); + } + + public bool AddF64Property(string Name, int Offset, out IFProperty? Property) + where TObject : unmanaged + { + Property = null; + return PropertyFactory.CreateF64(out Property, Name, Offset, PropertyVisibility.Public); + } + + public bool AddCBoolProperty(string Name, int Offset, out IFBoolProperty? Property) + where TObject : unmanaged + { + Property = null; + return PropertyFactory.CreateCBool(out Property, Name, Offset, PropertyVisibility.Public); + } + + public bool AddBitBoolProperty(string Name, int Offset, int Bit, + out IFBoolProperty? Property) where TObject : unmanaged + { + Property = null; + return PropertyFactory.CreateBitBool(out Property, Name, Offset, Bit, PropertyVisibility.Public); + } + + + public bool AddStructProperty(string Name, int Offset, out IFStructProperty? Property) + where TObject : unmanaged + where TField : unmanaged + { + Property = null; + return PropertyFactory.CreateStruct(out Property, Name, Offset, PropertyVisibility.Public); + } + + public bool AddObjectProperty(string Name, int Offset, out IFObjectProperty? Property) + where TObject : unmanaged + where TField : unmanaged + { + Property = null; + return PropertyFactory.CreateObject(out Property, Name, Offset, PropertyVisibility.Public); + } + + public bool AddClassProperty(string Name, int Offset, out IFClassProperty? Property) + where TClass : unmanaged + where TField : unmanaged + { + Property = null; + return PropertyFactory.CreateClass(out Property, Name, Offset, PropertyVisibility.Public); + } + + public bool AddNameProperty(string Name, int Offset, out IFProperty? Property) + where TObject : unmanaged + { + Property = null; + return PropertyFactory.CreateName(out Property, Name, Offset, PropertyVisibility.Public); + } + + public bool AddStringProperty(string String, int Offset, out IFProperty? Property) + where TObject : unmanaged + { + Property = null; + return PropertyFactory.CreateString(out Property, String, Offset, PropertyVisibility.Public); + } + + public bool AddTextProperty(string Text, int Offset, out IFProperty? Property) + where TObject : unmanaged + { + Property = null; + return PropertyFactory.CreateText(out Property, Text, Offset, PropertyVisibility.Public); + } + + public bool AddArrayProperty(string Name, int Offset, IFProperty Inner, + out IFArrayProperty? Property) where TObject : unmanaged + { + Property = null; + return false; + } #endregion #region IUnrealClassesInternal implementation public bool GetFieldClassGlobal(FName Name, out FieldClassGlobal FieldClass) - => FieldClasses.TryGetValue(Name.ComparisonIndex.Value, out FieldClass); + => FieldTypes.TryGetValue(Name.ComparisonIndex.Value, out FieldClass); #endregion private ConcurrentDictionary ClassExtensions = new(); private ConcurrentDictionary Classes = new(); - private ConcurrentDictionary FieldClasses = new(); + private ConcurrentDictionary ScriptStructs = new(); + private ConcurrentDictionary FieldTypes = new(); private IUnrealFactory Factory; private IUnrealMemory Memory; @@ -216,9 +371,9 @@ public UnrealClasses(IUnrealFactory _Factory, IUnrealMemory _Memory, IReloadedHo "P3R" => new Reflection.UE4_27_2.PropertyFactory(Factory, Memory, this), _ => new Reflection.UE5_4_4.PropertyFactory(Factory, Memory, this), }; - - Project.Inis.UsingSetting(Constants.UnrealIniId, "CreateDefaultObject", nameof(UClass), - value => CreateDefaultObjectOffset = value); + + Project.Inis.UsingSetting(Constants.UnrealIniId, "Link", nameof(UStruct), + value => UStructLinkOffset = value); _GetPrivateStaticClassBodyUE4 = new(GetPrivateStaticClassBodyUE4); _GetPrivateStaticClassBodyUE5 = new(GetPrivateStaticClassBodyUE5); From d5c6c8f175f8597ae71ff3f007e952af85358f0b Mon Sep 17 00:00:00 2001 From: Rirurin Date: Fri, 14 Nov 2025 10:32:10 +0800 Subject: [PATCH 04/12] Start work to generate new UScriptStructs at runtime --- .../Unreal/Factories/BaseUnrealFactory.cs | 3 + .../Types/Unreal/Factories/IUnrealFactory.cs | 3 + .../Factories/Interfaces/IFPropertyParams.cs | 11 +++ .../Factories/Interfaces/IFStructParams.cs | 19 ++++ .../Unreal/Factories/Interfaces/IUPackage.cs | 10 ++ .../Factories/UE4_27_2/UnrealFactory.cs | 91 +++++++++++++++++++ .../Unreal/Factories/UE5_4_4/UnrealFactory.cs | 68 ++++++++++++++ .../Types/Unreal/UE4_27_2/FPropertyParams.cs | 30 ++++++ .../Types/Unreal/UE4_27_2/FStructParams.cs | 25 +++++ .../Unreal/UE5_4_4/EInternalObjectFlags.cs | 18 ++-- .../Types/Unreal/UE5_4_4/EPropertyGenFlags.cs | 38 ++++++++ .../Types/Unreal/UE5_4_4/FPropertyParams.cs | 16 ++++ .../Types/Unreal/UE5_4_4/FStructParams.cs | 25 +++++ UE.Toolkit.Interfaces/IUnrealClasses.cs | 19 ++++ .../Project/UE.Toolkit.Reloaded/p3r/scans.ini | 11 ++- .../UE.Toolkit.Reloaded/p3r/unreal.ini | 5 +- .../Project/UE.Toolkit.Reloaded/scans.ini | 11 ++- .../Project/UE.Toolkit.Reloaded/unreal.ini | 5 +- .../Reflection/PropertyFactory.cs | 8 +- UE.Toolkit.Reloaded/Reflection/TypeFactory.cs | 24 +++++ .../Reflection/UE4_27_2/PropertyFactory.cs | 16 ++-- .../Reflection/UE4_27_2/TypeFactory.cs | 26 ++++++ .../Reflection/UE5_4_4/PropertyFactory.cs | 18 ++-- .../Reflection/UE5_4_4/TypeFactory.cs | 6 ++ .../UE.Toolkit.Reloaded.csproj | 26 +++--- UE.Toolkit.Reloaded/Unreal/UnrealClasses.cs | 40 ++++++++ 26 files changed, 525 insertions(+), 47 deletions(-) create mode 100644 UE.Toolkit.Core/Types/Unreal/Factories/Interfaces/IFPropertyParams.cs create mode 100644 UE.Toolkit.Core/Types/Unreal/Factories/Interfaces/IFStructParams.cs create mode 100644 UE.Toolkit.Core/Types/Unreal/Factories/Interfaces/IUPackage.cs create mode 100644 UE.Toolkit.Core/Types/Unreal/UE4_27_2/FPropertyParams.cs create mode 100644 UE.Toolkit.Core/Types/Unreal/UE4_27_2/FStructParams.cs create mode 100644 UE.Toolkit.Core/Types/Unreal/UE5_4_4/EPropertyGenFlags.cs create mode 100644 UE.Toolkit.Core/Types/Unreal/UE5_4_4/FPropertyParams.cs create mode 100644 UE.Toolkit.Core/Types/Unreal/UE5_4_4/FStructParams.cs create mode 100644 UE.Toolkit.Reloaded/Reflection/TypeFactory.cs create mode 100644 UE.Toolkit.Reloaded/Reflection/UE4_27_2/TypeFactory.cs create mode 100644 UE.Toolkit.Reloaded/Reflection/UE5_4_4/TypeFactory.cs diff --git a/UE.Toolkit.Core/Types/Unreal/Factories/BaseUnrealFactory.cs b/UE.Toolkit.Core/Types/Unreal/Factories/BaseUnrealFactory.cs index 6df32ae..9f60a89 100644 --- a/UE.Toolkit.Core/Types/Unreal/Factories/BaseUnrealFactory.cs +++ b/UE.Toolkit.Core/Types/Unreal/Factories/BaseUnrealFactory.cs @@ -73,4 +73,7 @@ public T Cast(IPtr obj) public abstract IUUserDefinedEnum CreateUUserDefinedEnum(nint ptr); public abstract IFFieldClass CreateFFieldClass(nint ptr); public abstract IFField CreateFField(nint ptr); + // public abstract IUPackage CreateUPackage(nint ptr); + public abstract IFStructParams CreateFStructParams(nint ptr); + public abstract IFPropertyParams CreateFPropertyParams(nint ptr); } \ No newline at end of file diff --git a/UE.Toolkit.Core/Types/Unreal/Factories/IUnrealFactory.cs b/UE.Toolkit.Core/Types/Unreal/Factories/IUnrealFactory.cs index 0c71c5c..7b62e7c 100644 --- a/UE.Toolkit.Core/Types/Unreal/Factories/IUnrealFactory.cs +++ b/UE.Toolkit.Core/Types/Unreal/Factories/IUnrealFactory.cs @@ -32,4 +32,7 @@ public interface IUnrealFactory IUUserDefinedEnum CreateUUserDefinedEnum(nint ptr); IFFieldClass CreateFFieldClass(nint ptr); IFField CreateFField(nint ptr); + // IUPackage CreateUPackage(nint ptr); + IFStructParams CreateFStructParams(nint ptr); + IFPropertyParams CreateFPropertyParams(nint ptr); } \ No newline at end of file diff --git a/UE.Toolkit.Core/Types/Unreal/Factories/Interfaces/IFPropertyParams.cs b/UE.Toolkit.Core/Types/Unreal/Factories/Interfaces/IFPropertyParams.cs new file mode 100644 index 0000000..37e781f --- /dev/null +++ b/UE.Toolkit.Core/Types/Unreal/Factories/Interfaces/IFPropertyParams.cs @@ -0,0 +1,11 @@ +using UE.Toolkit.Core.Types.Unreal.UE5_4_4; + +namespace UE.Toolkit.Core.Types.Unreal.Factories.Interfaces; + +public interface IFPropertyParams : IPtr +{ + string Name { get; } + EPropertyFlags PropertyFlags { get; } + EPropertyGenFlags GenFlags { get; } + EObjectFlags ObjectFlags { get; } +} \ No newline at end of file diff --git a/UE.Toolkit.Core/Types/Unreal/Factories/Interfaces/IFStructParams.cs b/UE.Toolkit.Core/Types/Unreal/Factories/Interfaces/IFStructParams.cs new file mode 100644 index 0000000..931f8cd --- /dev/null +++ b/UE.Toolkit.Core/Types/Unreal/Factories/Interfaces/IFStructParams.cs @@ -0,0 +1,19 @@ +using UE.Toolkit.Core.Types.Unreal.UE5_4_4; + +namespace UE.Toolkit.Core.Types.Unreal.Factories.Interfaces; + +public interface IFStructParams : IPtr +{ + nint OuterFunc { get; } + nint SuperFunc { get; } + nint StructOpsFunc { get; } + string Name { get; } + ulong Size { get; } + ulong Alignment { get; } + EObjectFlags ObjectFlags { get; } + int StructFlags { get; } + int PropertyCount { get; } + IFPropertyParams? GetProperty(int Index); + IEnumerable Properties { get; } + // IEnumerable Properties { get; } +} \ No newline at end of file diff --git a/UE.Toolkit.Core/Types/Unreal/Factories/Interfaces/IUPackage.cs b/UE.Toolkit.Core/Types/Unreal/Factories/Interfaces/IUPackage.cs new file mode 100644 index 0000000..467dbf6 --- /dev/null +++ b/UE.Toolkit.Core/Types/Unreal/Factories/Interfaces/IUPackage.cs @@ -0,0 +1,10 @@ +using UE.Toolkit.Core.Types.Unreal.UE5_4_4; + +namespace UE.Toolkit.Core.Types.Unreal.Factories.Interfaces; + +public interface IUPackage : IUObject +{ + public ulong PackageId { get; } + public ulong FileSize { get; } + public FName FileName { get; } +} \ No newline at end of file diff --git a/UE.Toolkit.Core/Types/Unreal/Factories/UE4_27_2/UnrealFactory.cs b/UE.Toolkit.Core/Types/Unreal/Factories/UE4_27_2/UnrealFactory.cs index c2da32c..f14671f 100644 --- a/UE.Toolkit.Core/Types/Unreal/Factories/UE4_27_2/UnrealFactory.cs +++ b/UE.Toolkit.Core/Types/Unreal/Factories/UE4_27_2/UnrealFactory.cs @@ -1,8 +1,10 @@ using System.Collections; +using System.Runtime.InteropServices; using UE.Toolkit.Core.Common; using UE.Toolkit.Core.Types.Unreal.Factories.Interfaces; using UE.Toolkit.Core.Types.Unreal.UE4_27_2; using EPropertyFlags = UE.Toolkit.Core.Types.Unreal.UE5_4_4.EPropertyFlags; +using EPropertyGenFlags = UE.Toolkit.Core.Types.Unreal.UE5_4_4.EPropertyGenFlags; using EClassFlags = UE.Toolkit.Core.Types.Unreal.UE5_4_4.EClassFlags; using FFieldObjectUnion = UE.Toolkit.Core.Types.Unreal.UE5_4_4.FFieldObjectUnion; using FUObjectArray_Pack4 = UE.Toolkit.Core.Types.Unreal.UE5_4_4.FUObjectArray_Pack4; @@ -11,6 +13,26 @@ using EStructFlags = UE.Toolkit.Core.Types.Unreal.UE5_4_4.EStructFlags; using EInternalObjectFlags = UE.Toolkit.Core.Types.Unreal.UE5_4_4.EInternalObjectFlags; using EObjectFlags = UE.Toolkit.Core.Types.Unreal.UE5_4_4.EObjectFlags; +using FArrayProperty = UE.Toolkit.Core.Types.Unreal.UE4_27_2.FArrayProperty; +using FBoolProperty = UE.Toolkit.Core.Types.Unreal.UE4_27_2.FBoolProperty; +using FByteProperty = UE.Toolkit.Core.Types.Unreal.UE4_27_2.FByteProperty; +using FClassProperty = UE.Toolkit.Core.Types.Unreal.UE4_27_2.FClassProperty; +using FEnumProperty = UE.Toolkit.Core.Types.Unreal.UE4_27_2.FEnumProperty; +using FField = UE.Toolkit.Core.Types.Unreal.UE4_27_2.FField; +using FFieldClass = UE.Toolkit.Core.Types.Unreal.UE4_27_2.FFieldClass; +using FMapProperty = UE.Toolkit.Core.Types.Unreal.UE4_27_2.FMapProperty; +using FObjectProperty = UE.Toolkit.Core.Types.Unreal.UE4_27_2.FObjectProperty; +using FProperty = UE.Toolkit.Core.Types.Unreal.UE4_27_2.FProperty; +using FSetProperty = UE.Toolkit.Core.Types.Unreal.UE4_27_2.FSetProperty; +using FStructParams = UE.Toolkit.Core.Types.Unreal.UE4_27_2.FStructParams; +using FStructProperty = UE.Toolkit.Core.Types.Unreal.UE4_27_2.FStructProperty; +using UClass = UE.Toolkit.Core.Types.Unreal.UE4_27_2.UClass; +using UEnum = UE.Toolkit.Core.Types.Unreal.UE4_27_2.UEnum; +using UField = UE.Toolkit.Core.Types.Unreal.UE4_27_2.UField; +using UObjectBase = UE.Toolkit.Core.Types.Unreal.UE4_27_2.UObjectBase; +using UScriptStruct = UE.Toolkit.Core.Types.Unreal.UE4_27_2.UScriptStruct; +using UStruct = UE.Toolkit.Core.Types.Unreal.UE4_27_2.UStruct; +using UUserDefinedEnum = UE.Toolkit.Core.Types.Unreal.UE4_27_2.UUserDefinedEnum; // ReSharper disable InconsistentNaming @@ -93,6 +115,10 @@ public override nint SizeOf() public override IFFieldClass CreateFFieldClass(nint ptr) => new FFieldClassUE4_27_2(ptr, this); public override IFField CreateFField(nint ptr) => new FFieldUE4_27_2(ptr, this); + + public override IFStructParams CreateFStructParams(nint ptr) => new FStructParamsUE4_27_2(ptr, this); + + public override IFPropertyParams CreateFPropertyParams(nint ptr) => new FPropertyParamsUE4_27_2(ptr, this); } public unsafe class FSetPropertyUE4_27_2(nint ptr, IUnrealFactory factory) @@ -465,3 +491,68 @@ public void RemoveFromRootSet(int idx) objItem->Object->ObjectFlags &= ~EObjectFlags.RF_MarkAsRootSet; } } + +public class FPropertyParamEnumerator(IFStructParams owner) + : IEnumerator, IEnumerable +{ + private int CurrentIndex = -1; + + #region impl IEnumerator + + public bool MoveNext() => ++CurrentIndex < owner.PropertyCount; + + public void Reset() => CurrentIndex = -1; + + public IFPropertyParams Current => owner.GetProperty(CurrentIndex); + + object? IEnumerator.Current => Current; + + public void Dispose() {} + + #endregion + + #region impl IEnumerable + + public IEnumerator GetEnumerator() => this; + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + #endregion +} + +public unsafe class FStructParamsUE4_27_2(nint ptr, IUnrealFactory factory) : IFStructParams +{ + private readonly FStructParams* _self = (FStructParams*)ptr; + protected readonly IUnrealFactory _factory = factory; + + public nint Ptr => (nint)_self; + + public nint OuterFunc => _self->OuterFunc; + public nint SuperFunc => _self->SuperFunc; + public nint StructOpsFunc => _self->StructOpsFunc; + public string Name => Marshal.PtrToStringUTF8(_self->NameUTF8)!; + public ulong Size => _self->SizeOf; + public ulong Alignment => _self->AlignOf; + public EObjectFlags ObjectFlags => _self->ObjectFlags; + public int StructFlags => _self->StructFlags; + public int PropertyCount => _self->NumProperties; + public IFPropertyParams? GetProperty(int Index) + { + var Result = ((FStructParams*)Ptr)->GetProperty(Index); + return Result != null ? _factory.CreateFPropertyParams((nint)Result) : null; + } + + public IEnumerable Properties => new FPropertyParamEnumerator(this); +} + +public unsafe class FPropertyParamsUE4_27_2(nint ptr, IUnrealFactory factory) : IFPropertyParams +{ + private readonly FPropertyParamsBase* _self = (FPropertyParamsBase*)ptr; + protected readonly IUnrealFactory _factory = factory; + + public nint Ptr => (nint)_self; + public string Name => Marshal.PtrToStringUTF8(_self->NameUTF8)!; + public EPropertyFlags PropertyFlags => _self->PropertyFlags; + public EPropertyGenFlags GenFlags => _self->PropertyGenFlags; + public EObjectFlags ObjectFlags => _self->ObjectFlags; +} \ No newline at end of file diff --git a/UE.Toolkit.Core/Types/Unreal/Factories/UE5_4_4/UnrealFactory.cs b/UE.Toolkit.Core/Types/Unreal/Factories/UE5_4_4/UnrealFactory.cs index 210c2d7..a606e63 100644 --- a/UE.Toolkit.Core/Types/Unreal/Factories/UE5_4_4/UnrealFactory.cs +++ b/UE.Toolkit.Core/Types/Unreal/Factories/UE5_4_4/UnrealFactory.cs @@ -1,4 +1,5 @@ using System.Collections; +using System.Runtime.InteropServices; using UE.Toolkit.Core.Common; using UE.Toolkit.Core.Types.Unreal.Factories.Interfaces; using UE.Toolkit.Core.Types.Unreal.UE5_4_4; @@ -63,6 +64,8 @@ public override nint SizeOf() public override IUUserDefinedEnum CreateUUserDefinedEnum(nint ptr) => new UUserDefinedEnum_UE5_4_4(ptr, this); public override IFFieldClass CreateFFieldClass(nint ptr) => new FFieldClass_UE5_4_4(ptr, this); public override IFField CreateFField(nint ptr) => new FField_UE5_4_4(ptr, this); + public override IFStructParams CreateFStructParams(nint ptr) => new FStructParamsUE5_4_4(ptr, this); + public override IFPropertyParams CreateFPropertyParams(nint ptr) => new FPropertyParamsUE5_4_4(ptr, this); } public unsafe class FOptionalProperty_UE5_4_4(nint ptr, IUnrealFactory factory) @@ -468,3 +471,68 @@ public void RemoveFromRootSet(int idx) objItem->Object->ObjectFlags &= ~EObjectFlags.RF_MarkAsRootSet; } } + +public class FPropertyParamEnumerator(IFStructParams owner) + : IEnumerator, IEnumerable +{ + private int CurrentIndex = -1; + + #region impl IEnumerator + + public bool MoveNext() => ++CurrentIndex < owner.PropertyCount; + + public void Reset() => CurrentIndex = -1; + + public IFPropertyParams Current => owner.GetProperty(CurrentIndex); + + object? IEnumerator.Current => Current; + + public void Dispose() {} + + #endregion + + #region impl IEnumerable + + public IEnumerator GetEnumerator() => this; + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + #endregion +} + +public unsafe class FStructParamsUE5_4_4(nint ptr, IUnrealFactory factory) : IFStructParams +{ + private readonly FStructParams* _self = (FStructParams*)ptr; + protected readonly IUnrealFactory _factory = factory; + + public nint Ptr => (nint)_self; + + public nint OuterFunc => _self->OuterFunc; + public nint SuperFunc => _self->SuperFunc; + public nint StructOpsFunc => _self->StructOpsFunc; + public string Name => Marshal.PtrToStringUTF8(_self->NameUTF8)!; + public ulong Size => _self->SizeOf; + public ulong Alignment => _self->AlignOf; + public EObjectFlags ObjectFlags => _self->ObjectFlags; + public int StructFlags => _self->StructFlags; + public int PropertyCount => _self->NumProperties; + public IFPropertyParams? GetProperty(int Index) + { + var Result = ((FStructParams*)Ptr)->GetProperty(Index); + return Result != null ? _factory.CreateFPropertyParams((nint)Result) : null; + } + + public IEnumerable Properties => new FPropertyParamEnumerator(this); +} + +public unsafe class FPropertyParamsUE5_4_4(nint ptr, IUnrealFactory factory) : IFPropertyParams +{ + private readonly FPropertyParamsBase* _self = (FPropertyParamsBase*)ptr; + protected readonly IUnrealFactory _factory = factory; + + public nint Ptr => (nint)_self; + public string Name => Marshal.PtrToStringUTF8(_self->NameUTF8)!; + public EPropertyFlags PropertyFlags => _self->PropertyFlags; + public EPropertyGenFlags GenFlags => _self->PropertyGenFlags; + public EObjectFlags ObjectFlags => _self->ObjectFlags; +} \ No newline at end of file diff --git a/UE.Toolkit.Core/Types/Unreal/UE4_27_2/FPropertyParams.cs b/UE.Toolkit.Core/Types/Unreal/UE4_27_2/FPropertyParams.cs new file mode 100644 index 0000000..a9b5e30 --- /dev/null +++ b/UE.Toolkit.Core/Types/Unreal/UE4_27_2/FPropertyParams.cs @@ -0,0 +1,30 @@ +using System.Runtime.InteropServices; +using EObjectFlags = UE.Toolkit.Core.Types.Unreal.UE5_4_4.EObjectFlags; +using EPropertyFlags = UE.Toolkit.Core.Types.Unreal.UE5_4_4.EPropertyFlags; +using EPropertyGenFlags = UE.Toolkit.Core.Types.Unreal.UE5_4_4.EPropertyGenFlags; + +namespace UE.Toolkit.Core.Types.Unreal.UE4_27_2; + +[StructLayout(LayoutKind.Sequential, Pack = 4)] +public struct FPropertyParamsBase +{ + public nint NameUTF8; + public nint RepNotifyFuncUTF8; + public EPropertyFlags PropertyFlags; + public EPropertyGenFlags PropertyGenFlags; + public EObjectFlags ObjectFlags; + public int ArrayDim; +} + +[StructLayout(LayoutKind.Sequential, Pack = 4)] +public struct FPropertyParamsBaseWithOffset +{ + public FPropertyParamsBase Super; + public int Offset; +} + +[StructLayout(LayoutKind.Sequential, Pack = 4)] +public struct FGenericPropertyParams +{ + public FPropertyParamsBaseWithOffset Super; +} \ No newline at end of file diff --git a/UE.Toolkit.Core/Types/Unreal/UE4_27_2/FStructParams.cs b/UE.Toolkit.Core/Types/Unreal/UE4_27_2/FStructParams.cs new file mode 100644 index 0000000..ad2a306 --- /dev/null +++ b/UE.Toolkit.Core/Types/Unreal/UE4_27_2/FStructParams.cs @@ -0,0 +1,25 @@ +using System.Runtime.InteropServices; +using EObjectFlags = UE.Toolkit.Core.Types.Unreal.UE5_4_4.EObjectFlags; + +namespace UE.Toolkit.Core.Types.Unreal.UE4_27_2; + +[StructLayout(LayoutKind.Sequential)] +public unsafe struct FStructParams +{ + public nint OuterFunc; // UObject* (*OuterFunc)(); - ZConstruct for UPackage + public nint SuperFunc; // UScriptStruct* (*OuterFunc)(); + public nint StructOpsFunc; // UScriptStruct::ICppStructOps* (*StructOpsFunc)(); + public nint NameUTF8; + public ulong SizeOf; + public ulong AlignOf; + public FPropertyParamsBase** Properties; + public int NumProperties; + public EObjectFlags ObjectFlags; + public int StructFlags; + + public FPropertyParamsBase* GetProperty(int Index) + { + if (Index < 0 || Index >= NumProperties) return null; + return Properties[Index]; + } +} \ No newline at end of file diff --git a/UE.Toolkit.Core/Types/Unreal/UE5_4_4/EInternalObjectFlags.cs b/UE.Toolkit.Core/Types/Unreal/UE5_4_4/EInternalObjectFlags.cs index ff382d1..e28eb56 100644 --- a/UE.Toolkit.Core/Types/Unreal/UE5_4_4/EInternalObjectFlags.cs +++ b/UE.Toolkit.Core/Types/Unreal/UE5_4_4/EInternalObjectFlags.cs @@ -10,13 +10,13 @@ namespace UE.Toolkit.Core.Types.Unreal.UE5_4_4; public enum EInternalObjectFlags : uint { None = 0, - ReachableInCluster = 0x800000, ///< External reference to object in cluster exists - ClusterRoot = 0x1000000, ///< Root of a cluster - Native = 0x2000000, ///< Native (UClass only). - Async = 0x4000000, ///< Object exists only on a different thread than the game thread. - AsyncLoading = 0x8000000, ///< Object is being asynchronously loaded. - Unreachable = 0x10000000, ///< Object is not reachable on the object graph. - PendingKill = 0x20000000, ///< Objects that are pending destruction (invalid for gameplay but valid objects) - RootSet = 0x40000000, ///< Object will not be garbage collected, even if unreferenced. - PendingConstruction = 0x80000000, ///< Object didn't have its class constructor called yet (only the UObjectBase one to initialize its most basic members) + ReachableInCluster = 0x800000, /// External reference to object in cluster exists + ClusterRoot = 0x1000000, /// Root of a cluster + Native = 0x2000000, /// Native (UClass only). + Async = 0x4000000, /// Object exists only on a different thread than the game thread. + AsyncLoading = 0x8000000, /// Object is being asynchronously loaded. + Unreachable = 0x10000000, /// Object is not reachable on the object graph. + PendingKill = 0x20000000, /// Objects that are pending destruction (invalid for gameplay but valid objects) + RootSet = 0x40000000, /// Object will not be garbage collected, even if unreferenced. + PendingConstruction = 0x80000000, /// Object didn't have its class constructor called yet (only the UObjectBase one to initialize its most basic members) } \ No newline at end of file diff --git a/UE.Toolkit.Core/Types/Unreal/UE5_4_4/EPropertyGenFlags.cs b/UE.Toolkit.Core/Types/Unreal/UE5_4_4/EPropertyGenFlags.cs new file mode 100644 index 0000000..577f4c0 --- /dev/null +++ b/UE.Toolkit.Core/Types/Unreal/UE5_4_4/EPropertyGenFlags.cs @@ -0,0 +1,38 @@ +namespace UE.Toolkit.Core.Types.Unreal.UE5_4_4; + +public enum EPropertyGenFlags : int +{ + Byte = 0, + Int8 = 1, + Int16 = 2, + Int = 3, + Int64 = 4, + UInt16 = 5, + UInt32 = 6, + UInt64 = 7, + UnsizedInt = 8, + UnsizedUInt = 9, + Float = 10, + Double = 11, + Bool = 12, + SoftClass = 13, + WeakObject = 14, + LazyObject = 15, + SoftObject = 16, + Class = 17, + Object = 18, + Interface = 19, + Name = 20, + Str = 21, + Array = 22, + Map = 23, + Set = 24, + Struct = 25, + Delegate = 26, + InlineMulticastDelegate = 27, + SparseMulticastDelegate = 28, + Text = 29, + Enum = 30, + FieldPath = 31, + NativeBool = 32, +} \ No newline at end of file diff --git a/UE.Toolkit.Core/Types/Unreal/UE5_4_4/FPropertyParams.cs b/UE.Toolkit.Core/Types/Unreal/UE5_4_4/FPropertyParams.cs new file mode 100644 index 0000000..e27f5bc --- /dev/null +++ b/UE.Toolkit.Core/Types/Unreal/UE5_4_4/FPropertyParams.cs @@ -0,0 +1,16 @@ +using System.Runtime.InteropServices; + +namespace UE.Toolkit.Core.Types.Unreal.UE5_4_4; + +[StructLayout(LayoutKind.Sequential)] +public struct FPropertyParamsBase +{ + public nint NameUTF8; + public nint RepNotifyFuncUTF8; + public EPropertyFlags PropertyFlags; + public EPropertyGenFlags PropertyGenFlags; + public EObjectFlags ObjectFlags; + public nint SetterFunc; + public nint GetterFunc; + public ushort ArrayDim; +} \ No newline at end of file diff --git a/UE.Toolkit.Core/Types/Unreal/UE5_4_4/FStructParams.cs b/UE.Toolkit.Core/Types/Unreal/UE5_4_4/FStructParams.cs new file mode 100644 index 0000000..3a72512 --- /dev/null +++ b/UE.Toolkit.Core/Types/Unreal/UE5_4_4/FStructParams.cs @@ -0,0 +1,25 @@ +using System.Runtime.InteropServices; +using EObjectFlags = UE.Toolkit.Core.Types.Unreal.UE5_4_4.EObjectFlags; + +namespace UE.Toolkit.Core.Types.Unreal.UE5_4_4; + +[StructLayout(LayoutKind.Sequential)] +public unsafe struct FStructParams +{ + public nint OuterFunc; // UObject* (*OuterFunc)(); - ZConstruct for UPackage + public nint SuperFunc; // UScriptStruct* (*OuterFunc)(); + public nint StructOpsFunc; // UScriptStruct::ICppStructOps* (*StructOpsFunc)(); + public nint NameUTF8; + public FPropertyParamsBase** Properties; + public ushort NumProperties; + public ushort SizeOf; + public byte AlignOf; + public EObjectFlags ObjectFlags; + public int StructFlags; + + public FPropertyParamsBase* GetProperty(int Index) + { + if (Index < 0 || Index >= NumProperties) return null; + return Properties[Index]; + } +} \ No newline at end of file diff --git a/UE.Toolkit.Interfaces/IUnrealClasses.cs b/UE.Toolkit.Interfaces/IUnrealClasses.cs index 4d0fa24..d19c958 100644 --- a/UE.Toolkit.Interfaces/IUnrealClasses.cs +++ b/UE.Toolkit.Interfaces/IUnrealClasses.cs @@ -70,6 +70,7 @@ public void AddConstructor(Action> callback) /// /// Name of the new field. /// Offset of the new field. + /// Return value. /// Object type. public bool AddI8Property(string Name, int Offset, out IFProperty? Out) where TObject : unmanaged; @@ -79,6 +80,7 @@ public void AddConstructor(Action> callback) /// /// Name of the new field. /// Offset of the new field. + /// Return value. /// Object type. public bool AddI16Property(string Name, int Offset, out IFProperty? Out) where TObject : unmanaged; @@ -88,6 +90,7 @@ public void AddConstructor(Action> callback) /// /// Name of the new field. /// Offset of the new field. + /// Return value. /// Object type. public bool AddI32Property(string Name, int Offset, out IFProperty? Out) where TObject : unmanaged; @@ -106,6 +109,7 @@ public void AddConstructor(Action> callback) /// /// Name of the new field. /// Offset of the new field. + /// Return value. /// Object type. public bool AddU8Property(string Name, int Offset, out IFProperty? Out) where TObject : unmanaged; @@ -115,6 +119,7 @@ public void AddConstructor(Action> callback) /// /// Name of the new field. /// Offset of the new field. + /// Return value. /// Object type. public bool AddU16Property(string Name, int Offset, out IFProperty? Out) where TObject : unmanaged; @@ -124,6 +129,7 @@ public void AddConstructor(Action> callback) /// /// Name of the new field. /// Offset of the new field. + /// Return value. /// Object type. public bool AddU32Property(string Name, int Offset, out IFProperty? Out) where TObject : unmanaged; @@ -133,6 +139,7 @@ public void AddConstructor(Action> callback) /// /// Name of the new field. /// Offset of the new field. + /// Return value. /// Object type. public bool AddU64Property(string Name, int Offset, out IFProperty? Out) where TObject : unmanaged; @@ -142,6 +149,7 @@ public void AddConstructor(Action> callback) /// /// Name of the new field. /// Offset of the new field. + /// Return value. /// Object type. public bool AddF32Property(string Name, int Offset, out IFProperty? Out) where TObject : unmanaged; @@ -151,6 +159,7 @@ public void AddConstructor(Action> callback) /// /// Name of the new field. /// Offset of the new field. + /// Return value. /// Object type. public bool AddF64Property(string Name, int Offset, out IFProperty? Out) where TObject : unmanaged; @@ -160,6 +169,7 @@ public void AddConstructor(Action> callback) /// /// Name of the new field. /// Offset of the new field. + /// Return value. /// Object type. public bool AddCBoolProperty(string Name, int Offset, out IFBoolProperty? Out) where TObject : unmanaged; @@ -170,6 +180,7 @@ public void AddConstructor(Action> callback) /// Name of the new field. /// Byte offset of the new field. /// Bit offset of the new field. + /// Return value. /// Object type. public bool AddBitBoolProperty(string Name, int Offset, int Bit, out IFBoolProperty? Out) where TObject : unmanaged; @@ -179,6 +190,7 @@ public void AddConstructor(Action> callback) /// /// Name of the new field. /// Offset of the new field. + /// Return value. /// Object type. /// Field type. public bool AddStructProperty(string Name, int Offset, out IFStructProperty? Out) @@ -191,6 +203,7 @@ public bool AddStructProperty(string Name, int Offset, out IFSt /// /// Name of the new field. /// Offset of the new field. + /// Return value. /// Object type. /// Field type. public bool AddObjectProperty(string Name, int Offset, out IFObjectProperty? Out) @@ -203,6 +216,7 @@ public bool AddObjectProperty(string Name, int Offset, out IFOb /// /// Name of the new field. /// Offset of the new field. + /// Return value. /// Object type. /// Field type. public bool AddClassProperty(string Name, int Offset, out IFClassProperty? Out) @@ -215,6 +229,7 @@ public bool AddClassProperty(string Name, int Offset, out IFCla /// /// Name of the new field. /// Offset of the new field. + /// Return value. /// Object type. public bool AddNameProperty(string Name, int Offset, out IFProperty? Out) where TObject : unmanaged; @@ -224,6 +239,7 @@ public bool AddClassProperty(string Name, int Offset, out IFCla /// /// Name of the new field. /// Offset of the new field. + /// Return value. /// Object type. public bool AddStringProperty(string Name, int Offset, out IFProperty? Out) where TObject : unmanaged; @@ -233,6 +249,7 @@ public bool AddClassProperty(string Name, int Offset, out IFCla /// /// Name of the new field. /// Offset of the new field. + /// Return value. /// Object type. public bool AddTextProperty(string Name, int Offset, out IFProperty? Out) where TObject : unmanaged; @@ -242,6 +259,8 @@ public bool AddClassProperty(string Name, int Offset, out IFCla /// /// Name of the new field. /// Offset of the new field. + /// The inner property used for each entry in the array. + /// Return value. /// Object type. public bool AddArrayProperty(string Name, int Offset, IFProperty Inner, out IFArrayProperty? Property) where TObject : unmanaged; diff --git a/UE.Toolkit.Reloaded/Project/UE.Toolkit.Reloaded/p3r/scans.ini b/UE.Toolkit.Reloaded/Project/UE.Toolkit.Reloaded/p3r/scans.ini index 1ccb638..1640018 100644 --- a/UE.Toolkit.Reloaded/Project/UE.Toolkit.Reloaded/p3r/scans.ini +++ b/UE.Toolkit.Reloaded/Project/UE.Toolkit.Reloaded/p3r/scans.ini @@ -38,4 +38,13 @@ GetPrivateStaticClassBody_UE4=48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 57 41 ;GetPrivateStaticClassBody_RESULT= GetPrivateStaticClassBody_UE5=DISABLED -;GetPrivateStaticClassBody_RESULT= \ No newline at end of file +;GetPrivateStaticClassBody_RESULT= + +StaticConstructObject_Internal=48 89 5C 24 ?? 48 89 74 24 ?? 55 57 41 54 41 56 41 57 48 8D AC 24 ?? ?? ?? ?? 48 81 EC B0 01 00 00 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 85 ?? ?? ?? ?? 48 8B 39 +;StaticConstructObject_Internal_RESULT= + +GetStaticStruct=48 89 5C 24 ?? 48 89 6C 24 ?? 56 57 41 56 48 83 EC 40 4D 8B F0 48 8B DA 48 8B F1 48 8B C2 66 90 48 83 78 ?? 00 48 8D 78 ?? 74 ?? 33 C9 F7 40 ?? 00 00 00 10 74 ?? 48 8B C8 E8 ?? ?? ?? ?? 48 8B C8 48 8B C1 48 85 C0 75 ?? 48 8B 07 EB ?? 48 8B 48 ?? 48 8D 54 24 ?? 48 89 4C 24 ?? 48 8D 4C 24 ?? E8 ?? ?? ?? ?? 83 7C 24 ?? 00 48 8D 2D ?? ?? ?? ?? 41 B9 01 00 00 00 C6 44 24 ?? 00 48 8B CD 48 C7 44 24 ?? 00 00 00 00 48 0F 45 4C 24 ?? 49 8B D6 45 8D 41 ?? E8 ?? ?? ?? ?? 48 8B 4C 24 ?? 48 85 C9 74 ?? E8 ?? ?? ?? ?? FF D6 48 8B F0 90 48 83 7B ?? 00 48 8D 7B ?? 74 ?? 33 C0 F7 43 ?? 00 00 00 10 74 ?? 48 8B CB E8 ?? ?? ?? ?? 48 8B D8 48 85 DB 75 ?? 48 8B 1F EB ?? 48 8B 43 ?? 48 8D 54 24 ?? 48 8D 4C 24 ?? 48 89 44 24 ?? E8 ?? ?? ?? ?? 83 7C 24 ?? 00 41 B9 02 00 00 00 C6 44 24 ?? 00 45 8B C1 +;GetStaticStruct_RESULT= + +ConstructUScriptStruct=40 53 56 57 41 54 41 55 41 56 48 81 EC 08 03 00 00 +;ConstructUScriptStruct_RESULT= \ No newline at end of file diff --git a/UE.Toolkit.Reloaded/Project/UE.Toolkit.Reloaded/p3r/unreal.ini b/UE.Toolkit.Reloaded/Project/UE.Toolkit.Reloaded/p3r/unreal.ini index 542b7b4..6d0374c 100644 --- a/UE.Toolkit.Reloaded/Project/UE.Toolkit.Reloaded/p3r/unreal.ini +++ b/UE.Toolkit.Reloaded/Project/UE.Toolkit.Reloaded/p3r/unreal.ini @@ -6,4 +6,7 @@ Realloc=0x20 QuantizeSize=0x38 [UStruct] -Link=0x288 \ No newline at end of file +Link=0x288 + +[UPackage] +GamePackage=/Script/xrd777 \ No newline at end of file diff --git a/UE.Toolkit.Reloaded/Project/UE.Toolkit.Reloaded/scans.ini b/UE.Toolkit.Reloaded/Project/UE.Toolkit.Reloaded/scans.ini index 0b4a9e9..1e9d534 100644 --- a/UE.Toolkit.Reloaded/Project/UE.Toolkit.Reloaded/scans.ini +++ b/UE.Toolkit.Reloaded/Project/UE.Toolkit.Reloaded/scans.ini @@ -40,4 +40,13 @@ GetPrivateStaticClassBody_UE4=DISABLED ;GetPrivateStaticClassBody_RESULT= GetPrivateStaticClassBody_UE5=48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 57 41 54 41 55 41 56 41 57 48 83 EC 60 45 33 ED -;GetPrivateStaticClassBody_RESULT= \ No newline at end of file +;GetPrivateStaticClassBody_RESULT= + +StaticConstructObject_Internal=4C 8B DC 49 89 5B ?? 49 89 73 ?? 55 57 41 54 41 56 41 57 49 8D AB ?? ?? ?? ?? +;StaticConstructObject_Internal_RESULT= + +GetStaticStruct=48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 57 48 83 EC 50 49 8B E8 48 8B DA FF D1 48 8B F0 90 48 83 7B ?? 00 48 8D 7B ?? 74 ?? 8B 43 ?? 33 C9 0F BA E0 1C 73 ?? 48 8B CB E8 ?? ?? ?? ?? 48 8B C8 48 8B D9 48 85 DB 75 ?? 48 8B 1F EB ?? 48 8B 43 ?? 48 8D 54 24 ?? 48 8D 4C 24 ?? 48 89 44 24 ?? E8 ?? ?? ?? ?? 83 7C 24 ?? 00 48 8D 0D ?? ?? ?? ?? 41 B9 02 00 00 00 48 89 74 24 ?? 48 0F 45 4C 24 ?? 45 8B C1 +;GetStaticStruct_RESULT= + +ConstructUScriptStruct=40 53 56 57 41 54 41 55 41 56 48 81 EC 98 03 00 00 +;ConstructUScriptStruct \ No newline at end of file diff --git a/UE.Toolkit.Reloaded/Project/UE.Toolkit.Reloaded/unreal.ini b/UE.Toolkit.Reloaded/Project/UE.Toolkit.Reloaded/unreal.ini index a8282f9..59190e9 100644 --- a/UE.Toolkit.Reloaded/Project/UE.Toolkit.Reloaded/unreal.ini +++ b/UE.Toolkit.Reloaded/Project/UE.Toolkit.Reloaded/unreal.ini @@ -8,4 +8,7 @@ Realloc=0x38 QuantizeSize=0x50 [UStruct] -Link=0x2d0 \ No newline at end of file +Link=0x2d0 + +[UPackage] +GamePackage=/Script/SandFall \ No newline at end of file diff --git a/UE.Toolkit.Reloaded/Reflection/PropertyFactory.cs b/UE.Toolkit.Reloaded/Reflection/PropertyFactory.cs index 3573abc..154a040 100644 --- a/UE.Toolkit.Reloaded/Reflection/PropertyFactory.cs +++ b/UE.Toolkit.Reloaded/Reflection/PropertyFactory.cs @@ -97,10 +97,10 @@ private bool CreatePropertyInner(out IFProperty? NewP if (!TryGetClassAndProperty(PropertyName, out var ClassReflection, out var PropertyClass)) return false; var Alloc = Memory.Malloc(Marshal.SizeOf(), FIELD_ALIGNMENT); - SetPropertySuperFields(Factory.CreateFField(Alloc), Name, ClassReflection, PropertyClass); + SetPropertySuperFields(Factory.CreateFField(Alloc), Name, ClassReflection!, PropertyClass!); NewProperty = Factory.CreateFProperty(Alloc); Callback(NewProperty, Offset, Visibility); - LinkToPropertyList(NewProperty, ClassReflection); + LinkToPropertyList(NewProperty, ClassReflection!); return true; } @@ -137,7 +137,7 @@ protected bool CreateBoolPropertyInner(out IFBoolProperty? NewProperty, if (!CreateCopyPropertyInner(out var BaseProperty, Name, Offset, "BoolProperty", Visibility)) return false; - NewProperty = Factory.CreateFBoolProperty(BaseProperty.Ptr); + NewProperty = Factory.CreateFBoolProperty(BaseProperty!.Ptr); SetBoolPropertyFields(NewProperty, Mask); return true; } @@ -215,7 +215,7 @@ public bool CreateClass(out IFClassProperty? NewProperty, NewProperty = null; if (!CreateObject(out var ObjectProperty, Name, Offset, Visibility)) return false; - NewProperty = Factory.CreateFClassProperty(ObjectProperty.Ptr); + NewProperty = Factory.CreateFClassProperty(ObjectProperty!.Ptr); return true; } diff --git a/UE.Toolkit.Reloaded/Reflection/TypeFactory.cs b/UE.Toolkit.Reloaded/Reflection/TypeFactory.cs new file mode 100644 index 0000000..83558d0 --- /dev/null +++ b/UE.Toolkit.Reloaded/Reflection/TypeFactory.cs @@ -0,0 +1,24 @@ +using UE.Toolkit.Core.Types.Unreal.Factories; +using UE.Toolkit.Interfaces; + +namespace UE.Toolkit.Reloaded.Reflection; + +public abstract class BaseTypeFactory(IUnrealFactory factory, IUnrealMemory memory, IUnrealClasses classes) +{ + + public abstract void CreateScriptStruct(string Name, int Size); + + #region Dependencies + + protected readonly IUnrealFactory Factory = factory; + protected readonly IUnrealMemory Memory = memory; + protected readonly IUnrealClasses Classes = classes; + + #endregion + + #region Constants + + protected const int TYPE_ALIGNMENT = 0x10; + + #endregion +} \ No newline at end of file diff --git a/UE.Toolkit.Reloaded/Reflection/UE4_27_2/PropertyFactory.cs b/UE.Toolkit.Reloaded/Reflection/UE4_27_2/PropertyFactory.cs index bdb5bd7..34dccfd 100644 --- a/UE.Toolkit.Reloaded/Reflection/UE4_27_2/PropertyFactory.cs +++ b/UE.Toolkit.Reloaded/Reflection/UE4_27_2/PropertyFactory.cs @@ -30,7 +30,7 @@ protected override EPropertyFlags CreatePropertyFlags(PropertyVisibility Visibil { PropertyVisibility.Public => EPropertyFlags.CPF_NativeAccessSpecifierPublic, PropertyVisibility.Protected => EPropertyFlags.CPF_NativeAccessSpecifierProtected, - PropertyVisibility.Private => EPropertyFlags.CPF_NativeAccessSpecifierPrivate, + _ => EPropertyFlags.CPF_NativeAccessSpecifierPrivate, }; return Flags; } @@ -150,16 +150,16 @@ public override bool CreateStruct(out IFStructProperty? NewPrope return false; var Alloc = Memory.Malloc(Marshal.SizeOf(), FIELD_ALIGNMENT); NewProperty = Factory.CreateFStructProperty(Alloc); - SetPropertySuperFields(Factory.CreateFField(Alloc), Name, ClassReflection, PropertyClass); + SetPropertySuperFields(Factory.CreateFField(Alloc), Name, ClassReflection!, PropertyClass!); unsafe { var pProperty = (FProperty*)Alloc; pProperty->array_dim = 1; - pProperty->element_size = ScriptStruct.PropertiesSize; // FExampleStruct mExampleField; + pProperty->element_size = ScriptStruct!.PropertiesSize; // FExampleStruct mExampleField; pProperty->property_flags = CreatePropertyFlags(Visibility, PropertyBuilderFlags.None); SetPropertyFieldDefaults(pProperty, Offset); } - LinkToPropertyList(NewProperty, ClassReflection); + LinkToPropertyList(NewProperty, ClassReflection!); unsafe { ((FStructProperty*)NewProperty.Ptr)->struct_data = (UScriptStruct*)ScriptStruct.Ptr; } return true; } @@ -173,7 +173,7 @@ public override bool CreateObject(out IFObjectProperty? NewPrope return false; var Alloc = Memory.Malloc(Marshal.SizeOf(), FIELD_ALIGNMENT); NewProperty = Factory.CreateFObjectProperty(Alloc); - SetPropertySuperFields(Factory.CreateFField(Alloc), Name, ClassReflection, PropertyClass); + SetPropertySuperFields(Factory.CreateFField(Alloc), Name, ClassReflection!, PropertyClass!); unsafe { var pProperty = (FProperty*)Alloc; @@ -184,8 +184,8 @@ public override bool CreateObject(out IFObjectProperty? NewPrope pProperty->property_flags = CreatePropertyFlags(Visibility, PropertyBuilderFlags.None); SetPropertyFieldDefaults(pProperty, Offset); } - LinkToPropertyList(NewProperty, ClassReflection); - unsafe { ((FObjectProperty*)NewProperty.Ptr)->prop_class = (UClass*)FieldClass.Ptr; } + LinkToPropertyList(NewProperty, ClassReflection!); + unsafe { ((FObjectProperty*)NewProperty.Ptr)->prop_class = (UClass*)FieldClass!.Ptr; } return true; } @@ -206,7 +206,7 @@ public override bool CreateArray(out IFArrayProperty? NewProperty, strin return false; var Alloc = Memory.Malloc(Marshal.SizeOf(), FIELD_ALIGNMENT); NewProperty = Factory.CreateFArrayProperty(Alloc); - SetPropertySuperFields(Factory.CreateFField(Alloc), Name, ClassReflection, PropertyClass); + SetPropertySuperFields(Factory.CreateFField(Alloc), Name, ClassReflection!, PropertyClass!); unsafe { var pProperty = (FProperty*)Alloc; diff --git a/UE.Toolkit.Reloaded/Reflection/UE4_27_2/TypeFactory.cs b/UE.Toolkit.Reloaded/Reflection/UE4_27_2/TypeFactory.cs new file mode 100644 index 0000000..dd7489e --- /dev/null +++ b/UE.Toolkit.Reloaded/Reflection/UE4_27_2/TypeFactory.cs @@ -0,0 +1,26 @@ +using System.Runtime.InteropServices; +using UE.Toolkit.Core.Types.Unreal.Factories; +using UE.Toolkit.Core.Types.Unreal.Factories.Interfaces; +using UE.Toolkit.Core.Types.Unreal.UE4_27_2; +using UE.Toolkit.Interfaces; + +namespace UE.Toolkit.Reloaded.Reflection.UE4_27_2; + +public class TypeFactory(IUnrealFactory factory, IUnrealMemory memory, IUnrealClasses classes) + : BaseTypeFactory(factory, memory, classes) +{ + + private unsafe void SetUObjectFields(IUObject Object, string Name) + { + // var pObject = (UObjectBase*)Object.Ptr; + // pObject->_vtable = 0; + // pObject->ObjectFlags = EObjectFlags + // pObject->InternalIndex = + } + + public override void CreateScriptStruct(string Name, int Size) + { + var Alloc = Memory.Malloc(Marshal.SizeOf(), TYPE_ALIGNMENT); + SetUObjectFields(Factory.CreateUObject(Alloc), Name); + } +} \ No newline at end of file diff --git a/UE.Toolkit.Reloaded/Reflection/UE5_4_4/PropertyFactory.cs b/UE.Toolkit.Reloaded/Reflection/UE5_4_4/PropertyFactory.cs index 6e1474e..1253fc8 100644 --- a/UE.Toolkit.Reloaded/Reflection/UE5_4_4/PropertyFactory.cs +++ b/UE.Toolkit.Reloaded/Reflection/UE5_4_4/PropertyFactory.cs @@ -21,7 +21,7 @@ protected override EPropertyFlags CreatePropertyFlags(PropertyVisibility Visibil { PropertyVisibility.Public => EPropertyFlags.CPF_NativeAccessSpecifierPublic, PropertyVisibility.Protected => EPropertyFlags.CPF_NativeAccessSpecifierProtected, - PropertyVisibility.Private => EPropertyFlags.CPF_NativeAccessSpecifierPrivate, + _ => EPropertyFlags.CPF_NativeAccessSpecifierPrivate, }; return Flags; } @@ -139,16 +139,16 @@ public override bool CreateStruct(out IFStructProperty? NewPrope return false; var Alloc = Memory.Malloc(Marshal.SizeOf(), FIELD_ALIGNMENT); NewProperty = Factory.CreateFStructProperty(Alloc); - SetPropertySuperFields(Factory.CreateFField(Alloc), Name, ClassReflection, PropertyClass); + SetPropertySuperFields(Factory.CreateFField(Alloc), Name, ClassReflection!, PropertyClass!); unsafe { var pProperty = (FProperty*)Alloc; pProperty->ArrayDim = 1; - pProperty->ElementSize = ScriptStruct.PropertiesSize; // FExampleStruct mExampleField; + pProperty->ElementSize = ScriptStruct!.PropertiesSize; // FExampleStruct mExampleField; pProperty->PropertyFlags = CreatePropertyFlags(Visibility, PropertyBuilderFlags.None); SetPropertyFieldDefaults(pProperty, Offset); } - LinkToPropertyList(NewProperty, ClassReflection); + LinkToPropertyList(NewProperty, ClassReflection!); unsafe { ((FStructProperty*)NewProperty.Ptr)->Struct = (UScriptStruct*)ScriptStruct.Ptr; } return true; } @@ -162,7 +162,7 @@ public override bool CreateObject(out IFObjectProperty? NewPrope return false; var Alloc = Memory.Malloc(Marshal.SizeOf(), FIELD_ALIGNMENT); NewProperty = Factory.CreateFObjectProperty(Alloc); - SetPropertySuperFields(Factory.CreateFField(Alloc), Name, ClassReflection, PropertyClass); + SetPropertySuperFields(Factory.CreateFField(Alloc), Name, ClassReflection!, PropertyClass!); unsafe { var pProperty = (FProperty*)Alloc; @@ -173,8 +173,8 @@ public override bool CreateObject(out IFObjectProperty? NewPrope pProperty->PropertyFlags = CreatePropertyFlags(Visibility, PropertyBuilderFlags.None); SetPropertyFieldDefaults(pProperty, Offset); } - LinkToPropertyList(NewProperty, ClassReflection); - unsafe { ((FObjectProperty*)NewProperty.Ptr)->PropertyClass = (UClass*)FieldClass.Ptr; } + LinkToPropertyList(NewProperty, ClassReflection!); + unsafe { ((FObjectProperty*)NewProperty.Ptr)->PropertyClass = (UClass*)FieldClass!.Ptr; } return true; } @@ -195,7 +195,7 @@ public override bool CreateArray(out IFArrayProperty? NewProperty, strin return false; var Alloc = Memory.Malloc(Marshal.SizeOf(), FIELD_ALIGNMENT); NewProperty = Factory.CreateFArrayProperty(Alloc); - SetPropertySuperFields(Factory.CreateFField(Alloc), Name, ClassReflection, PropertyClass); + SetPropertySuperFields(Factory.CreateFField(Alloc), Name, ClassReflection!, PropertyClass!); unsafe { var pProperty = (FProperty*)Alloc; @@ -204,7 +204,7 @@ public override bool CreateArray(out IFArrayProperty? NewProperty, strin pProperty->PropertyFlags = CreatePropertyFlags(Visibility, PropertyBuilderFlags.NoCtor); SetPropertyFieldDefaults(pProperty, Offset); } - LinkToPropertyList(NewProperty, ClassReflection); + LinkToPropertyList(NewProperty, ClassReflection!); unsafe { ((FArrayProperty*)Alloc)->Inner = (FProperty*)Inner.Ptr; } return true; } diff --git a/UE.Toolkit.Reloaded/Reflection/UE5_4_4/TypeFactory.cs b/UE.Toolkit.Reloaded/Reflection/UE5_4_4/TypeFactory.cs new file mode 100644 index 0000000..b78fafd --- /dev/null +++ b/UE.Toolkit.Reloaded/Reflection/UE5_4_4/TypeFactory.cs @@ -0,0 +1,6 @@ +namespace UE.Toolkit.Reloaded.Reflection.UE5_4_4; + +public class TypeFactory +{ + +} \ No newline at end of file diff --git a/UE.Toolkit.Reloaded/UE.Toolkit.Reloaded.csproj b/UE.Toolkit.Reloaded/UE.Toolkit.Reloaded.csproj index 7956969..968c12d 100644 --- a/UE.Toolkit.Reloaded/UE.Toolkit.Reloaded.csproj +++ b/UE.Toolkit.Reloaded/UE.Toolkit.Reloaded.csproj @@ -17,20 +17,20 @@ - + - - + + - - - - - + + + + + @@ -46,11 +46,11 @@ - - - - - + + + + + diff --git a/UE.Toolkit.Reloaded/Unreal/UnrealClasses.cs b/UE.Toolkit.Reloaded/Unreal/UnrealClasses.cs index a441500..fae00bd 100644 --- a/UE.Toolkit.Reloaded/Unreal/UnrealClasses.cs +++ b/UE.Toolkit.Reloaded/Unreal/UnrealClasses.cs @@ -1,6 +1,9 @@ using System.Collections.Concurrent; +using System.Reflection.Emit; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Reloaded.Hooks.Definitions; +using UE.Toolkit.Core.Types; using UE.Toolkit.Core.Types.Interfaces; using UE.Toolkit.Core.Types.Unreal.Factories; using UE.Toolkit.Core.Types.Unreal.Factories.Interfaces; @@ -182,6 +185,41 @@ private void UStruct_LinkImpl(UStruct* pThis, nint Ar, byte bRelinkExistingPrope } } } + + private delegate nint GetStaticStruct(nint pInRegister, nint pStructOuter, nint pStructName, nint Size, int Crc); + private SHFunction _GetStaticStruct; + private Dictionary PackageNameToUObject = new(); + private static Func? GetStaticStructCurrentCallback = null; + + private nint GetStaticStructImpl(nint pInRegister, nint pStructOuter, nint pStructName, nint Size, int Crc) + { + // UPackage::GamePackage + var StructOuter = Factory.CreateUObject(pStructOuter); + // var StructName = Marshal.PtrToStringUni(pStructName); + var PackageName = StructOuter.NamePrivate.ToString(); + PackageNameToUObject.TryAdd(PackageName, StructOuter); + // Log.Debug($"GetStaticStruct({StructName}): (outer: {StructOuter.NamePrivate}, size: 0x{Size:x}, crc: 0x{Crc:x})"); + return _GetStaticStruct!.Hook!.OriginalFunction(pInRegister, pStructOuter, pStructName, Size, Crc); + } + + [UnmanagedCallersOnly(CallConvs = [typeof(CallConvStdcall)])] + private static nint GetStaticStructCallback() => GetStaticStructCurrentCallback!(); + + private delegate void ConstructUScriptStruct(nint ppScriptStruct, nint pStructParams); + private SHFunction _ConstructUScriptStruct; + + private void ConstructUScriptStructImpl(nint ppScriptStruct, nint pStructParams) + { + /* + var StructParams = Factory.CreateFStructParams(pStructParams); + Log.Debug($"FStructParams {StructParams.Name} @ 0x{StructParams.Ptr:x}: flags: {StructParams.ObjectFlags}, {StructParams.StructFlags} (size: 0x{StructParams.Size:x}, align: 0x{StructParams.Alignment:x})"); + foreach (var Property in StructParams.Properties) + { + Log.Debug($"\tFPropertyParams {Property.Name} @ 0x{Property.Ptr:x}: {Property.GenFlags}, {Property.PropertyFlags}, {Property.ObjectFlags}"); + } + */ + _ConstructUScriptStruct!.Hook!.OriginalFunction(ppScriptStruct, pStructParams); + } #region IUnrealClasses implementation @@ -377,6 +415,8 @@ public UnrealClasses(IUnrealFactory _Factory, IUnrealMemory _Memory, IReloadedHo _GetPrivateStaticClassBodyUE4 = new(GetPrivateStaticClassBodyUE4); _GetPrivateStaticClassBodyUE5 = new(GetPrivateStaticClassBodyUE5); + _GetStaticStruct = new(GetStaticStructImpl); + _ConstructUScriptStruct = new(ConstructUScriptStructImpl); } } From ebe7c9ffc35a2b8cd671f5c99be64d4c41239ec4 Mon Sep 17 00:00:00 2001 From: Rirurin Date: Sun, 16 Nov 2025 12:14:22 +0800 Subject: [PATCH 05/12] Add ProcessEvent, completed UScriptStruct generation (P3R) --- .../IContainerAllocationPolicies.cs | 8 +- .../Types/Interfaces/IInvocationParameters.cs | 25 ++ .../Unreal/Factories/BaseUnrealFactory.cs | 15 +- .../Types/Unreal/Factories/IUnrealFactory.cs | 10 +- .../Factories/Interfaces/IFPropertyParams.cs | 6 + .../Factories/Interfaces/IFStructParams.cs | 2 +- .../Unreal/Factories/Interfaces/IUClass.cs | 2 + .../Unreal/Factories/Interfaces/IUFunction.cs | 13 + .../Factories/UE4_27_2/UnrealFactory.cs | 50 ++- .../Unreal/Factories/UE5_4_4/UnrealFactory.cs | 40 +- .../Types/Unreal/UE4_27_2/FPropertyParams.cs | 14 +- .../Types/Unreal/UE4_27_2/FStructParams.cs | 3 +- .../Types/Unreal/UE4_27_2/Unreal.cs | 5 + .../Types/Unreal/UE5_4_4/EFunctionFlags.cs | 41 ++ .../Types/Unreal/UE5_4_4/FPropertyParams.cs | 21 + .../Types/Unreal/UE5_4_4/FStructParams.cs | 3 +- .../Types/Unreal/UE5_4_4/UClass.cs | 3 + .../Types/Unreal/UE5_4_4/UFunction.cs | 43 +-- .../Types/Unreal/UE5_4_4/UScriptStruct.cs | 8 + UE.Toolkit.Interfaces/IUnrealClasses.cs | 145 ++++++- UE.Toolkit.Interfaces/IUnrealMethods.cs | 133 +++++++ .../Common/GameConfigs/Games/UE4_27_2_P3R.cs | 4 + .../GameConfigs/Games/UE5_4_4_ClairObscur.cs | 3 + .../Common/GameConfigs/IGameConfig.cs | 2 + UE.Toolkit.Reloaded/Config.cs | 4 + UE.Toolkit.Reloaded/Mod.cs | 11 +- .../UE.Toolkit.Reloaded/p3r/unreal.ini | 3 + .../Project/UE.Toolkit.Reloaded/unreal.ini | 3 + .../Reflection/IPropertyFlagsBuilder.cs | 9 + .../Reflection/PropertyFactory.cs | 7 +- UE.Toolkit.Reloaded/Reflection/TypeFactory.cs | 22 +- .../Reflection/UE4_27_2/PropertyFactory.cs | 29 +- .../UE4_27_2/PropertyFlagsBuilder.cs | 23 ++ .../Reflection/UE4_27_2/TypeFactory.cs | 166 +++++++- .../Reflection/UE5_4_4/PropertyFactory.cs | 28 +- .../UE5_4_4/PropertyFlagsBuilder.cs | 23 ++ .../Reflection/UE5_4_4/TypeFactory.cs | 75 +++- UE.Toolkit.Reloaded/Unreal/UnrealClasses.cs | 84 +++- UE.Toolkit.Reloaded/Unreal/UnrealMethods.cs | 360 ++++++++++++++++++ 39 files changed, 1283 insertions(+), 163 deletions(-) create mode 100644 UE.Toolkit.Core/Types/Interfaces/IInvocationParameters.cs create mode 100644 UE.Toolkit.Core/Types/Unreal/Factories/Interfaces/IUFunction.cs create mode 100644 UE.Toolkit.Core/Types/Unreal/UE5_4_4/EFunctionFlags.cs create mode 100644 UE.Toolkit.Interfaces/IUnrealMethods.cs create mode 100644 UE.Toolkit.Reloaded/Reflection/IPropertyFlagsBuilder.cs create mode 100644 UE.Toolkit.Reloaded/Reflection/UE4_27_2/PropertyFlagsBuilder.cs create mode 100644 UE.Toolkit.Reloaded/Reflection/UE5_4_4/PropertyFlagsBuilder.cs create mode 100644 UE.Toolkit.Reloaded/Unreal/UnrealMethods.cs diff --git a/UE.Toolkit.Core/Types/Interfaces/IContainerAllocationPolicies.cs b/UE.Toolkit.Core/Types/Interfaces/IContainerAllocationPolicies.cs index 1f125ac..055aaef 100644 --- a/UE.Toolkit.Core/Types/Interfaces/IContainerAllocationPolicies.cs +++ b/UE.Toolkit.Core/Types/Interfaces/IContainerAllocationPolicies.cs @@ -46,7 +46,7 @@ public class HeapAllocatorInt32 : HeapAllocator public unsafe abstract class InlineAllocator : ContainerAllocationPolicy where TType : unmanaged where TSize : INumber { - protected TType* Heap; + protected TType* Heap = null; protected TSize NumInlineElements; public bool HasAllocation() => Heap != null; @@ -62,12 +62,8 @@ public class InlineAllocatorInt32 : InlineAllocator public override nint GetAllocatedSize(int NumAllocatedElements, nint NumBytesPerElement) { if (NumAllocatedElements > NumInlineElements) - { return (NumAllocatedElements - NumInlineElements) * NumBytesPerElement; - } else - { - return 0; - } + return 0; } } diff --git a/UE.Toolkit.Core/Types/Interfaces/IInvocationParameters.cs b/UE.Toolkit.Core/Types/Interfaces/IInvocationParameters.cs new file mode 100644 index 0000000..c479584 --- /dev/null +++ b/UE.Toolkit.Core/Types/Interfaces/IInvocationParameters.cs @@ -0,0 +1,25 @@ +using System.Runtime.InteropServices; + +namespace UE.Toolkit.Core.Types.Interfaces; + +public interface IInvocationParameter +{ + object? Value { get; } + + void ToAlloc(nint pAlloc); // for in parameters + void FromAlloc(nint pAlloc); // for out parameters/return type + + string ExpectedPropertyClass { get; } + int ExpectedSize { get; } +} + +public abstract class InvocationParameterCopyable(T value) : IInvocationParameter where T: unmanaged +{ + protected T InnerValue { get; set; } = value; + public object? Value => InnerValue; + + public unsafe void ToAlloc(nint pAlloc) => *(T*)pAlloc = InnerValue; + public unsafe void FromAlloc(nint pAlloc) => InnerValue = *(T*)pAlloc; + public int ExpectedSize => Marshal.SizeOf(); + public abstract string ExpectedPropertyClass { get; } +} \ No newline at end of file diff --git a/UE.Toolkit.Core/Types/Unreal/Factories/BaseUnrealFactory.cs b/UE.Toolkit.Core/Types/Unreal/Factories/BaseUnrealFactory.cs index 9f60a89..674642c 100644 --- a/UE.Toolkit.Core/Types/Unreal/Factories/BaseUnrealFactory.cs +++ b/UE.Toolkit.Core/Types/Unreal/Factories/BaseUnrealFactory.cs @@ -1,10 +1,13 @@ -using System.Runtime.InteropServices; +using UE.Toolkit.Core.Types.Interfaces; using UE.Toolkit.Core.Types.Unreal.Factories.Interfaces; namespace UE.Toolkit.Core.Types.Unreal.Factories; public abstract class BaseUnrealFactory : IUnrealFactory { + + public IUnrealMemoryInternal? Memory { get; set; } + public T Cast(IPtr obj) { var typeName = typeof(T).Name; @@ -20,6 +23,7 @@ public T Cast(IPtr obj) return (T)CreateUEnum(obj.Ptr); case nameof(IUUserDefinedEnum): return (T)CreateUUserDefinedEnum(obj.Ptr); + case nameof(IFByteProperty): return (T)CreateFByteProperty(obj.Ptr); case nameof(IFBoolProperty): @@ -44,6 +48,7 @@ public T Cast(IPtr obj) return (T)CreateFSetProperty(obj.Ptr); case nameof(IFOptionalProperty): return (T)CreateFOptionalProperty(obj.Ptr); + default: throw new NotSupportedException(typeName); } @@ -63,7 +68,9 @@ public T Cast(IPtr obj) public abstract IFArrayProperty CreateFArrayProperty(nint ptr); public abstract IFSetProperty CreateFSetProperty(nint ptr); public abstract IFOptionalProperty CreateFOptionalProperty(nint ptr); + public abstract IUObjectArray CreateUObjectArray(nint ptr); + public abstract IUObject CreateUObject(nint ptr); public abstract IUClass CreateUClass(nint ptr); public abstract IUScriptStruct CreateUScriptStruct(nint ptr); @@ -71,9 +78,13 @@ public T Cast(IPtr obj) public abstract IUField CreateUField(nint ptr); public abstract IUStruct CreateUStruct(nint ptr); public abstract IUUserDefinedEnum CreateUUserDefinedEnum(nint ptr); + // public abstract IUPackage CreateUPackage(nint ptr); + public abstract IUFunction CreateUFunction(nint ptr); + public abstract IFFieldClass CreateFFieldClass(nint ptr); public abstract IFField CreateFField(nint ptr); - // public abstract IUPackage CreateUPackage(nint ptr); + public abstract IFStructParams CreateFStructParams(nint ptr); public abstract IFPropertyParams CreateFPropertyParams(nint ptr); + public abstract IFGenericPropertyParams CreateFGenericPropertyParams(nint ptr); } \ No newline at end of file diff --git a/UE.Toolkit.Core/Types/Unreal/Factories/IUnrealFactory.cs b/UE.Toolkit.Core/Types/Unreal/Factories/IUnrealFactory.cs index 7b62e7c..43099fa 100644 --- a/UE.Toolkit.Core/Types/Unreal/Factories/IUnrealFactory.cs +++ b/UE.Toolkit.Core/Types/Unreal/Factories/IUnrealFactory.cs @@ -1,3 +1,4 @@ +using UE.Toolkit.Core.Types.Interfaces; using UE.Toolkit.Core.Types.Unreal.Factories.Interfaces; namespace UE.Toolkit.Core.Types.Unreal.Factories; @@ -9,6 +10,8 @@ public interface IUnrealFactory { T Cast(IPtr obj); nint SizeOf(); + IUnrealMemoryInternal? Memory { get; set; } + IFProperty CreateFProperty(nint ptr); IFBoolProperty CreateFBoolProperty(nint ptr); IFByteProperty CreateFByteProperty(nint ptr); @@ -22,6 +25,7 @@ public interface IUnrealFactory IFArrayProperty CreateFArrayProperty(nint ptr); IFSetProperty CreateFSetProperty(nint ptr); IFOptionalProperty CreateFOptionalProperty(nint ptr); + IUObjectArray CreateUObjectArray(nint ptr); IUObject CreateUObject(nint ptr); IUClass CreateUClass(nint ptr); @@ -30,9 +34,13 @@ public interface IUnrealFactory IUField CreateUField(nint ptr); IUStruct CreateUStruct(nint ptr); IUUserDefinedEnum CreateUUserDefinedEnum(nint ptr); + // IUPackage CreateUPackage(nint ptr); + IUFunction CreateUFunction(nint ptr); + IFFieldClass CreateFFieldClass(nint ptr); IFField CreateFField(nint ptr); - // IUPackage CreateUPackage(nint ptr); + IFStructParams CreateFStructParams(nint ptr); IFPropertyParams CreateFPropertyParams(nint ptr); + IFGenericPropertyParams CreateFGenericPropertyParams(nint ptr); } \ No newline at end of file diff --git a/UE.Toolkit.Core/Types/Unreal/Factories/Interfaces/IFPropertyParams.cs b/UE.Toolkit.Core/Types/Unreal/Factories/Interfaces/IFPropertyParams.cs index 37e781f..5d63b0f 100644 --- a/UE.Toolkit.Core/Types/Unreal/Factories/Interfaces/IFPropertyParams.cs +++ b/UE.Toolkit.Core/Types/Unreal/Factories/Interfaces/IFPropertyParams.cs @@ -8,4 +8,10 @@ public interface IFPropertyParams : IPtr EPropertyFlags PropertyFlags { get; } EPropertyGenFlags GenFlags { get; } EObjectFlags ObjectFlags { get; } +} + +public interface IFGenericPropertyParams : IFPropertyParams +{ + int ArrayDim { get; } + int Offset { get; } } \ No newline at end of file diff --git a/UE.Toolkit.Core/Types/Unreal/Factories/Interfaces/IFStructParams.cs b/UE.Toolkit.Core/Types/Unreal/Factories/Interfaces/IFStructParams.cs index 931f8cd..3f6e019 100644 --- a/UE.Toolkit.Core/Types/Unreal/Factories/Interfaces/IFStructParams.cs +++ b/UE.Toolkit.Core/Types/Unreal/Factories/Interfaces/IFStructParams.cs @@ -11,7 +11,7 @@ public interface IFStructParams : IPtr ulong Size { get; } ulong Alignment { get; } EObjectFlags ObjectFlags { get; } - int StructFlags { get; } + EStructFlags StructFlags { get; } int PropertyCount { get; } IFPropertyParams? GetProperty(int Index); IEnumerable Properties { get; } diff --git a/UE.Toolkit.Core/Types/Unreal/Factories/Interfaces/IUClass.cs b/UE.Toolkit.Core/Types/Unreal/Factories/Interfaces/IUClass.cs index a9ac9fe..0691391 100644 --- a/UE.Toolkit.Core/Types/Unreal/Factories/Interfaces/IUClass.cs +++ b/UE.Toolkit.Core/Types/Unreal/Factories/Interfaces/IUClass.cs @@ -3,4 +3,6 @@ namespace UE.Toolkit.Core.Types.Unreal.Factories.Interfaces; public interface IUClass : IUStruct { IUClass? GetSuperClass(); + + IUFunction? GetFunction(string Name); } \ No newline at end of file diff --git a/UE.Toolkit.Core/Types/Unreal/Factories/Interfaces/IUFunction.cs b/UE.Toolkit.Core/Types/Unreal/Factories/Interfaces/IUFunction.cs new file mode 100644 index 0000000..d970c9f --- /dev/null +++ b/UE.Toolkit.Core/Types/Unreal/Factories/Interfaces/IUFunction.cs @@ -0,0 +1,13 @@ +using UE.Toolkit.Core.Types.Unreal.UE5_4_4; + +namespace UE.Toolkit.Core.Types.Unreal.Factories.Interfaces; + +public interface IUFunction : IUStruct +{ + EFunctionFlags FunctionFlags { get; } + int ParamCount { get; } + int ParamSize { get; } + int ReturnValueOffset { get; } + + int GetTotalParameterSize(); +} \ No newline at end of file diff --git a/UE.Toolkit.Core/Types/Unreal/Factories/UE4_27_2/UnrealFactory.cs b/UE.Toolkit.Core/Types/Unreal/Factories/UE4_27_2/UnrealFactory.cs index f14671f..a1b725a 100644 --- a/UE.Toolkit.Core/Types/Unreal/Factories/UE4_27_2/UnrealFactory.cs +++ b/UE.Toolkit.Core/Types/Unreal/Factories/UE4_27_2/UnrealFactory.cs @@ -1,8 +1,9 @@ using System.Collections; +using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; using UE.Toolkit.Core.Common; using UE.Toolkit.Core.Types.Unreal.Factories.Interfaces; -using UE.Toolkit.Core.Types.Unreal.UE4_27_2; +using EFunctionFlags = UE.Toolkit.Core.Types.Unreal.UE5_4_4.EFunctionFlags; using EPropertyFlags = UE.Toolkit.Core.Types.Unreal.UE5_4_4.EPropertyFlags; using EPropertyGenFlags = UE.Toolkit.Core.Types.Unreal.UE5_4_4.EPropertyGenFlags; using EClassFlags = UE.Toolkit.Core.Types.Unreal.UE5_4_4.EClassFlags; @@ -20,15 +21,18 @@ using FEnumProperty = UE.Toolkit.Core.Types.Unreal.UE4_27_2.FEnumProperty; using FField = UE.Toolkit.Core.Types.Unreal.UE4_27_2.FField; using FFieldClass = UE.Toolkit.Core.Types.Unreal.UE4_27_2.FFieldClass; +using FGenericPropertyParams = UE.Toolkit.Core.Types.Unreal.UE4_27_2.FGenericPropertyParams; using FMapProperty = UE.Toolkit.Core.Types.Unreal.UE4_27_2.FMapProperty; using FObjectProperty = UE.Toolkit.Core.Types.Unreal.UE4_27_2.FObjectProperty; using FProperty = UE.Toolkit.Core.Types.Unreal.UE4_27_2.FProperty; +using FPropertyParamsBase = UE.Toolkit.Core.Types.Unreal.UE4_27_2.FPropertyParamsBase; using FSetProperty = UE.Toolkit.Core.Types.Unreal.UE4_27_2.FSetProperty; using FStructParams = UE.Toolkit.Core.Types.Unreal.UE4_27_2.FStructParams; using FStructProperty = UE.Toolkit.Core.Types.Unreal.UE4_27_2.FStructProperty; using UClass = UE.Toolkit.Core.Types.Unreal.UE4_27_2.UClass; using UEnum = UE.Toolkit.Core.Types.Unreal.UE4_27_2.UEnum; using UField = UE.Toolkit.Core.Types.Unreal.UE4_27_2.UField; +using UFunction = UE.Toolkit.Core.Types.Unreal.UE4_27_2.UFunction; using UObjectBase = UE.Toolkit.Core.Types.Unreal.UE4_27_2.UObjectBase; using UScriptStruct = UE.Toolkit.Core.Types.Unreal.UE4_27_2.UScriptStruct; using UStruct = UE.Toolkit.Core.Types.Unreal.UE4_27_2.UStruct; @@ -111,6 +115,8 @@ public override nint SizeOf() public override IUStruct CreateUStruct(nint ptr) => new UStructUE4_27_2(ptr, this); public override IUUserDefinedEnum CreateUUserDefinedEnum(nint ptr) => new UUserDefinedEnumUE4_27_2(ptr, this); + + public override IUFunction CreateUFunction(nint ptr) => new UFunctionUE4_27_2(ptr, this); public override IFFieldClass CreateFFieldClass(nint ptr) => new FFieldClassUE4_27_2(ptr, this); @@ -119,6 +125,8 @@ public override nint SizeOf() public override IFStructParams CreateFStructParams(nint ptr) => new FStructParamsUE4_27_2(ptr, this); public override IFPropertyParams CreateFPropertyParams(nint ptr) => new FPropertyParamsUE4_27_2(ptr, this); + + public override IFGenericPropertyParams CreateFGenericPropertyParams(nint ptr) => new FGenericPropertyParamsUE4_27_2(ptr, this); } public unsafe class FSetPropertyUE4_27_2(nint ptr, IUnrealFactory factory) @@ -457,6 +465,34 @@ public unsafe class UClassUE4_27_2(nint ptr, IUnrealFactory factory) public IUClass? GetSuperClass() => _self->_super.super_struct != null ? _factory.CreateUClass((nint)_self->_super.super_struct) : null; + + public IUFunction? GetFunction(string Name) + { + var FuncMapDict = new UE.Toolkit.Core.Types.Unreal.UE5_4_4.TMapDictionary>( + (UE.Toolkit.Core.Types.Unreal.UE5_4_4.TMap>*)(&_self->func_map), + _factory.Memory + ); + return FuncMapDict.TryGetValue(new(Name), out var Function) + ? factory.CreateUFunction((nint)Function.Value->Value) + : null; + } +} + +public unsafe class UFunctionUE4_27_2(nint ptr, IUnrealFactory factory) + : UStructUE4_27_2(ptr, factory), IUFunction +{ + private readonly UFunction* _self = (UFunction*)ptr; + + public EFunctionFlags FunctionFlags => _self->func_flags; + public int ParamCount => _self->num_params; + public int ParamSize => _self->params_size; + public int ReturnValueOffset => _self->return_value_offset; + + public int GetTotalParameterSize() + { + var LastProperty = ChildProperties.Any() ? _factory.CreateFProperty(ChildProperties.Last().Ptr) : null; + return LastProperty != null ? LastProperty!.Offset_Internal + LastProperty!.ElementSize : 0; + } } public unsafe class UObjectArrayUE4_27_2(nint ptr, IUnrealFactory factory) : IUObjectArray @@ -492,6 +528,7 @@ public void RemoveFromRootSet(int idx) } } + public class FPropertyParamEnumerator(IFStructParams owner) : IEnumerator, IEnumerable { @@ -534,7 +571,7 @@ public unsafe class FStructParamsUE4_27_2(nint ptr, IUnrealFactory factory) : IF public ulong Size => _self->SizeOf; public ulong Alignment => _self->AlignOf; public EObjectFlags ObjectFlags => _self->ObjectFlags; - public int StructFlags => _self->StructFlags; + public EStructFlags StructFlags => _self->StructFlags; public int PropertyCount => _self->NumProperties; public IFPropertyParams? GetProperty(int Index) { @@ -555,4 +592,13 @@ public unsafe class FPropertyParamsUE4_27_2(nint ptr, IUnrealFactory factory) : public EPropertyFlags PropertyFlags => _self->PropertyFlags; public EPropertyGenFlags GenFlags => _self->PropertyGenFlags; public EObjectFlags ObjectFlags => _self->ObjectFlags; +} + +public unsafe class FGenericPropertyParamsUE4_27_2(nint ptr, IUnrealFactory factory) + : FPropertyParamsUE4_27_2(ptr, factory), IFGenericPropertyParams +{ + private readonly FGenericPropertyParams* _self = (FGenericPropertyParams*)ptr; + + public int ArrayDim => _self->Super.ArrayDim; + public int Offset => _self->Super.Offset; } \ No newline at end of file diff --git a/UE.Toolkit.Core/Types/Unreal/Factories/UE5_4_4/UnrealFactory.cs b/UE.Toolkit.Core/Types/Unreal/Factories/UE5_4_4/UnrealFactory.cs index a606e63..e704500 100644 --- a/UE.Toolkit.Core/Types/Unreal/Factories/UE5_4_4/UnrealFactory.cs +++ b/UE.Toolkit.Core/Types/Unreal/Factories/UE5_4_4/UnrealFactory.cs @@ -62,10 +62,12 @@ public override nint SizeOf() public override IUField CreateUField(nint ptr) => new UField_UE5_4_4(ptr, this); public override IUStruct CreateUStruct(nint ptr) => new UStruct_UE5_4_4(ptr, this); public override IUUserDefinedEnum CreateUUserDefinedEnum(nint ptr) => new UUserDefinedEnum_UE5_4_4(ptr, this); + public override IUFunction CreateUFunction(nint ptr) => new UFunction_UE5_4_4(ptr, this); public override IFFieldClass CreateFFieldClass(nint ptr) => new FFieldClass_UE5_4_4(ptr, this); public override IFField CreateFField(nint ptr) => new FField_UE5_4_4(ptr, this); public override IFStructParams CreateFStructParams(nint ptr) => new FStructParamsUE5_4_4(ptr, this); public override IFPropertyParams CreateFPropertyParams(nint ptr) => new FPropertyParamsUE5_4_4(ptr, this); + public override IFGenericPropertyParams CreateFGenericPropertyParams(nint ptr) => new FGenericPropertyParamsUE5_4_4(ptr, this); } public unsafe class FOptionalProperty_UE5_4_4(nint ptr, IUnrealFactory factory) @@ -438,6 +440,33 @@ public unsafe class UClass_UE5_4_4(nint ptr, IUnrealFactory factory) public IUClass? GetSuperClass() => _self->GetSuperClass() != null ? _factory.CreateUClass((nint)_self->GetSuperClass()) : null; + + public IUFunction? GetFunction(string Name) + { + var FuncMapDict = new TMapDictionary( + (TMap*)(&_self->FuncMap), factory.Memory + ); + return FuncMapDict.TryGetValue(new(Name), out var Function) + ? factory.CreateUFunction((nint)Function.Value) + : null; + } +} + +public unsafe class UFunction_UE5_4_4(nint ptr, IUnrealFactory factory) + : UStruct_UE5_4_4(ptr, factory), IUFunction +{ + private readonly UFunction* _self = (UFunction*)ptr; + + public EFunctionFlags FunctionFlags => _self->FunctionFlags; + public int ParamCount => _self->NumParms; + public int ParamSize => _self->ParmsSize; + public int ReturnValueOffset => _self->ReturnValueOffset; + + public int GetTotalParameterSize() + { + var LastProperty = ChildProperties.Any() ? _factory.CreateFProperty(ChildProperties.Last().Ptr) : null; + return LastProperty != null ? LastProperty!.Offset_Internal + LastProperty!.ElementSize : 0; + } } public unsafe class UObjectArray_UE5_4_4(nint ptr, IUnrealFactory factory) : IUObjectArray @@ -514,7 +543,7 @@ public unsafe class FStructParamsUE5_4_4(nint ptr, IUnrealFactory factory) : IFS public ulong Size => _self->SizeOf; public ulong Alignment => _self->AlignOf; public EObjectFlags ObjectFlags => _self->ObjectFlags; - public int StructFlags => _self->StructFlags; + public EStructFlags StructFlags => _self->StructFlags; public int PropertyCount => _self->NumProperties; public IFPropertyParams? GetProperty(int Index) { @@ -535,4 +564,13 @@ public unsafe class FPropertyParamsUE5_4_4(nint ptr, IUnrealFactory factory) : I public EPropertyFlags PropertyFlags => _self->PropertyFlags; public EPropertyGenFlags GenFlags => _self->PropertyGenFlags; public EObjectFlags ObjectFlags => _self->ObjectFlags; +} + +public unsafe class FGenericPropertyParamsUE5_4_4(nint ptr, IUnrealFactory factory) + : FPropertyParamsUE5_4_4(ptr, factory), IFGenericPropertyParams +{ + private readonly FGenericPropertyParams* _self = (FGenericPropertyParams*)ptr; + + public int ArrayDim => _self->Super.ArrayDim; + public int Offset => _self->Super.Offset; } \ No newline at end of file diff --git a/UE.Toolkit.Core/Types/Unreal/UE4_27_2/FPropertyParams.cs b/UE.Toolkit.Core/Types/Unreal/UE4_27_2/FPropertyParams.cs index a9b5e30..d6c6729 100644 --- a/UE.Toolkit.Core/Types/Unreal/UE4_27_2/FPropertyParams.cs +++ b/UE.Toolkit.Core/Types/Unreal/UE4_27_2/FPropertyParams.cs @@ -5,7 +5,7 @@ namespace UE.Toolkit.Core.Types.Unreal.UE4_27_2; -[StructLayout(LayoutKind.Sequential, Pack = 4)] +[StructLayout(LayoutKind.Sequential)] public struct FPropertyParamsBase { public nint NameUTF8; @@ -16,14 +16,20 @@ public struct FPropertyParamsBase public int ArrayDim; } -[StructLayout(LayoutKind.Sequential, Pack = 4)] +[StructLayout(LayoutKind.Sequential)] public struct FPropertyParamsBaseWithOffset { - public FPropertyParamsBase Super; + // public FPropertyParamsBase Super; + public nint NameUTF8; + public nint RepNotifyFuncUTF8; + public EPropertyFlags PropertyFlags; + public EPropertyGenFlags PropertyGenFlags; + public EObjectFlags ObjectFlags; + public int ArrayDim; public int Offset; } -[StructLayout(LayoutKind.Sequential, Pack = 4)] +[StructLayout(LayoutKind.Sequential)] public struct FGenericPropertyParams { public FPropertyParamsBaseWithOffset Super; diff --git a/UE.Toolkit.Core/Types/Unreal/UE4_27_2/FStructParams.cs b/UE.Toolkit.Core/Types/Unreal/UE4_27_2/FStructParams.cs index ad2a306..865ba8a 100644 --- a/UE.Toolkit.Core/Types/Unreal/UE4_27_2/FStructParams.cs +++ b/UE.Toolkit.Core/Types/Unreal/UE4_27_2/FStructParams.cs @@ -1,5 +1,6 @@ using System.Runtime.InteropServices; using EObjectFlags = UE.Toolkit.Core.Types.Unreal.UE5_4_4.EObjectFlags; +using EStructFlags = UE.Toolkit.Core.Types.Unreal.UE5_4_4.EStructFlags; namespace UE.Toolkit.Core.Types.Unreal.UE4_27_2; @@ -15,7 +16,7 @@ public unsafe struct FStructParams public FPropertyParamsBase** Properties; public int NumProperties; public EObjectFlags ObjectFlags; - public int StructFlags; + public EStructFlags StructFlags; public FPropertyParamsBase* GetProperty(int Index) { diff --git a/UE.Toolkit.Core/Types/Unreal/UE4_27_2/Unreal.cs b/UE.Toolkit.Core/Types/Unreal/UE4_27_2/Unreal.cs index cf44a61..51ed573 100644 --- a/UE.Toolkit.Core/Types/Unreal/UE4_27_2/Unreal.cs +++ b/UE.Toolkit.Core/Types/Unreal/UE4_27_2/Unreal.cs @@ -39,6 +39,8 @@ public unsafe struct UClass [FieldOffset(0xe8)] public FName class_conf_name; [FieldOffset(0x100)] public TArray net_fields; [FieldOffset(0x118)] public UObjectBase* class_default_obj; // Default object of type described in UClass instance + [FieldOffset(0x120)] public nint sparse_class_data; + [FieldOffset(0x128)] public UScriptStruct* sparse_class_data_struct; [FieldOffset(0x130)] public TMap func_map; [FieldOffset(0x180)] public TMap super_func_map; [FieldOffset(0x1d8)] public TArray interfaces; @@ -156,6 +158,9 @@ public unsafe struct UFunction [FieldOffset(0xb0)] public EFunctionFlags func_flags; [FieldOffset(0xb4)] public byte num_params; [FieldOffset(0xb6)] public ushort params_size; + [FieldOffset(0xb8)] public ushort return_value_offset; + [FieldOffset(0xba)] public ushort rpc_id; + [FieldOffset(0xbc)] public ushort rpc_response_id; [FieldOffset(0xc0)] public FProperty* first_prop_to_init; [FieldOffset(0xc8)] public UFunction* event_graph_func; [FieldOffset(0xd8)] public IntPtr exec_func_ptr; diff --git a/UE.Toolkit.Core/Types/Unreal/UE5_4_4/EFunctionFlags.cs b/UE.Toolkit.Core/Types/Unreal/UE5_4_4/EFunctionFlags.cs new file mode 100644 index 0000000..2fbb7ba --- /dev/null +++ b/UE.Toolkit.Core/Types/Unreal/UE5_4_4/EFunctionFlags.cs @@ -0,0 +1,41 @@ +namespace UE.Toolkit.Core.Types.Unreal.UE5_4_4; + +[Flags] +public enum EFunctionFlags : uint +{ + // Function flags. + FUNC_None = 0x00000000, + + FUNC_Final = 0x00000001, // Function is final (prebindable, non-overridable function). + FUNC_RequiredAPI = 0x00000002, // Indicates this function is DLL exported/imported. + FUNC_BlueprintAuthorityOnly= 0x00000004, // Function will only run if the object has network authority + FUNC_BlueprintCosmetic = 0x00000008, // Function is cosmetic in nature and should not be invoked on dedicated servers + // FUNC_ = 0x00000010, // unused. + // FUNC_ = 0x00000020, // unused. + FUNC_Net = 0x00000040, // Function is network-replicated. + FUNC_NetReliable = 0x00000080, // Function should be sent reliably on the network. + FUNC_NetRequest = 0x00000100, // Function is sent to a net service + FUNC_Exec = 0x00000200, // Executable from command line. + FUNC_Native = 0x00000400, // Native function. + FUNC_Event = 0x00000800, // Event function. + FUNC_NetResponse = 0x00001000, // Function response from a net service + FUNC_Static = 0x00002000, // Static function. + FUNC_NetMulticast = 0x00004000, // Function is networked multicast Server -> All Clients + FUNC_UbergraphFunction = 0x00008000, // Function is used as the merge 'ubergraph' for a blueprint, only assigned when using the persistent 'ubergraph' frame + FUNC_MulticastDelegate = 0x00010000, // Function is a multi-cast delegate signature (also requires FUNC_Delegate to be set!) + FUNC_Public = 0x00020000, // Function is accessible in all classes (if overridden, parameters must remain unchanged). + FUNC_Private = 0x00040000, // Function is accessible only in the class it is defined in (cannot be overridden, but function name may be reused in subclasses. IOW: if overridden, parameters don't need to match, and Super.Func() cannot be accessed since it's private.) + FUNC_Protected = 0x00080000, // Function is accessible only in the class it is defined in and subclasses (if overridden, parameters much remain unchanged). + FUNC_Delegate = 0x00100000, // Function is delegate signature (either single-cast or multi-cast, depending on whether FUNC_MulticastDelegate is set.) + FUNC_NetServer = 0x00200000, // Function is executed on servers (set by replication code if passes check) + FUNC_HasOutParms = 0x00400000, // function has out (pass by reference) parameters + FUNC_HasDefaults = 0x00800000, // function has structs that contain defaults + FUNC_NetClient = 0x01000000, // function is executed on clients + FUNC_DLLImport = 0x02000000, // function is imported from a DLL + FUNC_BlueprintCallable = 0x04000000, // function can be called from blueprint code + FUNC_BlueprintEvent = 0x08000000, // function can be overridden/implemented from a blueprint + FUNC_BlueprintPure = 0x10000000, // function can be called from blueprint code, and is also pure (produces no side effects). If you set this, you should set FUNC_BlueprintCallable as well. + FUNC_EditorOnly = 0x20000000, // function can only be called from an editor scrippt. + FUNC_Const = 0x40000000, // function can be called from blueprint code, and only reads state (never writes state) + FUNC_NetValidate = 0x80000000, // function must supply a _Validate implementation +}; \ No newline at end of file diff --git a/UE.Toolkit.Core/Types/Unreal/UE5_4_4/FPropertyParams.cs b/UE.Toolkit.Core/Types/Unreal/UE5_4_4/FPropertyParams.cs index e27f5bc..c03c277 100644 --- a/UE.Toolkit.Core/Types/Unreal/UE5_4_4/FPropertyParams.cs +++ b/UE.Toolkit.Core/Types/Unreal/UE5_4_4/FPropertyParams.cs @@ -13,4 +13,25 @@ public struct FPropertyParamsBase public nint SetterFunc; public nint GetterFunc; public ushort ArrayDim; +} + +[StructLayout(LayoutKind.Sequential)] +public struct FPropertyParamsBaseWithOffset +{ + // public FPropertyParamsBase Super; + public nint NameUTF8; + public nint RepNotifyFuncUTF8; + public EPropertyFlags PropertyFlags; + public EPropertyGenFlags PropertyGenFlags; + public EObjectFlags ObjectFlags; + public nint SetterFunc; + public nint GetterFunc; + public ushort ArrayDim; + public ushort Offset; +} + +[StructLayout(LayoutKind.Sequential)] +public struct FGenericPropertyParams +{ + public FPropertyParamsBaseWithOffset Super; } \ No newline at end of file diff --git a/UE.Toolkit.Core/Types/Unreal/UE5_4_4/FStructParams.cs b/UE.Toolkit.Core/Types/Unreal/UE5_4_4/FStructParams.cs index 3a72512..0d8965e 100644 --- a/UE.Toolkit.Core/Types/Unreal/UE5_4_4/FStructParams.cs +++ b/UE.Toolkit.Core/Types/Unreal/UE5_4_4/FStructParams.cs @@ -1,5 +1,4 @@ using System.Runtime.InteropServices; -using EObjectFlags = UE.Toolkit.Core.Types.Unreal.UE5_4_4.EObjectFlags; namespace UE.Toolkit.Core.Types.Unreal.UE5_4_4; @@ -15,7 +14,7 @@ public unsafe struct FStructParams public ushort SizeOf; public byte AlignOf; public EObjectFlags ObjectFlags; - public int StructFlags; + public EStructFlags StructFlags; public FPropertyParamsBase* GetProperty(int Index) { diff --git a/UE.Toolkit.Core/Types/Unreal/UE5_4_4/UClass.cs b/UE.Toolkit.Core/Types/Unreal/UE5_4_4/UClass.cs index 5d31649..a3d4340 100644 --- a/UE.Toolkit.Core/Types/Unreal/UE5_4_4/UClass.cs +++ b/UE.Toolkit.Core/Types/Unreal/UE5_4_4/UClass.cs @@ -27,6 +27,9 @@ private static readonly Dictionary CastFlagsMap public TArray ClassReps; public TArray NetFields; public UObjectBase* ClassDefaultObject; + public nint SparseClassData; + public UScriptStruct* SparseClassDataStruct; + public TMap FuncMap; public readonly UClass* GetSuperClass() => (UClass*)Super.SuperStruct; diff --git a/UE.Toolkit.Core/Types/Unreal/UE5_4_4/UFunction.cs b/UE.Toolkit.Core/Types/Unreal/UE5_4_4/UFunction.cs index 519cc2c..bc09905 100644 --- a/UE.Toolkit.Core/Types/Unreal/UE5_4_4/UFunction.cs +++ b/UE.Toolkit.Core/Types/Unreal/UE5_4_4/UFunction.cs @@ -22,45 +22,4 @@ public unsafe struct UFunction //private UFunction** SingletonPtr; //#if WITH_LIVE_CODING public void* Func; -} - -public enum EFunctionFlags : uint -{ - // Function flags. - FUNC_None = 0x00000000, - - FUNC_Final = 0x00000001, // Function is final (prebindable, non-overridable function). - FUNC_RequiredAPI = 0x00000002, // Indicates this function is DLL exported/imported. - FUNC_BlueprintAuthorityOnly= 0x00000004, // Function will only run if the object has network authority - FUNC_BlueprintCosmetic = 0x00000008, // Function is cosmetic in nature and should not be invoked on dedicated servers - // FUNC_ = 0x00000010, // unused. - // FUNC_ = 0x00000020, // unused. - FUNC_Net = 0x00000040, // Function is network-replicated. - FUNC_NetReliable = 0x00000080, // Function should be sent reliably on the network. - FUNC_NetRequest = 0x00000100, // Function is sent to a net service - FUNC_Exec = 0x00000200, // Executable from command line. - FUNC_Native = 0x00000400, // Native function. - FUNC_Event = 0x00000800, // Event function. - FUNC_NetResponse = 0x00001000, // Function response from a net service - FUNC_Static = 0x00002000, // Static function. - FUNC_NetMulticast = 0x00004000, // Function is networked multicast Server -> All Clients - FUNC_UbergraphFunction = 0x00008000, // Function is used as the merge 'ubergraph' for a blueprint, only assigned when using the persistent 'ubergraph' frame - FUNC_MulticastDelegate = 0x00010000, // Function is a multi-cast delegate signature (also requires FUNC_Delegate to be set!) - FUNC_Public = 0x00020000, // Function is accessible in all classes (if overridden, parameters must remain unchanged). - FUNC_Private = 0x00040000, // Function is accessible only in the class it is defined in (cannot be overridden, but function name may be reused in subclasses. IOW: if overridden, parameters don't need to match, and Super.Func() cannot be accessed since it's private.) - FUNC_Protected = 0x00080000, // Function is accessible only in the class it is defined in and subclasses (if overridden, parameters much remain unchanged). - FUNC_Delegate = 0x00100000, // Function is delegate signature (either single-cast or multi-cast, depending on whether FUNC_MulticastDelegate is set.) - FUNC_NetServer = 0x00200000, // Function is executed on servers (set by replication code if passes check) - FUNC_HasOutParms = 0x00400000, // function has out (pass by reference) parameters - FUNC_HasDefaults = 0x00800000, // function has structs that contain defaults - FUNC_NetClient = 0x01000000, // function is executed on clients - FUNC_DLLImport = 0x02000000, // function is imported from a DLL - FUNC_BlueprintCallable = 0x04000000, // function can be called from blueprint code - FUNC_BlueprintEvent = 0x08000000, // function can be overridden/implemented from a blueprint - FUNC_BlueprintPure = 0x10000000, // function can be called from blueprint code, and is also pure (produces no side effects). If you set this, you should set FUNC_BlueprintCallable as well. - FUNC_EditorOnly = 0x20000000, // function can only be called from an editor scrippt. - FUNC_Const = 0x40000000, // function can be called from blueprint code, and only reads state (never writes state) - FUNC_NetValidate = 0x80000000, // function must supply a _Validate implementation - - FUNC_AllFlags = 0xFFFFFFFF, -}; \ No newline at end of file +} \ No newline at end of file diff --git a/UE.Toolkit.Core/Types/Unreal/UE5_4_4/UScriptStruct.cs b/UE.Toolkit.Core/Types/Unreal/UE5_4_4/UScriptStruct.cs index 632f2a4..a4b04d5 100644 --- a/UE.Toolkit.Core/Types/Unreal/UE5_4_4/UScriptStruct.cs +++ b/UE.Toolkit.Core/Types/Unreal/UE5_4_4/UScriptStruct.cs @@ -9,4 +9,12 @@ public struct UScriptStruct public EStructFlags StructFlags; public bool bPrepareCppStructOpsCompleted; public nint CppStructOps; +} + +[StructLayout(LayoutKind.Sequential)] +public struct ICppStructOps +{ + public nint VTable; + public uint Size; + public uint Alignment; } \ No newline at end of file diff --git a/UE.Toolkit.Interfaces/IUnrealClasses.cs b/UE.Toolkit.Interfaces/IUnrealClasses.cs index d19c958..65211e5 100644 --- a/UE.Toolkit.Interfaces/IUnrealClasses.cs +++ b/UE.Toolkit.Interfaces/IUnrealClasses.cs @@ -5,28 +5,8 @@ namespace UE.Toolkit.Interfaces; public interface IUnrealClasses : IUnrealClassesInternal { - /// - /// Listen for the creation of an object's class, then extend it's allocation size and call a custom constructor - /// after the existing constructor has executed. - /// - /// Number of bytes to increase the allocation size of object instances by. - /// A custom constructor to call after the original constructor - /// Object type. - /// - /// The extension is only created for the specific object type. If a struct/class inherits from this class, it - /// won't receive an extension. - /// - public void AddExtension(uint ExtraSize, Action> callback) - where TObject: unmanaged; - /// - /// Listen for the creation of an object's class, then call a custom constructor after the existing constructor - /// has executed. - /// - /// A custom constructor to call after the original constructor - /// Object type. - public void AddConstructor(Action> callback) - where TObject : unmanaged => AddExtension(0, callback); + #region Class/Struct Getters /// /// Get the type information for a specified object. If this class has no type info, this will return null. @@ -63,6 +43,33 @@ public void AddConstructor(Action> callback) /// Type information for the object type with the given name. /// If the type information exists. public bool GetScriptStructInfoFromName(string Name, out IUScriptStruct? Value); + + #endregion + + #region Struct Field Extension Methods + + /// + /// Listen for the creation of an object's class, then extend it's allocation size and call a custom constructor + /// after the existing constructor has executed. + /// + /// Number of bytes to increase the allocation size of object instances by. + /// A custom constructor to call after the original constructor + /// Object type. + /// + /// The extension is only created for the specific object type. If a struct/class inherits from this class, it + /// won't receive an extension. + /// + public void AddExtension(uint ExtraSize, Action> callback) + where TObject: unmanaged; + + /// + /// Listen for the creation of an object's class, then call a custom constructor after the existing constructor + /// has executed. + /// + /// A custom constructor to call after the original constructor + /// Object type. + public void AddConstructor(Action> callback) + where TObject : unmanaged => AddExtension(0, callback); /// /// Add an Int8 property to the object's class with the specified name and offset. This will make the field @@ -264,4 +271,100 @@ public bool AddClassProperty(string Name, int Offset, out IFCla /// Object type. public bool AddArrayProperty(string Name, int Offset, IFProperty Inner, out IFArrayProperty? Property) where TObject : unmanaged; + + #endregion + + #region New Struct Construction + + /// + /// Create a Int8 param to insert into a struct param's property list. This is used when registering new + /// struct types to the engine at runtime. + /// + /// Param's name. + /// Param's offset. + public IFGenericPropertyParams? CreateI8Param(string Name, int Offset); + + /// + /// Create a Int16 param to insert into a struct param's property list. This is used when registering new + /// struct types to the engine at runtime. + /// + /// Param's name. + /// Param's offset. + public IFGenericPropertyParams? CreateI16Param(string Name, int Offset); + + /// + /// Create a Int32 param to insert into a struct param's property list. This is used when registering new + /// struct types to the engine at runtime. + /// + /// Param's name. + /// Param's offset. + public IFGenericPropertyParams? CreateI32Param(string Name, int Offset); + + /// + /// Create a Int64 param to insert into a struct param's property list. This is used when registering new + /// struct types to the engine at runtime. + /// + /// Param's name. + /// Param's offset. + public IFGenericPropertyParams? CreateI64Param(string Name, int Offset); + + /// + /// Create a UInt8 param to insert into a struct param's property list. This is used when registering new + /// struct types to the engine at runtime. + /// + /// Param's name. + /// Param's offset. + public IFGenericPropertyParams? CreateU8Param(string Name, int Offset); + + /// + /// Create a UInt16 param to insert into a struct param's property list. This is used when registering new + /// struct types to the engine at runtime. + /// + /// Param's name. + /// Param's offset. + public IFGenericPropertyParams? CreateU16Param(string Name, int Offset); + + /// + /// Create a UInt32 param to insert into a struct param's property list. This is used when registering new + /// struct types to the engine at runtime. + /// + /// Param's name. + /// Param's offset. + public IFGenericPropertyParams? CreateU32Param(string Name, int Offset); + + /// + /// Create a UInt64 param to insert into a struct param's property list. This is used when registering new + /// struct types to the engine at runtime. + /// + /// Param's name. + /// Param's offset. + public IFGenericPropertyParams? CreateU64Param(string Name, int Offset); + + /// + /// Create a float param to insert into a struct param's property list. This is used when registering new + /// struct types to the engine at runtime. + /// + /// Param's name. + /// Param's offset. + public IFGenericPropertyParams? CreateF32Param(string Name, int Offset); + + /// + /// Create a double param to insert into a struct param's property list. This is used when registering new + /// struct types to the engine at runtime. + /// + /// Param's name. + /// Param's offset. + public IFGenericPropertyParams? CreateF64Param(string Name, int Offset); + + /// + /// Create a new UE struct definition at runtime which can be used by Blueprints and Object XML. This can be used + /// to annotate instances of another struct with an inner type. + /// + /// Name of the new struct. + /// Size of the new struct. + /// A list of exposed fields within this struct. + /// New script struct. + public bool CreateScriptStruct(string Name, int Size, List Fields, out IUScriptStruct? Out); + + #endregion } \ No newline at end of file diff --git a/UE.Toolkit.Interfaces/IUnrealMethods.cs b/UE.Toolkit.Interfaces/IUnrealMethods.cs new file mode 100644 index 0000000..1d39476 --- /dev/null +++ b/UE.Toolkit.Interfaces/IUnrealMethods.cs @@ -0,0 +1,133 @@ +using UE.Toolkit.Core.Types.Interfaces; + +namespace UE.Toolkit.Interfaces; + +public interface IUnrealMethods +{ + + /// + /// Create a Int8 parameter, which can be passed into ProcessEvent to call a blueprint exposable function + /// with the specified value. + /// + /// Target value. + /// An invocation parameter containing the value. + public IInvocationParameter CreateI8Param(sbyte Value); + + /// + /// Create a Int16 parameter, which can be passed into ProcessEvent to call a blueprint exposable function + /// with the specified value. + /// + /// Target value. + /// An invocation parameter containing the value. + public IInvocationParameter CreateI16Param(short Value); + + /// + /// Create a Int32 parameter, which can be passed into ProcessEvent to call a blueprint exposable function + /// with the specified value. + /// + /// Target value. + /// An invocation parameter containing the value. + public IInvocationParameter CreateI32Param(int Value); + + /// + /// Create a Int64 parameter, which can be passed into ProcessEvent to call a blueprint exposable function + /// with the specified value. + /// + /// Target value. + /// An invocation parameter containing the value. + public IInvocationParameter CreateI64Param(long Value); + + /// + /// Create a UInt8 parameter, which can be passed into ProcessEvent to call a blueprint exposable function + /// with the specified value. + /// + /// Target value. + /// An invocation parameter containing the value. + public IInvocationParameter CreateU8Param(byte Value); + + /// + /// Create a UInt16 parameter, which can be passed into ProcessEvent to call a blueprint exposable function + /// with the specified value. + /// + /// Target value. + /// An invocation parameter containing the value. + public IInvocationParameter CreateU16Param(ushort Value); + + /// + /// Create a UInt32 parameter, which can be passed into ProcessEvent to call a blueprint exposable function + /// with the specified value. + /// + /// Target value. + /// An invocation parameter containing the value. + public IInvocationParameter CreateU32Param(uint Value); + + /// + /// Create a UInt64 parameter, which can be passed into ProcessEvent to call a blueprint exposable function + /// with the specified value. + /// + /// Target value. + /// An invocation parameter containing the value. + public IInvocationParameter CreateU64Param(ulong Value); + + /// + /// Create a float parameter, which can be passed into ProcessEvent to call a blueprint exposable function + /// with the specified value. + /// + /// Target value. + /// An invocation parameter containing the value. + public IInvocationParameter CreateF32Param(float Value); + + /// + /// Create a double parameter, which can be passed into ProcessEvent to call a blueprint exposable function + /// with the specified value. + /// + /// Target value. + /// An invocation parameter containing the value. + public IInvocationParameter CreateF64Param(double Value); + + /// + /// Create a FName parameter, which can be passed into ProcessEvent to call a blueprint exposable function + /// with the specified value. + /// + /// Target value. + /// An invocation parameter containing the value. + public IInvocationParameter CreateNameParam(string Value); + + /// + /// Create a FString parameter, which can be passed into ProcessEvent to call a blueprint exposable function + /// with the specified value. + /// + /// Target value. + /// An invocation parameter containing the value. + public IInvocationParameter CreateStringParam(string Value); + + /// + /// Create a bool parameter, which can be passed into ProcessEvent to call a blueprint exposable function + /// with the specified value. + /// + /// Target value. + /// An invocation parameter containing the value. + public IInvocationParameter CreateBoolParam(bool Value); + + /// + /// Invoke a blueprint exposable function of the given name on the target object. Assumes that the function has + /// no return value. + /// + /// Object to invoke function on. + /// Name of the function to invoke. + /// Object type. + public void ProcessEvent(ToolkitUObject Object, string Name, + ref List Parameters) where TObject : unmanaged; + + /// + /// Invoke a blueprint exposable function of the given name on the target object. Assumes that the function has + /// a return value. + /// + /// Object to invoke function on. + /// Name of the function to invoke. + /// Object type. + public TReturnType ProcessEvent(ToolkitUObject Object, string Name, + ref List Parameters) + where TObject : unmanaged + where TReturnType: unmanaged; +} \ No newline at end of file diff --git a/UE.Toolkit.Reloaded/Common/GameConfigs/Games/UE4_27_2_P3R.cs b/UE.Toolkit.Reloaded/Common/GameConfigs/Games/UE4_27_2_P3R.cs index e3a480d..43bc30c 100644 --- a/UE.Toolkit.Reloaded/Common/GameConfigs/Games/UE4_27_2_P3R.cs +++ b/UE.Toolkit.Reloaded/Common/GameConfigs/Games/UE4_27_2_P3R.cs @@ -1,5 +1,8 @@ using UE.Toolkit.Core.Types.Unreal.Factories; using UE.Toolkit.Core.Types.Unreal.Factories.UE4_27_2; +using UE.Toolkit.Interfaces; +using UE.Toolkit.Reloaded.Unreal; + // ReSharper disable InconsistentNaming namespace UE.Toolkit.Reloaded.Common.GameConfigs.Games; @@ -8,4 +11,5 @@ public class UE4_27_2_P3R : UE5_4_4_ClairObscur { public override string Id => "P3R"; public override IUnrealFactory Factory { get; } = new UnrealFactory(); + public override IUnrealMemory Memory { get; } = new UnrealMemory(); } \ No newline at end of file diff --git a/UE.Toolkit.Reloaded/Common/GameConfigs/Games/UE5_4_4_ClairObscur.cs b/UE.Toolkit.Reloaded/Common/GameConfigs/Games/UE5_4_4_ClairObscur.cs index b4254fb..3bfa78e 100644 --- a/UE.Toolkit.Reloaded/Common/GameConfigs/Games/UE5_4_4_ClairObscur.cs +++ b/UE.Toolkit.Reloaded/Common/GameConfigs/Games/UE5_4_4_ClairObscur.cs @@ -1,5 +1,7 @@ using UE.Toolkit.Core.Types.Unreal.Factories; using UE.Toolkit.Core.Types.Unreal.Factories.UE5_4_4; +using UE.Toolkit.Interfaces; +using UE.Toolkit.Reloaded.Unreal; namespace UE.Toolkit.Reloaded.Common.GameConfigs.Games; @@ -8,4 +10,5 @@ public class UE5_4_4_ClairObscur : IGameConfig { public virtual string Id => "Clair Obscur"; public virtual IUnrealFactory Factory { get; } = new UnrealFactory(); + public virtual IUnrealMemory Memory { get; } = new UnrealMemory(); } \ No newline at end of file diff --git a/UE.Toolkit.Reloaded/Common/GameConfigs/IGameConfig.cs b/UE.Toolkit.Reloaded/Common/GameConfigs/IGameConfig.cs index 320466e..329448c 100644 --- a/UE.Toolkit.Reloaded/Common/GameConfigs/IGameConfig.cs +++ b/UE.Toolkit.Reloaded/Common/GameConfigs/IGameConfig.cs @@ -1,6 +1,7 @@ // ReSharper disable InconsistentNaming using UE.Toolkit.Core.Types.Unreal.Factories; +using UE.Toolkit.Interfaces; namespace UE.Toolkit.Reloaded.Common.GameConfigs; @@ -8,4 +9,5 @@ public interface IGameConfig { string Id { get; } IUnrealFactory Factory { get; } + IUnrealMemory Memory { get; } } \ No newline at end of file diff --git a/UE.Toolkit.Reloaded/Config.cs b/UE.Toolkit.Reloaded/Config.cs index b36c22f..11af816 100644 --- a/UE.Toolkit.Reloaded/Config.cs +++ b/UE.Toolkit.Reloaded/Config.cs @@ -19,6 +19,10 @@ public class Config : Configurable [DefaultValue(false)] public bool LogTablesEnabled { get; set; } = false; + [DisplayName("Log UScriptStruct")] + [DefaultValue(false)] + public bool LogScriptStructEnabled { get; set; } = false; + [DisplayName("Log FNames (SLOW)")] [Description("Logs FNames.\nThis has a significant performance hit and names should be searched in the log file, not the console.")] [DefaultValue(false)] diff --git a/UE.Toolkit.Reloaded/Mod.cs b/UE.Toolkit.Reloaded/Mod.cs index e7eb1a2..46b5feb 100644 --- a/UE.Toolkit.Reloaded/Mod.cs +++ b/UE.Toolkit.Reloaded/Mod.cs @@ -23,6 +23,7 @@ public class Mod : ModBase, IExports public static Config Config = null!; #pragma warning restore CA2211 + // Reloaded-II API private readonly IModLoader _modLoader; private readonly IReloadedHooks _hooks; private readonly ILogger _log; @@ -30,9 +31,10 @@ public class Mod : ModBase, IExports private readonly IModConfig _modConfig; + // Unreal Toolkit API private readonly IUnrealFactory _factory; private readonly UnrealNames _names; - private readonly UnrealMemory _memory; + private readonly IUnrealMemory _memory; private readonly UnrealObjects _objects; private readonly DataTablesService _tables; private readonly TypeRegistry _typeRegistry; @@ -41,6 +43,7 @@ public class Mod : ModBase, IExports private readonly UnrealStrings _strings; private readonly UnrealClasses _classes; private readonly Common.ResolveAddress _address; + private readonly UnrealMethods _methods; public Mod(ModContext context) { @@ -63,15 +66,16 @@ public Mod(ModContext context) _names = new(); _objects = new(_factory); _tables = new(); - _memory = new(); + _memory = GameConfig.Instance.Memory; _typeRegistry = new(); _writer = new(_typeRegistry, _objects, _tables); _toolkit = new(_writer); _strings = new(); _address = new(); _classes = new(_factory, _memory, _hooks, _address); + _methods = new(_factory, _memory, _classes, _objects, _hooks); - _modLoader.AddOrReplaceController(_owner, _memory); + _modLoader.AddOrReplaceController(_owner, _memory); _modLoader.AddOrReplaceController(_owner, _tables); _modLoader.AddOrReplaceController(_owner, _objects); _modLoader.AddOrReplaceController(_owner, _typeRegistry); @@ -80,6 +84,7 @@ public Mod(ModContext context) _modLoader.AddOrReplaceController(_owner, _strings); _modLoader.AddOrReplaceController(_owner, _classes); _modLoader.AddOrReplaceController(_owner, _factory); + _modLoader.AddOrReplaceController(_owner, _methods); _modLoader.ModLoaded += OnModLoaded; } diff --git a/UE.Toolkit.Reloaded/Project/UE.Toolkit.Reloaded/p3r/unreal.ini b/UE.Toolkit.Reloaded/Project/UE.Toolkit.Reloaded/p3r/unreal.ini index 6d0374c..e771c20 100644 --- a/UE.Toolkit.Reloaded/Project/UE.Toolkit.Reloaded/p3r/unreal.ini +++ b/UE.Toolkit.Reloaded/Project/UE.Toolkit.Reloaded/p3r/unreal.ini @@ -5,6 +5,9 @@ Malloc=0x10 Realloc=0x20 QuantizeSize=0x38 +[UObject] +ProcessEvent=0x220 + [UStruct] Link=0x288 diff --git a/UE.Toolkit.Reloaded/Project/UE.Toolkit.Reloaded/unreal.ini b/UE.Toolkit.Reloaded/Project/UE.Toolkit.Reloaded/unreal.ini index 59190e9..4c4736a 100644 --- a/UE.Toolkit.Reloaded/Project/UE.Toolkit.Reloaded/unreal.ini +++ b/UE.Toolkit.Reloaded/Project/UE.Toolkit.Reloaded/unreal.ini @@ -7,6 +7,9 @@ Malloc=0x28 Realloc=0x38 QuantizeSize=0x50 +[UObject] +ProcessEvent=0x268 + [UStruct] Link=0x2d0 diff --git a/UE.Toolkit.Reloaded/Reflection/IPropertyFlagsBuilder.cs b/UE.Toolkit.Reloaded/Reflection/IPropertyFlagsBuilder.cs new file mode 100644 index 0000000..ea8746e --- /dev/null +++ b/UE.Toolkit.Reloaded/Reflection/IPropertyFlagsBuilder.cs @@ -0,0 +1,9 @@ +using UE.Toolkit.Core.Types.Unreal.Factories.Interfaces; +using EPropertyFlags = UE.Toolkit.Core.Types.Unreal.UE5_4_4.EPropertyFlags; + +namespace UE.Toolkit.Reloaded.Reflection; + +public interface IPropertyFlagsBuilder +{ + EPropertyFlags CreatePropertyFlags(PropertyVisibility Visibility, PropertyBuilderFlags InFlags); +} \ No newline at end of file diff --git a/UE.Toolkit.Reloaded/Reflection/PropertyFactory.cs b/UE.Toolkit.Reloaded/Reflection/PropertyFactory.cs index 154a040..d0055b2 100644 --- a/UE.Toolkit.Reloaded/Reflection/PropertyFactory.cs +++ b/UE.Toolkit.Reloaded/Reflection/PropertyFactory.cs @@ -4,10 +4,12 @@ using UE.Toolkit.Core.Types.Unreal.Factories.Interfaces; using UE.Toolkit.Core.Types.Unreal.UE5_4_4; using UE.Toolkit.Interfaces; +using UE.Toolkit.Reloaded.Reflection.UE5_4_4; namespace UE.Toolkit.Reloaded.Reflection; -public abstract class BasePropertyFactory(IUnrealFactory factory, IUnrealMemory memory, IUnrealClasses classes) +public abstract class BasePropertyFactory(IUnrealFactory factory, IUnrealMemory memory, + IUnrealClasses classes, IPropertyFlagsBuilder flags) { private Dictionary? PropertyNames = null; @@ -75,8 +77,6 @@ protected bool TryGetClassAndProperty(string PropertyName, out IUClass? protected abstract void SetPropertySuperFields(IFField Field, string Name, IUClass ClassReflection, FieldClassGlobal PropertyClass); - protected abstract EPropertyFlags CreatePropertyFlags(PropertyVisibility Visibility, PropertyBuilderFlags InFlags); - protected abstract void SetCopyPropertyFields(IFProperty Property, int Offset, PropertyVisibility Visibility) where T : unmanaged; @@ -236,6 +236,7 @@ public abstract bool CreateArray(out IFArrayProperty? NewProperty, stri protected readonly IUnrealFactory Factory = factory; protected readonly IUnrealMemory Memory = memory; protected readonly IUnrealClasses Classes = classes; + protected readonly IPropertyFlagsBuilder Flags = flags; protected const int FIELD_ALIGNMENT = 0x80; } diff --git a/UE.Toolkit.Reloaded/Reflection/TypeFactory.cs b/UE.Toolkit.Reloaded/Reflection/TypeFactory.cs index 83558d0..606ff4b 100644 --- a/UE.Toolkit.Reloaded/Reflection/TypeFactory.cs +++ b/UE.Toolkit.Reloaded/Reflection/TypeFactory.cs @@ -1,18 +1,34 @@ using UE.Toolkit.Core.Types.Unreal.Factories; +using UE.Toolkit.Core.Types.Unreal.Factories.Interfaces; using UE.Toolkit.Interfaces; namespace UE.Toolkit.Reloaded.Reflection; -public abstract class BaseTypeFactory(IUnrealFactory factory, IUnrealMemory memory, IUnrealClasses classes) +public abstract class BaseTypeFactory(IUnrealFactory factory, IUnrealMemory memory, + IUnrealClasses classes, IPropertyFlagsBuilder flags) { - public abstract void CreateScriptStruct(string Name, int Size); + // public abstract void CreateScriptStruct(string Name, int Size); + public abstract bool CreateI8Param(string Name, int Offset, out IFGenericPropertyParams? Out); + public abstract bool CreateI16Param(string Name, int Offset, out IFGenericPropertyParams? Out); + public abstract bool CreateI32Param(string Name, int Offset, out IFGenericPropertyParams? Out); + public abstract bool CreateI64Param(string Name, int Offset, out IFGenericPropertyParams? Out); + public abstract bool CreateU8Param(string Name, int Offset, out IFGenericPropertyParams? Out); + public abstract bool CreateU16Param(string Name, int Offset, out IFGenericPropertyParams? Out); + public abstract bool CreateU32Param(string Name, int Offset, out IFGenericPropertyParams? Out); + public abstract bool CreateU64Param(string Name, int Offset, out IFGenericPropertyParams? Out); + public abstract bool CreateF32Param(string Name, int Offset, out IFGenericPropertyParams? Out); + public abstract bool CreateF64Param(string Name, int Offset, out IFGenericPropertyParams? Out); + + internal abstract bool CreateStructParam(string Name, int Size, + List Fields, out IFStructParams? Out); #region Dependencies protected readonly IUnrealFactory Factory = factory; protected readonly IUnrealMemory Memory = memory; - protected readonly IUnrealClasses Classes = classes; + protected readonly IUnrealClasses Classes = classes; + protected readonly IPropertyFlagsBuilder Flags = flags; #endregion diff --git a/UE.Toolkit.Reloaded/Reflection/UE4_27_2/PropertyFactory.cs b/UE.Toolkit.Reloaded/Reflection/UE4_27_2/PropertyFactory.cs index 34dccfd..686094a 100644 --- a/UE.Toolkit.Reloaded/Reflection/UE4_27_2/PropertyFactory.cs +++ b/UE.Toolkit.Reloaded/Reflection/UE4_27_2/PropertyFactory.cs @@ -15,26 +15,11 @@ namespace UE.Toolkit.Reloaded.Reflection.UE4_27_2; -public class PropertyFactory(IUnrealFactory factory, IUnrealMemory memory, IUnrealClasses classes) - : BasePropertyFactory(factory, memory, classes) +public class PropertyFactory(IUnrealFactory factory, IUnrealMemory memory, + IUnrealClasses classes, IPropertyFlagsBuilder flags) + : BasePropertyFactory(factory, memory, classes, flags) { - protected override EPropertyFlags CreatePropertyFlags(PropertyVisibility Visibility, PropertyBuilderFlags InFlags) - { - var Flags = EPropertyFlags.CPF_Edit | EPropertyFlags.CPF_BlueprintVisible; - if (InFlags.HasFlag(PropertyBuilderFlags.NoCtor)) Flags |= EPropertyFlags.CPF_ZeroConstructor; - if (InFlags.HasFlag(PropertyBuilderFlags.Copy)) Flags |= EPropertyFlags.CPF_IsPlainOldData; - if (InFlags.HasFlag(PropertyBuilderFlags.NoDtor)) Flags |= EPropertyFlags.CPF_NoDestructor; - if (InFlags.HasFlag(PropertyBuilderFlags.Hash)) Flags |= EPropertyFlags.CPF_HasGetValueTypeHash; - Flags |= Visibility switch - { - PropertyVisibility.Public => EPropertyFlags.CPF_NativeAccessSpecifierPublic, - PropertyVisibility.Protected => EPropertyFlags.CPF_NativeAccessSpecifierProtected, - _ => EPropertyFlags.CPF_NativeAccessSpecifierPrivate, - }; - return Flags; - } - protected override unsafe void LinkToPropertyList(IFProperty Property, IUClass Reflect) { var pProperty = (FProperty*)Property.Ptr; @@ -83,7 +68,7 @@ private unsafe void SetPropertyFieldsInner(IFProperty Property, int Offset, var pProperty = (FProperty*)Property.Ptr; pProperty->array_dim = 1; pProperty->element_size = Marshal.SizeOf(); - pProperty->property_flags = CreatePropertyFlags(Visibility, PropertyFlags); + pProperty->property_flags = Flags.CreatePropertyFlags(Visibility, PropertyFlags); SetPropertyFieldDefaults(pProperty, Offset); } @@ -156,7 +141,7 @@ public override bool CreateStruct(out IFStructProperty? NewPrope var pProperty = (FProperty*)Alloc; pProperty->array_dim = 1; pProperty->element_size = ScriptStruct!.PropertiesSize; // FExampleStruct mExampleField; - pProperty->property_flags = CreatePropertyFlags(Visibility, PropertyBuilderFlags.None); + pProperty->property_flags = Flags.CreatePropertyFlags(Visibility, PropertyBuilderFlags.None); SetPropertyFieldDefaults(pProperty, Offset); } LinkToPropertyList(NewProperty, ClassReflection!); @@ -181,7 +166,7 @@ public override bool CreateObject(out IFObjectProperty? NewPrope pProperty->element_size = Marshal.SizeOf(); // For ObjectProperty: UExampleClass* pExampleObject; // For ClassProperty: TSubclassOf pExampleClass; - pProperty->property_flags = CreatePropertyFlags(Visibility, PropertyBuilderFlags.None); + pProperty->property_flags = Flags.CreatePropertyFlags(Visibility, PropertyBuilderFlags.None); SetPropertyFieldDefaults(pProperty, Offset); } LinkToPropertyList(NewProperty, ClassReflection!); @@ -212,7 +197,7 @@ public override bool CreateArray(out IFArrayProperty? NewProperty, strin var pProperty = (FProperty*)Alloc; pProperty->array_dim = 1; pProperty->element_size = 0x10; // sizeof(TArray) - pProperty->property_flags = CreatePropertyFlags(Visibility, PropertyBuilderFlags.NoCtor); + pProperty->property_flags = Flags.CreatePropertyFlags(Visibility, PropertyBuilderFlags.NoCtor); SetPropertyFieldDefaults(pProperty, Offset); } LinkToPropertyList(NewProperty, ClassReflection); diff --git a/UE.Toolkit.Reloaded/Reflection/UE4_27_2/PropertyFlagsBuilder.cs b/UE.Toolkit.Reloaded/Reflection/UE4_27_2/PropertyFlagsBuilder.cs new file mode 100644 index 0000000..4bdebe5 --- /dev/null +++ b/UE.Toolkit.Reloaded/Reflection/UE4_27_2/PropertyFlagsBuilder.cs @@ -0,0 +1,23 @@ +using UE.Toolkit.Core.Types.Unreal.Factories.Interfaces; +using EPropertyFlags = UE.Toolkit.Core.Types.Unreal.UE5_4_4.EPropertyFlags; + +namespace UE.Toolkit.Reloaded.Reflection.UE4_27_2; + +public class PropertyFlagsBuilder : IPropertyFlagsBuilder +{ + public EPropertyFlags CreatePropertyFlags(PropertyVisibility Visibility, PropertyBuilderFlags InFlags) + { + var Flags = EPropertyFlags.CPF_Edit | EPropertyFlags.CPF_BlueprintVisible; + if (InFlags.HasFlag(PropertyBuilderFlags.NoCtor)) Flags |= EPropertyFlags.CPF_ZeroConstructor; + if (InFlags.HasFlag(PropertyBuilderFlags.Copy)) Flags |= EPropertyFlags.CPF_IsPlainOldData; + if (InFlags.HasFlag(PropertyBuilderFlags.NoDtor)) Flags |= EPropertyFlags.CPF_NoDestructor; + if (InFlags.HasFlag(PropertyBuilderFlags.Hash)) Flags |= EPropertyFlags.CPF_HasGetValueTypeHash; + Flags |= Visibility switch + { + PropertyVisibility.Public => EPropertyFlags.CPF_NativeAccessSpecifierPublic, + PropertyVisibility.Protected => EPropertyFlags.CPF_NativeAccessSpecifierProtected, + _ => EPropertyFlags.CPF_NativeAccessSpecifierPrivate, + }; + return Flags; + } +} \ No newline at end of file diff --git a/UE.Toolkit.Reloaded/Reflection/UE4_27_2/TypeFactory.cs b/UE.Toolkit.Reloaded/Reflection/UE4_27_2/TypeFactory.cs index dd7489e..acea6c4 100644 --- a/UE.Toolkit.Reloaded/Reflection/UE4_27_2/TypeFactory.cs +++ b/UE.Toolkit.Reloaded/Reflection/UE4_27_2/TypeFactory.cs @@ -1,26 +1,166 @@ -using System.Runtime.InteropServices; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using UE.Toolkit.Core.Types.Unreal.Factories; using UE.Toolkit.Core.Types.Unreal.Factories.Interfaces; -using UE.Toolkit.Core.Types.Unreal.UE4_27_2; using UE.Toolkit.Interfaces; +using ICppStructOps = UE.Toolkit.Core.Types.Unreal.UE5_4_4.ICppStructOps; +using FStructParams = UE.Toolkit.Core.Types.Unreal.UE4_27_2.FStructParams; +using FPropertyParamsBase = UE.Toolkit.Core.Types.Unreal.UE4_27_2.FPropertyParamsBase; +using FGenericPropertyParams = UE.Toolkit.Core.Types.Unreal.UE4_27_2.FGenericPropertyParams; +using EObjectFlags = UE.Toolkit.Core.Types.Unreal.UE5_4_4.EObjectFlags; +using EPropertyGenFlags = UE.Toolkit.Core.Types.Unreal.UE5_4_4.EPropertyGenFlags; +using EStructFlags = UE.Toolkit.Core.Types.Unreal.UE5_4_4.EStructFlags; namespace UE.Toolkit.Reloaded.Reflection.UE4_27_2; -public class TypeFactory(IUnrealFactory factory, IUnrealMemory memory, IUnrealClasses classes) - : BaseTypeFactory(factory, memory, classes) +public class TypeFactory(IUnrealFactory factory, IUnrealMemory memory, + IUnrealClasses classes, IPropertyFlagsBuilder flags) + : BaseTypeFactory(factory, memory, classes, flags) { - private unsafe void SetUObjectFields(IUObject Object, string Name) + private static Func? FMemory_Malloc_Static = null; + private static nint Malloc_Static_Size = 0; + + [UnmanagedCallersOnly(CallConvs = [typeof(CallConvStdcall)])] + private static byte Noop_Bool_False() => 0; + + [UnmanagedCallersOnly(CallConvs = [typeof(CallConvStdcall)])] + private static byte Noop_Bool_True() => 1; + + [UnmanagedCallersOnly(CallConvs = [typeof(CallConvStdcall)])] + private static void Noop_Void() {} + + [UnmanagedCallersOnly(CallConvs = [typeof(CallConvStdcall)])] + private static uint Noop_U32() => 0; + + private static nint CurrentCppStructOpsVtable; + private static uint CurrentCppStructOpsSize; + private const uint CPP_STRUCT_OPS_ALIGNMENT = 0x8; + private const nint CPP_STRUCT_OPS_VTABLE_SIZE = 39; + + [UnmanagedCallersOnly(CallConvs = [typeof(CallConvStdcall)])] + private static unsafe nint InitializeCppStructOps() { - // var pObject = (UObjectBase*)Object.Ptr; - // pObject->_vtable = 0; - // pObject->ObjectFlags = EObjectFlags - // pObject->InternalIndex = + var Alloc = (ICppStructOps*)FMemory_Malloc_Static!(Marshal.SizeOf(), 0); + Alloc->VTable = CurrentCppStructOpsVtable; + Alloc->Size = CurrentCppStructOpsSize; + Alloc->Alignment = CPP_STRUCT_OPS_ALIGNMENT; + return (nint)Alloc; } - - public override void CreateScriptStruct(string Name, int Size) + + private unsafe void CreateCppStructOpsVtable() + { + // 0-38 + ((nint*)CurrentCppStructOpsVtable)[0] = (nint)(delegate* unmanaged[Stdcall])(&Noop_Void); // ~ICppStructOps + ((nint*)CurrentCppStructOpsVtable)[1] = (nint)(delegate* unmanaged[Stdcall])(&Noop_Bool_False); // HasNoopConstructor + ((nint*)CurrentCppStructOpsVtable)[2] = (nint)(delegate* unmanaged[Stdcall])(&Noop_Bool_True); // HasZeroConstructor + ((nint*)CurrentCppStructOpsVtable)[3] = (nint)(delegate* unmanaged[Stdcall])(&Noop_Void); // Construct + ((nint*)CurrentCppStructOpsVtable)[4] = (nint)(delegate* unmanaged[Stdcall])(&Noop_Void); // ConstructForTests + ((nint*)CurrentCppStructOpsVtable)[5] = (nint)(delegate* unmanaged[Stdcall])(&Noop_Bool_False); // HasDestructor + ((nint*)CurrentCppStructOpsVtable)[6] = (nint)(delegate* unmanaged[Stdcall])(&Noop_Void); // Destruct + ((nint*)CurrentCppStructOpsVtable)[7] = (nint)(delegate* unmanaged[Stdcall])(&Noop_Bool_False); // HasSerializer + ((nint*)CurrentCppStructOpsVtable)[8] = (nint)(delegate* unmanaged[Stdcall])(&Noop_Bool_False); // HasStructuredSerializer + ((nint*)CurrentCppStructOpsVtable)[9] = (nint)(delegate* unmanaged[Stdcall])(&Noop_Bool_False); // Serialize(FArchive) + ((nint*)CurrentCppStructOpsVtable)[10] = (nint)(delegate* unmanaged[Stdcall])(&Noop_Bool_False); // Serialize(FStructedArchive::FSlot) + ((nint*)CurrentCppStructOpsVtable)[11] = (nint)(delegate* unmanaged[Stdcall])(&Noop_Bool_False); // HasPostSerialize + ((nint*)CurrentCppStructOpsVtable)[12] = (nint)(delegate* unmanaged[Stdcall])(&Noop_Void); // PostSerializer + ((nint*)CurrentCppStructOpsVtable)[13] = (nint)(delegate* unmanaged[Stdcall])(&Noop_Bool_False); // HasNetSerializer + ((nint*)CurrentCppStructOpsVtable)[14] = (nint)(delegate* unmanaged[Stdcall])(&Noop_Bool_False); // HasNetSharedSerialization + ((nint*)CurrentCppStructOpsVtable)[15] = (nint)(delegate* unmanaged[Stdcall])(&Noop_Void); // NetSerializer + ((nint*)CurrentCppStructOpsVtable)[16] = (nint)(delegate* unmanaged[Stdcall])(&Noop_Bool_False); // HasNetDeltaSerializer + ((nint*)CurrentCppStructOpsVtable)[17] = (nint)(delegate* unmanaged[Stdcall])(&Noop_Void); // NetDeltaSerialize + ((nint*)CurrentCppStructOpsVtable)[18] = (nint)(delegate* unmanaged[Stdcall])(&Noop_Bool_False); // HasPostScriptConstruct + ((nint*)CurrentCppStructOpsVtable)[19] = (nint)(delegate* unmanaged[Stdcall])(&Noop_Void); // PostScriptConstruct + ((nint*)CurrentCppStructOpsVtable)[20] = (nint)(delegate* unmanaged[Stdcall])(&Noop_Bool_True); // IsPlainOldData + ((nint*)CurrentCppStructOpsVtable)[21] = (nint)(delegate* unmanaged[Stdcall])(&Noop_Bool_True); // HasCopy + ((nint*)CurrentCppStructOpsVtable)[22] = (nint)(delegate* unmanaged[Stdcall])(&Noop_Void); // Copy + ((nint*)CurrentCppStructOpsVtable)[23] = (nint)(delegate* unmanaged[Stdcall])(&Noop_Bool_False); // HasIdentical + ((nint*)CurrentCppStructOpsVtable)[24] = (nint)(delegate* unmanaged[Stdcall])(&Noop_Bool_False); // Identical + ((nint*)CurrentCppStructOpsVtable)[25] = (nint)(delegate* unmanaged[Stdcall])(&Noop_Bool_False); // HasExportTextItem + ((nint*)CurrentCppStructOpsVtable)[26] = (nint)(delegate* unmanaged[Stdcall])(&Noop_Bool_False); // ExportTextItem + ((nint*)CurrentCppStructOpsVtable)[27] = (nint)(delegate* unmanaged[Stdcall])(&Noop_Bool_False); // HasImportTextItem + ((nint*)CurrentCppStructOpsVtable)[28] = (nint)(delegate* unmanaged[Stdcall])(&Noop_Bool_False); // ImportTextItem + ((nint*)CurrentCppStructOpsVtable)[29] = (nint)(delegate* unmanaged[Stdcall])(&Noop_Bool_False); // HasAddStructReferencedObjects + ((nint*)CurrentCppStructOpsVtable)[30] = (nint)(delegate* unmanaged[Stdcall])(&Noop_Void); // AddStructReferencedObjects + ((nint*)CurrentCppStructOpsVtable)[31] = (nint)(delegate* unmanaged[Stdcall])(&Noop_Bool_False); // HasSerializeFromMismatchTag + ((nint*)CurrentCppStructOpsVtable)[32] = (nint)(delegate* unmanaged[Stdcall])(&Noop_Bool_False); // HasStructuredSerializeFromMismatchedTag + ((nint*)CurrentCppStructOpsVtable)[33] = (nint)(delegate* unmanaged[Stdcall])(&Noop_Bool_False); // SerializeFroMismatchedTag + ((nint*)CurrentCppStructOpsVtable)[34] = (nint)(delegate* unmanaged[Stdcall])(&Noop_Bool_False); // StructuredSerializeFroMismatchedTag + ((nint*)CurrentCppStructOpsVtable)[35] = (nint)(delegate* unmanaged[Stdcall])(&Noop_Bool_False); // HasGetTypeHash + ((nint*)CurrentCppStructOpsVtable)[36] = (nint)(delegate* unmanaged[Stdcall])(&Noop_U32); // GetStructTypeHash + ((nint*)CurrentCppStructOpsVtable)[37] = (nint)(delegate* unmanaged[Stdcall])(&Noop_U32); // GetComputedPropertyFlags + ((nint*)CurrentCppStructOpsVtable)[38] = (nint)(delegate* unmanaged[Stdcall])(&Noop_Bool_False); // IsAbstract + } + + internal override unsafe bool CreateStructParam(string Name, int Size, + List Fields, out IFStructParams? Out) + { + var pProperties = (FPropertyParamsBase**)Memory.MallocZeroed(Marshal.SizeOf() * Fields.Count); + var StructParamStatic = (FStructParams*)Memory.MallocZeroed(Marshal.SizeOf()); + StructParamStatic->OuterFunc = (nint)(delegate* unmanaged[Stdcall])(&Unreal.UnrealClasses.FStructProperty_OuterFunc_Callback); + StructParamStatic->SuperFunc = nint.Zero; + StructParamStatic->StructOpsFunc = (nint)(delegate* unmanaged[Stdcall])(&InitializeCppStructOps); + StructParamStatic->NameUTF8 = Marshal.StringToHGlobalAnsi(Name); + StructParamStatic->SizeOf = (ulong)Size; + StructParamStatic->AlignOf = CPP_STRUCT_OPS_ALIGNMENT; // alignof(nint) + StructParamStatic->Properties = pProperties; + StructParamStatic->NumProperties = Fields.Count; + StructParamStatic->ObjectFlags = EObjectFlags.RF_Public | EObjectFlags.RF_MarkAsNative | EObjectFlags.RF_Transient; + StructParamStatic->StructFlags = EStructFlags.STRUCT_Native; + foreach (var (i, Field) in Fields.Select((x, i) => (i, x))) + StructParamStatic->Properties[i] = (FPropertyParamsBase*)Field.Ptr; + Out = Factory.CreateFStructParams((nint)StructParamStatic); + FMemory_Malloc_Static ??= (size, align) => Memory.MallocZeroed(size, align); + // sizeof(UScriptStruct::ICppStructOps::VTable) + CurrentCppStructOpsVtable = Memory.MallocZeroed(Marshal.SizeOf() * CPP_STRUCT_OPS_VTABLE_SIZE); + CurrentCppStructOpsSize = (uint)Size; + CreateCppStructOpsVtable(); + return true; + } + + private unsafe bool CreateGenericParam(string Name, int Offset, + EPropertyGenFlags GenFlags, out IFGenericPropertyParams? Out) { - var Alloc = Memory.Malloc(Marshal.SizeOf(), TYPE_ALIGNMENT); - SetUObjectFields(Factory.CreateUObject(Alloc), Name); + var PropParamStatic = (FGenericPropertyParams*)Memory.Malloc(Marshal.SizeOf()); + PropParamStatic->Super.NameUTF8 = Marshal.StringToHGlobalAnsi(Name); + PropParamStatic->Super.RepNotifyFuncUTF8 = nint.Zero; + PropParamStatic->Super.PropertyFlags = Flags.CreatePropertyFlags(PropertyVisibility.Public, PropertyBuilderFlags.None); + PropParamStatic->Super.PropertyGenFlags = GenFlags; + PropParamStatic->Super.ObjectFlags = EObjectFlags.RF_Public | EObjectFlags.RF_MarkAsNative | + EObjectFlags.RF_Transient; + PropParamStatic->Super.ArrayDim = 1; + PropParamStatic->Super.Offset = Offset; + Out = Factory.CreateFGenericPropertyParams((nint)PropParamStatic); + return true; } + + public override bool CreateI8Param(string Name, int Offset, out IFGenericPropertyParams? Out) + => CreateGenericParam(Name, Offset, EPropertyGenFlags.Int8, out Out); + + public override bool CreateI16Param(string Name, int Offset, out IFGenericPropertyParams? Out) + => CreateGenericParam(Name, Offset, EPropertyGenFlags.Int16, out Out); + + public override bool CreateI32Param(string Name, int Offset, out IFGenericPropertyParams? Out) + => CreateGenericParam(Name, Offset, EPropertyGenFlags.Int, out Out); + + public override bool CreateI64Param(string Name, int Offset, out IFGenericPropertyParams? Out) + => CreateGenericParam(Name, Offset, EPropertyGenFlags.Int64, out Out); + + public override bool CreateU8Param(string Name, int Offset, out IFGenericPropertyParams? Out) + => CreateGenericParam(Name, Offset, EPropertyGenFlags.Int8, out Out); + + public override bool CreateU16Param(string Name, int Offset, out IFGenericPropertyParams? Out) + => CreateGenericParam(Name, Offset, EPropertyGenFlags.UInt16, out Out); + + public override bool CreateU32Param(string Name, int Offset, out IFGenericPropertyParams? Out) + => CreateGenericParam(Name, Offset, EPropertyGenFlags.UInt32, out Out); + + public override bool CreateU64Param(string Name, int Offset, out IFGenericPropertyParams? Out) + => CreateGenericParam(Name, Offset, EPropertyGenFlags.UInt64, out Out); + + public override bool CreateF32Param(string Name, int Offset, out IFGenericPropertyParams? Out) + => CreateGenericParam(Name, Offset, EPropertyGenFlags.Float, out Out); + + public override bool CreateF64Param(string Name, int Offset, out IFGenericPropertyParams? Out) + => CreateGenericParam(Name, Offset, EPropertyGenFlags.Double, out Out); } \ No newline at end of file diff --git a/UE.Toolkit.Reloaded/Reflection/UE5_4_4/PropertyFactory.cs b/UE.Toolkit.Reloaded/Reflection/UE5_4_4/PropertyFactory.cs index 1253fc8..761a624 100644 --- a/UE.Toolkit.Reloaded/Reflection/UE5_4_4/PropertyFactory.cs +++ b/UE.Toolkit.Reloaded/Reflection/UE5_4_4/PropertyFactory.cs @@ -7,24 +7,10 @@ namespace UE.Toolkit.Reloaded.Reflection.UE5_4_4; -public class PropertyFactory(IUnrealFactory factory, IUnrealMemory memory, IUnrealClasses classes) - : BasePropertyFactory(factory, memory, classes) +public class PropertyFactory(IUnrealFactory factory, IUnrealMemory memory, + IUnrealClasses classes, IPropertyFlagsBuilder flags) + : BasePropertyFactory(factory, memory, classes, flags) { - protected override EPropertyFlags CreatePropertyFlags(PropertyVisibility Visibility, PropertyBuilderFlags InFlags) - { - var Flags = EPropertyFlags.CPF_Edit | EPropertyFlags.CPF_BlueprintVisible; - if (InFlags.HasFlag(PropertyBuilderFlags.NoCtor)) Flags |= EPropertyFlags.CPF_ZeroConstructor; - if (InFlags.HasFlag(PropertyBuilderFlags.Copy)) Flags |= EPropertyFlags.CPF_IsPlainOldData; - if (InFlags.HasFlag(PropertyBuilderFlags.NoDtor)) Flags |= EPropertyFlags.CPF_NoDestructor; - if (InFlags.HasFlag(PropertyBuilderFlags.Hash)) Flags |= EPropertyFlags.CPF_HasGetValueTypeHash; - Flags |= Visibility switch - { - PropertyVisibility.Public => EPropertyFlags.CPF_NativeAccessSpecifierPublic, - PropertyVisibility.Protected => EPropertyFlags.CPF_NativeAccessSpecifierProtected, - _ => EPropertyFlags.CPF_NativeAccessSpecifierPrivate, - }; - return Flags; - } protected override unsafe void LinkToPropertyList(IFProperty Property, IUClass Reflect) { @@ -72,7 +58,7 @@ private unsafe void SetPropertyFieldsInner(IFProperty Property, int Offset, var pProperty = (FProperty*)Property.Ptr; pProperty->ArrayDim = 1; pProperty->ElementSize = Marshal.SizeOf(); - pProperty->PropertyFlags = CreatePropertyFlags(Visibility, PropertyFlags); + pProperty->PropertyFlags = Flags.CreatePropertyFlags(Visibility, PropertyFlags); SetPropertyFieldDefaults(pProperty, Offset); } @@ -145,7 +131,7 @@ public override bool CreateStruct(out IFStructProperty? NewPrope var pProperty = (FProperty*)Alloc; pProperty->ArrayDim = 1; pProperty->ElementSize = ScriptStruct!.PropertiesSize; // FExampleStruct mExampleField; - pProperty->PropertyFlags = CreatePropertyFlags(Visibility, PropertyBuilderFlags.None); + pProperty->PropertyFlags = Flags.CreatePropertyFlags(Visibility, PropertyBuilderFlags.None); SetPropertyFieldDefaults(pProperty, Offset); } LinkToPropertyList(NewProperty, ClassReflection!); @@ -170,7 +156,7 @@ public override bool CreateObject(out IFObjectProperty? NewPrope pProperty->ElementSize = Marshal.SizeOf(); // For ObjectProperty: UExampleClass* pExampleObject; // For ClassProperty: TSubclassOf pExampleClass; - pProperty->PropertyFlags = CreatePropertyFlags(Visibility, PropertyBuilderFlags.None); + pProperty->PropertyFlags = Flags.CreatePropertyFlags(Visibility, PropertyBuilderFlags.None); SetPropertyFieldDefaults(pProperty, Offset); } LinkToPropertyList(NewProperty, ClassReflection!); @@ -201,7 +187,7 @@ public override bool CreateArray(out IFArrayProperty? NewProperty, strin var pProperty = (FProperty*)Alloc; pProperty->ArrayDim = 1; pProperty->ElementSize= 0x10; // sizeof(TArray) - pProperty->PropertyFlags = CreatePropertyFlags(Visibility, PropertyBuilderFlags.NoCtor); + pProperty->PropertyFlags = Flags.CreatePropertyFlags(Visibility, PropertyBuilderFlags.NoCtor); SetPropertyFieldDefaults(pProperty, Offset); } LinkToPropertyList(NewProperty, ClassReflection!); diff --git a/UE.Toolkit.Reloaded/Reflection/UE5_4_4/PropertyFlagsBuilder.cs b/UE.Toolkit.Reloaded/Reflection/UE5_4_4/PropertyFlagsBuilder.cs new file mode 100644 index 0000000..3bd1f51 --- /dev/null +++ b/UE.Toolkit.Reloaded/Reflection/UE5_4_4/PropertyFlagsBuilder.cs @@ -0,0 +1,23 @@ +using UE.Toolkit.Core.Types.Unreal.Factories.Interfaces; +using EPropertyFlags = UE.Toolkit.Core.Types.Unreal.UE5_4_4.EPropertyFlags; + +namespace UE.Toolkit.Reloaded.Reflection.UE5_4_4; + +public class PropertyFlagsBuilder : IPropertyFlagsBuilder +{ + public EPropertyFlags CreatePropertyFlags(PropertyVisibility Visibility, PropertyBuilderFlags InFlags) + { + var Flags = EPropertyFlags.CPF_Edit | EPropertyFlags.CPF_BlueprintVisible; + if (InFlags.HasFlag(PropertyBuilderFlags.NoCtor)) Flags |= EPropertyFlags.CPF_ZeroConstructor; + if (InFlags.HasFlag(PropertyBuilderFlags.Copy)) Flags |= EPropertyFlags.CPF_IsPlainOldData; + if (InFlags.HasFlag(PropertyBuilderFlags.NoDtor)) Flags |= EPropertyFlags.CPF_NoDestructor; + if (InFlags.HasFlag(PropertyBuilderFlags.Hash)) Flags |= EPropertyFlags.CPF_HasGetValueTypeHash; + Flags |= Visibility switch + { + PropertyVisibility.Public => EPropertyFlags.CPF_NativeAccessSpecifierPublic, + PropertyVisibility.Protected => EPropertyFlags.CPF_NativeAccessSpecifierProtected, + _ => EPropertyFlags.CPF_NativeAccessSpecifierPrivate, + }; + return Flags; + } +} \ No newline at end of file diff --git a/UE.Toolkit.Reloaded/Reflection/UE5_4_4/TypeFactory.cs b/UE.Toolkit.Reloaded/Reflection/UE5_4_4/TypeFactory.cs index b78fafd..8da6ad5 100644 --- a/UE.Toolkit.Reloaded/Reflection/UE5_4_4/TypeFactory.cs +++ b/UE.Toolkit.Reloaded/Reflection/UE5_4_4/TypeFactory.cs @@ -1,6 +1,77 @@ -namespace UE.Toolkit.Reloaded.Reflection.UE5_4_4; +using UE.Toolkit.Core.Types.Unreal.Factories; +using UE.Toolkit.Core.Types.Unreal.Factories.Interfaces; +using UE.Toolkit.Interfaces; -public class TypeFactory +namespace UE.Toolkit.Reloaded.Reflection.UE5_4_4; + +public class TypeFactory(IUnrealFactory factory, IUnrealMemory memory, + IUnrealClasses classes, IPropertyFlagsBuilder flags) + : BaseTypeFactory(factory, memory, classes, flags) { + public override bool CreateI8Param(string Name, int Offset, out IFGenericPropertyParams? Out) + { + Out = null; + return false; + } + + public override bool CreateI16Param(string Name, int Offset, out IFGenericPropertyParams? Out) + { + Out = null; + return false; + } + + public override bool CreateI32Param(string Name, int Offset, out IFGenericPropertyParams? Out) + { + Out = null; + return false; + } + + public override bool CreateI64Param(string Name, int Offset, out IFGenericPropertyParams? Out) + { + Out = null; + return false; + } + + public override bool CreateU8Param(string Name, int Offset, out IFGenericPropertyParams? Out) + { + Out = null; + return false; + } + public override bool CreateU16Param(string Name, int Offset, out IFGenericPropertyParams? Out) + { + Out = null; + return false; + } + + public override bool CreateU32Param(string Name, int Offset, out IFGenericPropertyParams? Out) + { + Out = null; + return false; + } + + public override bool CreateU64Param(string Name, int Offset, out IFGenericPropertyParams? Out) + { + Out = null; + return false; + } + + public override bool CreateF32Param(string Name, int Offset, out IFGenericPropertyParams? Out) + { + Out = null; + return false; + } + + public override bool CreateF64Param(string Name, int Offset, out IFGenericPropertyParams? Out) + { + Out = null; + return false; + } + + internal override unsafe bool CreateStructParam(string Name, int Size, + List Fields, out IFStructParams? Out) + { + Out = null; + return false; + } } \ No newline at end of file diff --git a/UE.Toolkit.Reloaded/Unreal/UnrealClasses.cs b/UE.Toolkit.Reloaded/Unreal/UnrealClasses.cs index fae00bd..8265050 100644 --- a/UE.Toolkit.Reloaded/Unreal/UnrealClasses.cs +++ b/UE.Toolkit.Reloaded/Unreal/UnrealClasses.cs @@ -1,9 +1,8 @@ using System.Collections.Concurrent; -using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Runtime.InteropServices.Marshalling; using Reloaded.Hooks.Definitions; -using UE.Toolkit.Core.Types; using UE.Toolkit.Core.Types.Interfaces; using UE.Toolkit.Core.Types.Unreal.Factories; using UE.Toolkit.Core.Types.Unreal.Factories.Interfaces; @@ -188,7 +187,8 @@ private void UStruct_LinkImpl(UStruct* pThis, nint Ar, byte bRelinkExistingPrope private delegate nint GetStaticStruct(nint pInRegister, nint pStructOuter, nint pStructName, nint Size, int Crc); private SHFunction _GetStaticStruct; - private Dictionary PackageNameToUObject = new(); + private static Dictionary PackageNameToUObject = new(); + private static string? PackageNameToUObjectKey; private static Func? GetStaticStructCurrentCallback = null; private nint GetStaticStructImpl(nint pInRegister, nint pStructOuter, nint pStructName, nint Size, int Crc) @@ -205,19 +205,21 @@ private nint GetStaticStructImpl(nint pInRegister, nint pStructOuter, nint pStru [UnmanagedCallersOnly(CallConvs = [typeof(CallConvStdcall)])] private static nint GetStaticStructCallback() => GetStaticStructCurrentCallback!(); + [UnmanagedCallersOnly(CallConvs = [typeof(CallConvStdcall)])] + internal static nint FStructProperty_OuterFunc_Callback() => PackageNameToUObject[PackageNameToUObjectKey].Ptr; + private delegate void ConstructUScriptStruct(nint ppScriptStruct, nint pStructParams); private SHFunction _ConstructUScriptStruct; private void ConstructUScriptStructImpl(nint ppScriptStruct, nint pStructParams) { - /* - var StructParams = Factory.CreateFStructParams(pStructParams); - Log.Debug($"FStructParams {StructParams.Name} @ 0x{StructParams.Ptr:x}: flags: {StructParams.ObjectFlags}, {StructParams.StructFlags} (size: 0x{StructParams.Size:x}, align: 0x{StructParams.Alignment:x})"); - foreach (var Property in StructParams.Properties) + if (Mod.Config.LogScriptStructEnabled) { - Log.Debug($"\tFPropertyParams {Property.Name} @ 0x{Property.Ptr:x}: {Property.GenFlags}, {Property.PropertyFlags}, {Property.ObjectFlags}"); + var StructParams = Factory.CreateFStructParams(pStructParams); + Log.Information($"{StructParams.Name} @ 0x{StructParams.Ptr:x} || Object Flags: {StructParams.ObjectFlags} || Struct Flags: {StructParams.StructFlags} || Size: 0x{StructParams.Size:x} || Align: 0x{StructParams.Alignment:x})"); + foreach (var Property in StructParams.Properties) + Log.Information($"\t\t {Property.Name} @ 0x{Property.Ptr:x} || Gen Flags: {Property.GenFlags} || Property Flags: {Property.PropertyFlags} || Object Flags: {Property.ObjectFlags}"); } - */ _ConstructUScriptStruct!.Hook!.OriginalFunction(ppScriptStruct, pStructParams); } @@ -373,7 +375,46 @@ public bool AddArrayProperty(string Name, int Offset, IFProperty Inner, out IFArrayProperty? Property) where TObject : unmanaged { Property = null; - return false; + return PropertyFactory.CreateArray(out Property, Name, Offset, PropertyVisibility.Public, Inner); + } + + public IFGenericPropertyParams? CreateI8Param(string Name, int Offset) + => TypeFactory.CreateI8Param(Name, Offset, out var Out) ? Out : null; + + public IFGenericPropertyParams? CreateI16Param(string Name, int Offset) + => TypeFactory.CreateI16Param(Name, Offset, out var Out) ? Out : null; + + public IFGenericPropertyParams? CreateI32Param(string Name, int Offset) + => TypeFactory.CreateI32Param(Name, Offset, out var Out) ? Out : null; + + public IFGenericPropertyParams? CreateI64Param(string Name, int Offset) + => TypeFactory.CreateI64Param(Name, Offset, out var Out) ? Out : null; + + public IFGenericPropertyParams? CreateU8Param(string Name, int Offset) + => TypeFactory.CreateU8Param(Name, Offset, out var Out) ? Out : null; + + public IFGenericPropertyParams? CreateU16Param(string Name, int Offset) + => TypeFactory.CreateU16Param(Name, Offset, out var Out) ? Out : null; + + public IFGenericPropertyParams? CreateU32Param(string Name, int Offset) + => TypeFactory.CreateU32Param(Name, Offset, out var Out) ? Out : null; + + public IFGenericPropertyParams? CreateU64Param(string Name, int Offset) + => TypeFactory.CreateU64Param(Name, Offset, out var Out) ? Out : null; + + public IFGenericPropertyParams? CreateF32Param(string Name, int Offset) + => TypeFactory.CreateF32Param(Name, Offset, out var Out) ? Out : null; + + public IFGenericPropertyParams? CreateF64Param(string Name, int Offset) + => TypeFactory.CreateF64Param(Name, Offset, out var Out) ? Out : null; + + public bool CreateScriptStruct(string Name, int Size, List Fields, out IUScriptStruct? Out) + { + TypeFactory.CreateStructParam(Name, Size, Fields, out var Param); + var pScriptStruct = nint.Zero; + ConstructUScriptStructImpl((nint)(&pScriptStruct), Param.Ptr); + Out = Factory.CreateUScriptStruct(pScriptStruct); + return true; } #endregion @@ -396,6 +437,7 @@ public bool GetFieldClassGlobal(FName Name, out FieldClassGlobal FieldClass) private ResolveAddress Address; private BasePropertyFactory PropertyFactory; + private BaseTypeFactory TypeFactory; public UnrealClasses(IUnrealFactory _Factory, IUnrealMemory _Memory, IReloadedHooks _Hooks, ResolveAddress _Address) { @@ -403,20 +445,36 @@ public UnrealClasses(IUnrealFactory _Factory, IUnrealMemory _Memory, IReloadedHo Memory = _Memory; Hooks = _Hooks; Address = _Address; - + + IPropertyFlagsBuilder FlagsBuilder = GameConfig.Instance.Id switch + { + "P3R" => new Reflection.UE4_27_2.PropertyFlagsBuilder(), + _ => new Reflection.UE5_4_4.PropertyFlagsBuilder(), + }; + PropertyFactory = GameConfig.Instance.Id switch { - "P3R" => new Reflection.UE4_27_2.PropertyFactory(Factory, Memory, this), - _ => new Reflection.UE5_4_4.PropertyFactory(Factory, Memory, this), + "P3R" => new Reflection.UE4_27_2.PropertyFactory(Factory, Memory, this, FlagsBuilder), + _ => new Reflection.UE5_4_4.PropertyFactory(Factory, Memory, this, FlagsBuilder), + }; + + TypeFactory = GameConfig.Instance.Id switch + { + "P3R" => new Reflection.UE4_27_2.TypeFactory(Factory, Memory, this, FlagsBuilder), + _ => new Reflection.UE5_4_4.TypeFactory(Factory, Memory, this, FlagsBuilder), }; Project.Inis.UsingSetting(Constants.UnrealIniId, "Link", nameof(UStruct), value => UStructLinkOffset = value); + Project.Inis.UsingSetting(Constants.UnrealIniId, "GamePackage", nameof(UPackage), + value => PackageNameToUObjectKey = value); + _GetPrivateStaticClassBodyUE4 = new(GetPrivateStaticClassBodyUE4); _GetPrivateStaticClassBodyUE5 = new(GetPrivateStaticClassBodyUE5); _GetStaticStruct = new(GetStaticStructImpl); _ConstructUScriptStruct = new(ConstructUScriptStructImpl); + // _ConstructUScriptStruct = new(); } } diff --git a/UE.Toolkit.Reloaded/Unreal/UnrealMethods.cs b/UE.Toolkit.Reloaded/Unreal/UnrealMethods.cs new file mode 100644 index 0000000..70f33c0 --- /dev/null +++ b/UE.Toolkit.Reloaded/Unreal/UnrealMethods.cs @@ -0,0 +1,360 @@ +using System.Runtime.InteropServices; +using Reloaded.Hooks.Definitions; +using UE.Toolkit.Core.Types; +using UE.Toolkit.Core.Types.Interfaces; +using UE.Toolkit.Core.Types.Unreal.Factories; +using UE.Toolkit.Core.Types.Unreal.Factories.Interfaces; +using EPropertyFlags = UE.Toolkit.Core.Types.Unreal.UE5_4_4.EPropertyFlags; +using FName = UE.Toolkit.Core.Types.Unreal.UE5_4_4.FName; +using FString = UE.Toolkit.Core.Types.Unreal.UE5_4_4.FString; +using UE.Toolkit.Interfaces; +using UE.Toolkit.Reloaded.Common; + +namespace UE.Toolkit.Reloaded.Unreal; + +public class I8InvocationParameter(sbyte value) : InvocationParameterCopyable(value) +{ + public override string ExpectedPropertyClass => "Int8Property"; +} + +public class I16InvocationParameter(short value) : InvocationParameterCopyable(value) +{ + public override string ExpectedPropertyClass => "Int16Property"; +} + +public class I32InvocationParameter(int value) : InvocationParameterCopyable(value) +{ + public override string ExpectedPropertyClass => "IntProperty"; +} + +public class I64InvocationParameter(long value) : InvocationParameterCopyable(value) +{ + public override string ExpectedPropertyClass => "Int64Property"; +} + +public class U8InvocationParameter(byte value) : InvocationParameterCopyable(value) +{ + public override string ExpectedPropertyClass => "Uint8Property"; +} + +public class U16InvocationParameter(ushort value) : InvocationParameterCopyable(value) +{ + public override string ExpectedPropertyClass => "Uint16Property"; +} + +public class U32InvocationParameter(uint value) : InvocationParameterCopyable(value) +{ + public override string ExpectedPropertyClass => "Uint32Property"; +} + +public class U64InvocationParameter(ulong value) : InvocationParameterCopyable(value) +{ + public override string ExpectedPropertyClass => "Uint64Property"; +} + +public class F32InvocationParameter(float value) : InvocationParameterCopyable(value) +{ + public override string ExpectedPropertyClass => "FloatProperty"; +} + +public class F64InvocationParameter(double value) : InvocationParameterCopyable(value) +{ + public override string ExpectedPropertyClass => "DoubleProperty"; +} + +public class NameInvocationParameter(FName value) : InvocationParameterCopyable(value) +{ + public override string ExpectedPropertyClass => "NameProperty"; +} + +public class StringInvocationParameter(string value, IUnrealMemory memory, IUnrealObjects objects) + : IInvocationParameter, IDisposable +{ + private IUnrealMemory _memory = memory; + private IUnrealObjects _objects = objects; + + private string InnerValue { get; set; } = value; + public object? Value => InnerValue; + + // Assume that Empty FStrings are out values. + private bool IsOutValue => InnerValue.Length == 0; + private nint StringDataAlloc = nint.Zero; + + public unsafe void ToAlloc(nint pAlloc) + { + // Zero out this memory region so Unreal doesn't try to Free this + if (IsOutValue) + { + NativeMemory.Clear((void*)pAlloc, (nuint)sizeof(FString)); + } + else + { + var pFString = _objects.CreateFString(InnerValue); + StringDataAlloc = (nint)pFString->Data.AllocatorInstance; + NativeMemory.Copy(pFString, (void*)pAlloc, (nuint)sizeof(FString)); + _memory.Free((nint)pFString); + } + } + + public unsafe void FromAlloc(nint pAlloc) + { + var pFString = (FString*)pAlloc; + // String was changed during the function invocation - FString was passed by a mutable reference + // Unreal would have already deallocated the old StringDataAlloc + if (!IsOutValue && (nint)pFString->Data.AllocatorInstance != StringDataAlloc) + StringDataAlloc = (nint)pFString->Data.AllocatorInstance; + InnerValue = ((FString*)pAlloc)->ToString(); + } + + public string ExpectedPropertyClass => "StrProperty"; + public int ExpectedSize => Marshal.SizeOf(); + + public void Dispose() + { + Disposing(); + GC.SuppressFinalize(this); + } + + ~StringInvocationParameter() => Disposing(); + + protected virtual void Disposing() + { + if (StringDataAlloc != nint.Zero) + _memory.Free(StringDataAlloc); + } +} + +public class BoolInvocationParameter(bool value) : IInvocationParameter +{ + private bool InnerValue { get; set; } = value; + public object? Value => InnerValue; + + public unsafe void ToAlloc(nint pAlloc) + => *(byte*)pAlloc = (byte)(InnerValue ? 1 : 0); + + public unsafe void FromAlloc(nint pAlloc) + => InnerValue = *(byte*)pAlloc != 0; + + public string ExpectedPropertyClass => "BoolProperty"; + public int ExpectedSize => Marshal.SizeOf(); +} + +public class UnrealMethods : IUnrealMethods +{ + + #region Function Invocation Parameters + + public IInvocationParameter CreateI8Param(sbyte Value = 0) => new I8InvocationParameter(Value); + public IInvocationParameter CreateI16Param(short Value = 0) => new I16InvocationParameter(Value); + public IInvocationParameter CreateI32Param(int Value = 0) => new I32InvocationParameter(Value); + public IInvocationParameter CreateI64Param(long Value = 0) => new I64InvocationParameter(Value); + public IInvocationParameter CreateU8Param(byte Value = 0) => new U8InvocationParameter(Value); + public IInvocationParameter CreateU16Param(ushort Value = 0) => new U16InvocationParameter(Value); + public IInvocationParameter CreateU32Param(uint Value = 0) => new U32InvocationParameter(Value); + public IInvocationParameter CreateU64Param(ulong Value = 0) => new U64InvocationParameter(Value); + public IInvocationParameter CreateF32Param(float Value = 0) => new F32InvocationParameter(Value); + public IInvocationParameter CreateF64Param(double Value = 0) => new F64InvocationParameter(Value); + public IInvocationParameter CreateNameParam(string Value) => new NameInvocationParameter(new FName(Value)); + public IInvocationParameter CreateStringParam(string Value) => new StringInvocationParameter(Value, Memory, Objects); + public IInvocationParameter CreateBoolParam(bool Value = false) => new BoolInvocationParameter(Value); + + #endregion + + #region Function Invocation Execute + + private delegate void UObject_ProcessEvent(nint Object, nint TargetFunction, nint Params); + private uint UObject_ProcessEvent_Offset; + + [Flags] + private enum ExecutionFlags + { + None = 0, + DoTypeChecking = 1 << 0, + // NoReturnType = 1 << 1, + } + + private bool ProcessEventInner(ToolkitUObject Object, string Name, + ref List Parameters, out IUFunction? Function, out nint Alloc, ExecutionFlags Flags) + where TObject : unmanaged + { + // Get type reflection for object type + Function = null; + Alloc = nint.Zero; + if (!Classes.GetClassInfoFromClass(out var Reflection)) + { + Log.Warning($"ProcessEvent: Could not retrieve reflection information for {nameof(TObject)}"); + return false; + } + Function = Reflection.GetFunction(Name); + if (Function == null) + { + Log.Warning($"ProcessEvent: Could not find function in {nameof(TObject)} named {Name}"); + return false; + } + // Create allocation + Alloc = Function.GetTotalParameterSize() switch + { + 0 => nint.Zero, var Value => Memory.Malloc(Value) + }; + // Set parameters + foreach (var (Index, Field) in Function.ChildProperties.Select((x, i) => (i, x))) + { + // Assume CPF_Parm + var Property = Factory.CreateFProperty(Field.Ptr); + // If the function returns a non-void value, it will always contain a ReturnValue property as the last property. + if (Property.PropertyFlags.HasFlag(EPropertyFlags.CPF_ReturnParm)) + { + unsafe + { + NativeMemory.Clear((void*)(Alloc + Property.Offset_Internal), (nuint)Property.ElementSize); + } + break; + } + var Parameter = Parameters[Index]; + // Type checking: Get type of current parameter, check against expected FProperty type + if (Flags.HasFlag(ExecutionFlags.DoTypeChecking)) + { + // var PropName = Property.ClassPrivate.Name; + if (Parameter.ExpectedSize != Property.ElementSize) + { + Log.Warning($"Could not invoke method {Name} on object with type {nameof(TObject)}: Type for parameter {Index} does not match."); + return false; + } + } + Parameter.ToAlloc(Alloc + Property.Offset_Internal); + } + unsafe + { + var ProcessEventWrapper = Hooks.CreateWrapper(*(nint*)(Function.VTable + UObject_ProcessEvent_Offset), out _); + ProcessEventWrapper((nint)Object.Self, Function.Ptr, Alloc); + } + return true; + } + + private IInvocationParameter? CreateReturnInvocationParameter(IFProperty Property) + => Property.ClassPrivate.Name switch + { + "Int8Property" => CreateI8Param(), + "Int16Property" => CreateI16Param(), + "IntProperty" => CreateI32Param(), + "Int64Property" => CreateI64Param(), + "UInt16Property" => CreateU16Param(), + "UInt32Property" => CreateU32Param(), + "UInt64Property" => CreateU64Param(), + "FloatProperty" => CreateF32Param(), + "DoubleProperty" => CreateF64Param(), + "NameProperty" => CreateNameParam(string.Empty), + "StrProperty" => CreateStringParam(string.Empty), + "BoolProperty" => CreateBoolParam(), + _ => null + }; + + private bool TypeCheckReturnValueParameter(IFProperty Property) where TReturnType : unmanaged + => Property.ClassPrivate.Name switch + { + "Int8Property" => typeof(TReturnType) == typeof(sbyte), + "Int16Property" => typeof(TReturnType) == typeof(short), + "IntProperty" => typeof(TReturnType) == typeof(int), + "Int64Property" => typeof(TReturnType) == typeof(long), + "UInt16Property" => typeof(TReturnType) == typeof(ushort), + "UInt32Property" => typeof(TReturnType) == typeof(uint), + "UInt64Property" => typeof(TReturnType) == typeof(ulong), + "FloatProperty" => typeof(TReturnType) == typeof(float), + "DoubleProperty" => typeof(TReturnType) == typeof(double), + "NameProperty" => typeof(TReturnType) == typeof(FName), + "StrProperty" => typeof(TReturnType) == typeof(Ptr), + "BoolProperty" => typeof(TReturnType) == typeof(bool), + _ => false + }; + + private unsafe TReturnType CastIntoReturnType(IInvocationParameter Param) where TReturnType : unmanaged + { + switch (typeof(TReturnType).Name) + { + case "FString": + var pFString = Objects.CreateFString((string)Param.Value); + var Return = *(TReturnType*)pFString; + Memory.Free((nint)pFString); + return Return; + default: + return (TReturnType)Param.Value; + } + } + + public void ProcessEvent(ToolkitUObject Object, string Name, + ref List Parameters) where TObject : unmanaged + { + if (!ProcessEventInner(Object, Name, ref Parameters, out var Function, out var Alloc, ExecutionFlags.None)) + return; + foreach (var (Index, Field) in Function.ChildProperties.Select((x, i) => (i, x))) + { + var Property = Factory.CreateFProperty(Field.Ptr); + if (Property.PropertyFlags.HasFlag(EPropertyFlags.CPF_ReferenceParm)) + Parameters[Index].FromAlloc(Alloc + Property.Offset_Internal); + } + if (Alloc != nint.Zero) + Memory.Free(Alloc); + } + + public TReturnType ProcessEvent(ToolkitUObject Object, string Name, + ref List Parameters) + where TObject : unmanaged + where TReturnType : unmanaged + { + TReturnType ReturnValue = default; + if (!ProcessEventInner(Object, Name, ref Parameters, out var Function, out var Alloc, ExecutionFlags.None)) + return ReturnValue; + foreach (var (Index, Field) in Function.ChildProperties.Select((x, i) => (i, x))) + { + var Property = Factory.CreateFProperty(Field.Ptr); + if (Property.PropertyFlags.HasFlag(EPropertyFlags.CPF_ReferenceParm)) + Parameters[Index].FromAlloc(Alloc + Property.Offset_Internal); + else if (Property.PropertyFlags.HasFlag(EPropertyFlags.CPF_ReturnParm)) + { + // if (!TypeCheckReturnValueParameter(Property)) + var Return = CreateReturnInvocationParameter(Property); + if (Return != null) + { + Return.FromAlloc(Alloc + Property.Offset_Internal); + ReturnValue = CastIntoReturnType(Return); + } + else + { + Log.Warning($"Unimplemented ProcessEvent return value parameter type {Property.ClassPrivate.Name}"); + } + break; + } + } + + if (Alloc != nint.Zero) + Memory.Free(Alloc); + return ReturnValue; + } + + #endregion + + #region Unreal Toolkit API References + + private IUnrealFactory Factory; + private IUnrealMemory Memory; + private IUnrealClasses Classes; + private IUnrealObjects Objects; + #endregion + + #region Reloaded-II API References + + private IReloadedHooks Hooks; + #endregion + + public UnrealMethods(IUnrealFactory factory, IUnrealMemory memory, IUnrealClasses classes, IUnrealObjects objects, IReloadedHooks hooks) + { + Factory = factory; + Memory = memory; + Classes = classes; + Objects = objects; + Hooks = hooks; + + Project.Inis.UsingSetting(Constants.UnrealIniId, "ProcessEvent", "UObject", + value => UObject_ProcessEvent_Offset = value); + } +} \ No newline at end of file From 387ef31853e3bd1aae6afeb9c5ae17b0ead936f9 Mon Sep 17 00:00:00 2001 From: Rirurin Date: Mon, 17 Nov 2025 10:55:12 +0800 Subject: [PATCH 06/12] Object XML TArray/UDataTable insertion, Insert new FProprety fields in correct order --- .../Types/Unreal/UE5_4_4/TArray.cs | 36 ++++++++++++------- UE.Toolkit.DumperMod/Dumper.cs | 15 +++++--- UE.Toolkit.DumperMod/ModConfig.json | 2 +- UE.Toolkit.Interfaces/IUnrealClasses.cs | 2 +- UE.Toolkit.Reloaded/Mod.cs | 2 +- .../ObjectWriters/Nodes/DataTableFieldNode.cs | 15 +++++--- .../ObjectWriters/Nodes/FieldNodeFactory.cs | 3 +- .../ObjectWriters/Nodes/TArrayFieldNode.cs | 22 +++++++++--- .../ObjectWriters/ObjectWriterService.cs | 4 +-- .../Reflection/PropertyFactory.cs | 29 ++++++++++++--- .../Reflection/UE4_27_2/PropertyFactory.cs | 16 ++++++--- .../Reflection/UE5_4_4/PropertyFactory.cs | 15 ++++++-- UE.Toolkit.Reloaded/Unreal/UnrealClasses.cs | 4 ++- 13 files changed, 120 insertions(+), 45 deletions(-) diff --git a/UE.Toolkit.Core/Types/Unreal/UE5_4_4/TArray.cs b/UE.Toolkit.Core/Types/Unreal/UE5_4_4/TArray.cs index 7495856..8065fa0 100644 --- a/UE.Toolkit.Core/Types/Unreal/UE5_4_4/TArray.cs +++ b/UE.Toolkit.Core/Types/Unreal/UE5_4_4/TArray.cs @@ -14,6 +14,26 @@ public unsafe struct TArray where T : unmanaged public int ArrayMax; } +public unsafe class TArrayListStatic +{ + internal const int DEFAULT_ARRAY_SIZE = 4; + + public static int CalculateNewArraySizeStatic(TArray* Arr) => Arr->AllocatorInstance != null ? Arr->ArrayMax * 2 : DEFAULT_ARRAY_SIZE; + + // Expose this to allow for TArray reallocation in Object XML + public static void ResizeToStatic(TArray* Arr, int newSize, int elemSize, IUnrealMemoryInternal Allocator) + { + var newAlloc = (byte*)Allocator.Malloc(elemSize * newSize); + if (Arr->AllocatorInstance != null) + { + NativeMemory.Copy(Arr->AllocatorInstance, newAlloc, (nuint)(Arr->ArrayMax * elemSize)); + Allocator.Free((nint)Arr->AllocatorInstance); + } + Arr->AllocatorInstance = newAlloc; + Arr->ArrayMax = newSize; + } +} + /// /// /// A dynamically sizable array of typed elements. Assumes that your elements can be relocated without a copy constructor. @@ -34,7 +54,7 @@ public unsafe class TArrayList : IDisposable, IList> where TTy protected bool OwnsInstance; protected bool Disposed = false; - private const int DEFAULT_ARRAY_SIZE = 4; + /// /// Wraps a TArrayList around an existing TArray created in C++ @@ -82,7 +102,7 @@ public int ArrayMax bool InBounds(int index) => index >= 0 && index < ArrayNum; bool InBoundsForInsertion(int index) => index >= 0 && index <= ArrayNum; - int CalculateNewArraySize() => (Allocation != null) ? ArrayMax * 2 : DEFAULT_ARRAY_SIZE; + int CalculateNewArraySize() => TArrayListStatic.CalculateNewArraySizeStatic((TArray*)Self); /// /// Relinquish ownership of this TArray. This is used in cases where you know that Unreal will deallocate it or it otherwise @@ -90,17 +110,7 @@ public int ArrayMax /// public void Leak() => OwnsInstance = false; - public void ResizeTo(int newSize) - { - var newAlloc = (TType*)Allocator.Malloc(sizeof(TType) * newSize); - if (Allocation != null) - { - NativeMemory.Copy(Allocation, newAlloc, (nuint)(ArrayMax * sizeof(TType))); - Allocator.Free((nint)Allocation); - } - Allocation = newAlloc; - ArrayMax = newSize; - } + public void ResizeTo(int newSize) => TArrayListStatic.ResizeToStatic((TArray*)Self, newSize, sizeof(TType), Allocator); public void Resize() => ResizeTo(CalculateNewArraySize()); diff --git a/UE.Toolkit.DumperMod/Dumper.cs b/UE.Toolkit.DumperMod/Dumper.cs index cd21969..fb76f0e 100644 --- a/UE.Toolkit.DumperMod/Dumper.cs +++ b/UE.Toolkit.DumperMod/Dumper.cs @@ -242,7 +242,7 @@ private static string GetHeaderNameForObject(IUObject obj) private static void AddHeader(StringBuilder sb) { - sb.AppendLine("/* Generated with UE Toolkit: Dumper (1.4.5) */"); + sb.AppendLine("/* Generated with UE Toolkit: Dumper (1.6.0) */"); sb.AppendLine("/* GitHub: https://github.com/RyoTune/UE.Toolkit */"); sb.AppendLine("/* Author: RyoTune */"); sb.AppendLine("/* Special thanks to UE4SS team and Rirurin */"); @@ -277,6 +277,7 @@ private void GenerateObjectDefinition(IUClass uclass) // TODO: Flag stuff? var super = uclass.GetSuperClass(); + var superSize = super?.PropertiesSize ?? 0; var superName = super?.NamePrivate.ToString(); //var superNativeName = super != null ? GetNativeClassName(super) : "UObjectBaseUtility"; @@ -284,7 +285,7 @@ private void GenerateObjectDefinition(IUClass uclass) // TODO: Generate delegates. - var properties = ResolveProperties(uclass.PropertyLink); + var properties = ResolveProperties(uclass.PropertyLink, superSize); // TODO: Generate functions. @@ -301,10 +302,11 @@ private void GenerateStructDefinition(IUScriptStruct scriptStruct) var size = scriptStruct.PropertiesSize; var alignment = scriptStruct.MinAlignment; var super = scriptStruct.SuperStruct; + var superSize = super?.PropertiesSize ?? 0; var superName = super?.NamePrivate.ToString(); //var superNativeName = super != null ? GetNativeStructName((UScriptStruct*)super) : null; - var props = ResolveProperties(scriptStruct.PropertyLink); + var props = ResolveProperties(scriptStruct.PropertyLink, superSize); _UStructDefinitions[structName] = new(structName, structNativeName, size, alignment, props, superName); Log.Debug($"UScriptStruct: {structNativeName}"); @@ -372,11 +374,16 @@ private void GenerateEnumDefinition(IUEnum uenum, string? knownType = null) _UEnumDefinitions[name] = new(name, underlyingType, entries); } - private PropertyDefinition[] ResolveProperties(IEnumerable propLink) + private PropertyDefinition[] ResolveProperties(IEnumerable propLink, int SuperStructEnd) { var props = new List(); foreach (var prop in propLink) { + // Stop when we find a property with an offset lower than the size of the super struct. + // PropertyLink contains a list of fields declared by object in ascending order, then from it's super + // object and so on. + if (prop.Offset_Internal < SuperStructEnd) + break; var newProp = ResolveProperty(prop); var numSameName = props.Count(x => x.Name.StartsWith(newProp.Name)); // Compare against sanitized name, since that's what props are using. diff --git a/UE.Toolkit.DumperMod/ModConfig.json b/UE.Toolkit.DumperMod/ModConfig.json index d87b684..acf56a9 100644 --- a/UE.Toolkit.DumperMod/ModConfig.json +++ b/UE.Toolkit.DumperMod/ModConfig.json @@ -2,7 +2,7 @@ "ModId": "UE.Toolkit.DumperMod", "ModName": "UE Toolkit: Dumper", "ModAuthor": "RyoTune", - "ModVersion": "1.4.5", + "ModVersion": "1.6.0", "ModDescription": "Unreal Engine object dumper to C# types.", "ModDll": "UE.Toolkit.DumperMod.dll", "ModIcon": "Preview.png", diff --git a/UE.Toolkit.Interfaces/IUnrealClasses.cs b/UE.Toolkit.Interfaces/IUnrealClasses.cs index 65211e5..ba1f90e 100644 --- a/UE.Toolkit.Interfaces/IUnrealClasses.cs +++ b/UE.Toolkit.Interfaces/IUnrealClasses.cs @@ -261,7 +261,7 @@ public bool AddClassProperty(string Name, int Offset, out IFCla public bool AddTextProperty(string Name, int Offset, out IFProperty? Out) where TObject : unmanaged; /// - /// Add a Array (TArray) containing elememts of the property defined in Inner to the object's class with the + /// Add a Array (TArray) containing elememts of the property defined in Inner to the object's class with the /// specified name and offset. This will make the field exposable to blueprints and Object XML. /// /// Name of the new field. diff --git a/UE.Toolkit.Reloaded/Mod.cs b/UE.Toolkit.Reloaded/Mod.cs index 46b5feb..b2a2893 100644 --- a/UE.Toolkit.Reloaded/Mod.cs +++ b/UE.Toolkit.Reloaded/Mod.cs @@ -68,7 +68,7 @@ public Mod(ModContext context) _tables = new(); _memory = GameConfig.Instance.Memory; _typeRegistry = new(); - _writer = new(_typeRegistry, _objects, _tables); + _writer = new(_typeRegistry, _objects, _tables, _memory); _toolkit = new(_writer); _strings = new(); _address = new(); diff --git a/UE.Toolkit.Reloaded/ObjectWriters/Nodes/DataTableFieldNode.cs b/UE.Toolkit.Reloaded/ObjectWriters/Nodes/DataTableFieldNode.cs index 31f27df..e9cf183 100644 --- a/UE.Toolkit.Reloaded/ObjectWriters/Nodes/DataTableFieldNode.cs +++ b/UE.Toolkit.Reloaded/ObjectWriters/Nodes/DataTableFieldNode.cs @@ -1,5 +1,7 @@ using System.Diagnostics.CodeAnalysis; +using System.Runtime.InteropServices; using System.Xml; +using UE.Toolkit.Core.Types; using UE.Toolkit.Core.Types.Unreal.UE5_4_4; using UE.Toolkit.Interfaces; @@ -11,9 +13,11 @@ public unsafe void ConsumeNode(XmlReader reader) { // DataTable item type. if (!TryGetRowType(reader, out var itemType)) return; + var itemSize = Marshal.SizeOf(itemType); // DataTable map type is always FName + Pointer, can just use UObject. - var tempTable = new ToolkitDataTable((UDataTable*)fieldPtr); + // var tempTable = new ToolkitDataTable((UDataTable*)fieldPtr); + var tempTable = new UDataTableManaged((UDataTable*)fieldPtr, nodeFactory.Memory); // Get any item nodes. using var subReader = reader.ReadSubtree(); @@ -30,8 +34,8 @@ public unsafe void ConsumeNode(XmlReader reader) Log.Error($"{nameof(DataTableFieldNode)} || '{WriterConstants.ItemTag}' in field '{fieldName}' is missing an ID."); break; } - - if (tempTable.TryGetValue(id, out var row)) + + if (tempTable.TryGetValue(new(id), out var row)) { var rowValuePtr = (nint)row.Value; if (nodeFactory.TryCreate($"{fieldName} (ID: {id})", rowValuePtr, itemType, out var itemNode)) @@ -44,8 +48,9 @@ public unsafe void ConsumeNode(XmlReader reader) } else { - - Log.Error($"{nameof(DataTableFieldNode)} || Failed to find row with ID '{id}' in '{fieldName}'.");; + tempTable.Add(new (id), new Ptr((UObjectBase*)nodeFactory.Memory.MallocZeroed(itemSize))); + Log.Debug($"{nameof(DataTableFieldNode)} || Added row with ID '{id}' into '{fieldName}'."); + // Log.Error($"{nameof(DataTableFieldNode)} || Failed to find row with ID '{id}' in '{fieldName}'."); break; } } diff --git a/UE.Toolkit.Reloaded/ObjectWriters/Nodes/FieldNodeFactory.cs b/UE.Toolkit.Reloaded/ObjectWriters/Nodes/FieldNodeFactory.cs index 52ff3eb..0c59427 100644 --- a/UE.Toolkit.Reloaded/ObjectWriters/Nodes/FieldNodeFactory.cs +++ b/UE.Toolkit.Reloaded/ObjectWriters/Nodes/FieldNodeFactory.cs @@ -5,9 +5,10 @@ namespace UE.Toolkit.Reloaded.ObjectWriters.Nodes; -public class FieldNodeFactory(ITypeRegistry typeReg, IObjectCreator objCreator) +public class FieldNodeFactory(ITypeRegistry typeReg, IObjectCreator objCreator, IUnrealMemoryInternal memory) { public ITypeRegistry TypeRegistry { get; } = typeReg; + public IUnrealMemoryInternal Memory { get; } = memory; public bool TryCreate(string fieldName, nint fieldPtr, Type fieldType, [NotNullWhen(true)]out IFieldNode? node) { diff --git a/UE.Toolkit.Reloaded/ObjectWriters/Nodes/TArrayFieldNode.cs b/UE.Toolkit.Reloaded/ObjectWriters/Nodes/TArrayFieldNode.cs index 6ba4f4f..06f6b55 100644 --- a/UE.Toolkit.Reloaded/ObjectWriters/Nodes/TArrayFieldNode.cs +++ b/UE.Toolkit.Reloaded/ObjectWriters/Nodes/TArrayFieldNode.cs @@ -37,11 +37,25 @@ public unsafe void ConsumeNode(XmlReader reader) break; } - itemIdx -= 1; // We're doing 1 indexing because normal people can't handle 0... - if (itemIdx < 0 || itemIdx > tempArray->ArrayNum) + // Reserve -1 as a value for pushing a new value into the TArray. Choose a better method perhaps? + if (itemIdx != -1) { - Log.Warning($"{nameof(TArrayFieldNode)} || ID is either less than 1 or more than item count: {id} || Total: {tempArray->ArrayNum}"); - break; + itemIdx -= 1; // We're doing 1 indexing because normal people can't handle 0... + if (itemIdx < 0 || itemIdx > tempArray->ArrayNum) + { + Log.Warning($"{nameof(TArrayFieldNode)} || ID is either less than 1 or more than item count: {id} || Total: {tempArray->ArrayNum}"); + break; + } + } else + { + Log.Verbose($"{nameof(TArrayFieldNode)} @ 0x{(nint)tempArray:x} || Old Size : {tempArray->ArrayNum} || Old Capacity: {tempArray->ArrayMax}"); + if (tempArray->ArrayNum == tempArray->ArrayMax) + { + TArrayListStatic.ResizeToStatic(tempArray, + TArrayListStatic.CalculateNewArraySizeStatic(tempArray), itemSize, nodeFactory.Memory); + } + itemIdx = tempArray->ArrayNum; + tempArray->ArrayNum++; } var itemPtr = itemIdx * itemSize + (nint)tempArray->AllocatorInstance; diff --git a/UE.Toolkit.Reloaded/ObjectWriters/ObjectWriterService.cs b/UE.Toolkit.Reloaded/ObjectWriters/ObjectWriterService.cs index d1dc87c..c34f3b7 100644 --- a/UE.Toolkit.Reloaded/ObjectWriters/ObjectWriterService.cs +++ b/UE.Toolkit.Reloaded/ObjectWriters/ObjectWriterService.cs @@ -8,11 +8,11 @@ namespace UE.Toolkit.Reloaded.ObjectWriters; -public class ObjectWriterService(ITypeRegistry typeReg, IUnrealObjects uobjs, IDataTables dt) +public class ObjectWriterService(ITypeRegistry typeReg, IUnrealObjects uobjs, IDataTables dt, IUnrealMemory memory) { private readonly Dictionary _types = []; private readonly List _objWriters = []; - private readonly FieldNodeFactory _nodeFactory = new(typeReg, uobjs); + private readonly FieldNodeFactory _nodeFactory = new(typeReg, uobjs, memory); public void AddPath(string path) { diff --git a/UE.Toolkit.Reloaded/Reflection/PropertyFactory.cs b/UE.Toolkit.Reloaded/Reflection/PropertyFactory.cs index d0055b2..76d1b57 100644 --- a/UE.Toolkit.Reloaded/Reflection/PropertyFactory.cs +++ b/UE.Toolkit.Reloaded/Reflection/PropertyFactory.cs @@ -86,11 +86,10 @@ protected abstract void SetStringPropertyFields(IFProperty Property, int Offs protected abstract void SetTextPropertyFields(IFProperty Property, int Offset, PropertyVisibility Visibility) where T : unmanaged; - private bool CreatePropertyInner(out IFProperty? NewProperty, + private bool CreatePropertyInner(out IFProperty? NewProperty, string Name, int Offset, string PropertyName, PropertyVisibility Visibility, Action Callback) where TOwner : unmanaged - where TField : unmanaged where TProperty : unmanaged { NewProperty = null; @@ -109,7 +108,7 @@ protected bool CreateCopyPropertyInner(out IFProperty where TOwner : unmanaged where TField : unmanaged where TProperty : unmanaged - => CreatePropertyInner(out NewProperty, Name, + => CreatePropertyInner(out NewProperty, Name, Offset, PropertyName, Visibility, SetCopyPropertyFields); protected bool CreateStringPropertyInner(out IFProperty? NewProperty, @@ -117,7 +116,7 @@ protected bool CreateStringPropertyInner(out IFProper where TOwner : unmanaged where TField : unmanaged where TProperty : unmanaged - => CreatePropertyInner(out NewProperty, Name, + => CreatePropertyInner(out NewProperty, Name, Offset, PropertyName, Visibility, SetStringPropertyFields); protected bool CreateTextPropertyInner(out IFProperty? NewProperty, @@ -125,7 +124,7 @@ protected bool CreateTextPropertyInner(out IFProperty where TOwner : unmanaged where TField : unmanaged where TProperty : unmanaged - => CreatePropertyInner(out NewProperty, Name, + => CreatePropertyInner(out NewProperty, Name, Offset, PropertyName, Visibility, SetTextPropertyFields); protected abstract void SetBoolPropertyFields(IFBoolProperty Property, BooleanMask Mask); @@ -141,6 +140,26 @@ protected bool CreateBoolPropertyInner(out IFBoolProperty? NewProperty, SetBoolPropertyFields(NewProperty, Mask); return true; } + + protected IFProperty GetPreviousProperty(IFProperty Property, IUClass Reflect) + { + IFProperty? TargetProp = null; + foreach (var Prop in Reflect.PropertyLink) + { + if (!Prop.PropertyLinkNext.Any()) + { + // We're the last element in the chain + TargetProp = Prop; + } + else if (Prop.PropertyLinkNext.First().Offset_Internal > Property.Offset_Internal) + { + // The next element will have a larger offset than our new field, so insert before them + TargetProp = Prop; + break; + } + } + return TargetProp!; + } #endregion diff --git a/UE.Toolkit.Reloaded/Reflection/UE4_27_2/PropertyFactory.cs b/UE.Toolkit.Reloaded/Reflection/UE4_27_2/PropertyFactory.cs index 686094a..e593680 100644 --- a/UE.Toolkit.Reloaded/Reflection/UE4_27_2/PropertyFactory.cs +++ b/UE.Toolkit.Reloaded/Reflection/UE4_27_2/PropertyFactory.cs @@ -32,13 +32,21 @@ protected override unsafe void LinkToPropertyList(IFProperty Property, IUClass R if (((UStruct*)pClass)->prop_link == null) { ((UStruct*)pClass)->prop_link = pProperty; + ((UStruct*)pClass)->child_properties = (FField*)pProperty; } else { - var LastProp = Reflect.PropertyLink.Last(); - var LastFProp = (FProperty*)LastProp.Ptr; - LastFProp->prop_link_next = pProperty; - ((FField*)LastFProp)->next = (FField*)pProperty; + var TargetProp = GetPreviousProperty(Property, Reflect); + var NextProp = TargetProp.PropertyLinkNext.Any() ? TargetProp.PropertyLinkNext.First() : null; + var pTargetProp = (FProperty*)TargetProp.Ptr; + pTargetProp->prop_link_next = pProperty; + ((FField*)pTargetProp)->next = (FField*)pProperty; + if (NextProp != null) + { + var pNextProp = (FProperty*)NextProp.Ptr; + pProperty->prop_link_next = pNextProp; + ((FField*)pProperty)->next = (FField*)pNextProp; + } } } diff --git a/UE.Toolkit.Reloaded/Reflection/UE5_4_4/PropertyFactory.cs b/UE.Toolkit.Reloaded/Reflection/UE5_4_4/PropertyFactory.cs index 761a624..39629af 100644 --- a/UE.Toolkit.Reloaded/Reflection/UE5_4_4/PropertyFactory.cs +++ b/UE.Toolkit.Reloaded/Reflection/UE5_4_4/PropertyFactory.cs @@ -24,12 +24,21 @@ protected override unsafe void LinkToPropertyList(IFProperty Property, IUClass R if (((UStruct*)pClass)->PropertyLink == null) { ((UStruct*)pClass)->PropertyLink = pProperty; + ((UStruct*)pClass)->ChildProperties = (FField*)pProperty; } else { - var LastProp = Reflect.PropertyLink.Last(); - var LastFProp = (FProperty*)LastProp.Ptr; - LastFProp->PropertyLinkNext = pProperty; + var TargetProp = GetPreviousProperty(Property, Reflect); + var NextProp = TargetProp.PropertyLinkNext.Any() ? TargetProp.PropertyLinkNext.First() : null; + var pTargetProp = (FProperty*)TargetProp.Ptr; + pTargetProp->PropertyLinkNext = pProperty; + ((FField*)pTargetProp)->Next = (FField*)pProperty; + if (NextProp != null) + { + var pNextProp = (FProperty*)NextProp.Ptr; + pProperty->PropertyLinkNext = pNextProp; + ((FField*)pProperty)->Next = (FField*)pNextProp; + } } } diff --git a/UE.Toolkit.Reloaded/Unreal/UnrealClasses.cs b/UE.Toolkit.Reloaded/Unreal/UnrealClasses.cs index 8265050..fcc558e 100644 --- a/UE.Toolkit.Reloaded/Unreal/UnrealClasses.cs +++ b/UE.Toolkit.Reloaded/Unreal/UnrealClasses.cs @@ -410,7 +410,9 @@ public bool AddArrayProperty(string Name, int Offset, IFProperty Inner, public bool CreateScriptStruct(string Name, int Size, List Fields, out IUScriptStruct? Out) { - TypeFactory.CreateStructParam(Name, Size, Fields, out var Param); + Out = null; + if (!TypeFactory.CreateStructParam(Name, Size, Fields, out var Param)) + return false; var pScriptStruct = nint.Zero; ConstructUScriptStructImpl((nint)(&pScriptStruct), Param.Ptr); Out = Factory.CreateUScriptStruct(pScriptStruct); From b23ac35bd4c5264ecc12e9f1dbaec5623bf874b8 Mon Sep 17 00:00:00 2001 From: Rirurin Date: Mon, 24 Nov 2025 18:14:34 +0800 Subject: [PATCH 07/12] Fix setting bool type in Object XML, start work on TMap editing in Object XML, add GetCurrentWorld from engine state --- .gitignore | 3 +- UE.Toolkit.Core/Common/ToolkitUtils.cs | 3 +- .../Unreal/Factories/BaseUnrealFactory.cs | 3 + .../Types/Unreal/Factories/IUnrealFactory.cs | 3 + .../Factories/Interfaces/IFWorldContext.cs | 9 + .../Unreal/Factories/Interfaces/IUEngine.cs | 6 + .../Unreal/Factories/Interfaces/IUObject.cs | 2 + .../Factories/UE4_27_2/UnrealFactory.cs | 53 +++ .../Unreal/Factories/UE5_4_4/UnrealFactory.cs | 63 +++- .../Types/Unreal/UE4_27_2/FWorldContext.cs | 11 + .../Types/Unreal/UE4_27_2/UEngine.cs | 10 + .../Types/Unreal/UE4_27_2/UWorld.cs | 8 + .../Types/Unreal/UE5_4_4/FWorldContext.cs | 38 ++ UE.Toolkit.Core/Types/Unreal/UE5_4_4/TMap.cs | 350 ++++++++++++++++-- .../Types/Unreal/UE5_4_4/UEngine.cs | 9 + .../Types/Unreal/UE5_4_4/UWorld.cs | 8 + UE.Toolkit.DumperMod/Dumper.cs | 40 +- UE.Toolkit.Interfaces/IUnrealState.cs | 15 + UE.Toolkit.Reloaded/Mod.cs | 6 +- .../ObjectWriters/Nodes/DataTableFieldNode.cs | 1 - .../ObjectWriters/Nodes/FieldNodeFactory.cs | 19 + .../ObjectWriters/Nodes/StructFieldNode.cs | 32 +- .../ObjectWriters/Nodes/TMapFieldNode.cs | 66 ++++ .../Writers/PrimitiveFieldWriter.cs | 4 + .../Project/UE.Toolkit.Reloaded/p3r/scans.ini | 5 +- .../UE.Toolkit.Reloaded/p3r/unreal.ini | 8 +- .../Project/UE.Toolkit.Reloaded/scans.ini | 5 +- .../Project/UE.Toolkit.Reloaded/unreal.ini | 8 +- UE.Toolkit.Reloaded/Unreal/UnrealState.cs | 34 ++ 29 files changed, 763 insertions(+), 59 deletions(-) create mode 100644 UE.Toolkit.Core/Types/Unreal/Factories/Interfaces/IFWorldContext.cs create mode 100644 UE.Toolkit.Core/Types/Unreal/Factories/Interfaces/IUEngine.cs create mode 100644 UE.Toolkit.Core/Types/Unreal/UE4_27_2/FWorldContext.cs create mode 100644 UE.Toolkit.Core/Types/Unreal/UE4_27_2/UEngine.cs create mode 100644 UE.Toolkit.Core/Types/Unreal/UE4_27_2/UWorld.cs create mode 100644 UE.Toolkit.Core/Types/Unreal/UE5_4_4/FWorldContext.cs create mode 100644 UE.Toolkit.Core/Types/Unreal/UE5_4_4/UEngine.cs create mode 100644 UE.Toolkit.Core/Types/Unreal/UE5_4_4/UWorld.cs create mode 100644 UE.Toolkit.Interfaces/IUnrealState.cs create mode 100644 UE.Toolkit.Reloaded/ObjectWriters/Nodes/TMapFieldNode.cs create mode 100644 UE.Toolkit.Reloaded/Unreal/UnrealState.cs diff --git a/.gitignore b/.gitignore index 76e50b3..d9356ea 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,5 @@ riderModule.iml *.sln.DotSettings.user # Jetbrains IDE -.idea/* \ No newline at end of file +.idea/* +global.json \ No newline at end of file diff --git a/UE.Toolkit.Core/Common/ToolkitUtils.cs b/UE.Toolkit.Core/Common/ToolkitUtils.cs index 64c19f7..d6fbb99 100644 --- a/UE.Toolkit.Core/Common/ToolkitUtils.cs +++ b/UE.Toolkit.Core/Common/ToolkitUtils.cs @@ -1,3 +1,4 @@ +using System.Collections.Concurrent; using UE.Toolkit.Core.Types.Unreal.Factories.Interfaces; using UE.Toolkit.Core.Types.Unreal.UE5_4_4; @@ -5,7 +6,7 @@ namespace UE.Toolkit.Core.Common; public static unsafe class ToolkitUtils { - private static readonly Dictionary PrivateToNativeNameMap = []; + private static readonly ConcurrentDictionary PrivateToNativeNameMap = []; public static nint GetGlobalAddress(nint address) => *(int*)address + address + 4; diff --git a/UE.Toolkit.Core/Types/Unreal/Factories/BaseUnrealFactory.cs b/UE.Toolkit.Core/Types/Unreal/Factories/BaseUnrealFactory.cs index 674642c..75e5c10 100644 --- a/UE.Toolkit.Core/Types/Unreal/Factories/BaseUnrealFactory.cs +++ b/UE.Toolkit.Core/Types/Unreal/Factories/BaseUnrealFactory.cs @@ -87,4 +87,7 @@ public T Cast(IPtr obj) public abstract IFStructParams CreateFStructParams(nint ptr); public abstract IFPropertyParams CreateFPropertyParams(nint ptr); public abstract IFGenericPropertyParams CreateFGenericPropertyParams(nint ptr); + + public abstract IFWorldContext CreateFWorldContext(nint ptr); + public abstract IUEngine CreateUEngine(nint ptr); } \ No newline at end of file diff --git a/UE.Toolkit.Core/Types/Unreal/Factories/IUnrealFactory.cs b/UE.Toolkit.Core/Types/Unreal/Factories/IUnrealFactory.cs index 43099fa..8640be8 100644 --- a/UE.Toolkit.Core/Types/Unreal/Factories/IUnrealFactory.cs +++ b/UE.Toolkit.Core/Types/Unreal/Factories/IUnrealFactory.cs @@ -43,4 +43,7 @@ public interface IUnrealFactory IFStructParams CreateFStructParams(nint ptr); IFPropertyParams CreateFPropertyParams(nint ptr); IFGenericPropertyParams CreateFGenericPropertyParams(nint ptr); + + IFWorldContext CreateFWorldContext(nint ptr); + IUEngine CreateUEngine(nint ptr); } \ No newline at end of file diff --git a/UE.Toolkit.Core/Types/Unreal/Factories/Interfaces/IFWorldContext.cs b/UE.Toolkit.Core/Types/Unreal/Factories/Interfaces/IFWorldContext.cs new file mode 100644 index 0000000..8a1afd8 --- /dev/null +++ b/UE.Toolkit.Core/Types/Unreal/Factories/Interfaces/IFWorldContext.cs @@ -0,0 +1,9 @@ +using UE.Toolkit.Core.Types.Unreal.UE5_4_4; + +namespace UE.Toolkit.Core.Types.Unreal.Factories.Interfaces; + +public interface IFWorldContext : IPtr +{ + public WorldType GetWorldType(); + public nint GetWorld(); +} \ No newline at end of file diff --git a/UE.Toolkit.Core/Types/Unreal/Factories/Interfaces/IUEngine.cs b/UE.Toolkit.Core/Types/Unreal/Factories/Interfaces/IUEngine.cs new file mode 100644 index 0000000..afc1369 --- /dev/null +++ b/UE.Toolkit.Core/Types/Unreal/Factories/Interfaces/IUEngine.cs @@ -0,0 +1,6 @@ +namespace UE.Toolkit.Core.Types.Unreal.Factories.Interfaces; + +public interface IUEngine : IUObject +{ + public IEnumerable GetWorldList(); +} \ No newline at end of file diff --git a/UE.Toolkit.Core/Types/Unreal/Factories/Interfaces/IUObject.cs b/UE.Toolkit.Core/Types/Unreal/Factories/Interfaces/IUObject.cs index 3519b28..5b42dcd 100644 --- a/UE.Toolkit.Core/Types/Unreal/Factories/Interfaces/IUObject.cs +++ b/UE.Toolkit.Core/Types/Unreal/Factories/Interfaces/IUObject.cs @@ -27,4 +27,6 @@ public interface IUObject : IPtr IUObject GetOutermost(); string GetNativeName(); + + // IUObject GetWorld(); } \ No newline at end of file diff --git a/UE.Toolkit.Core/Types/Unreal/Factories/UE4_27_2/UnrealFactory.cs b/UE.Toolkit.Core/Types/Unreal/Factories/UE4_27_2/UnrealFactory.cs index a1b725a..c51b71c 100644 --- a/UE.Toolkit.Core/Types/Unreal/Factories/UE4_27_2/UnrealFactory.cs +++ b/UE.Toolkit.Core/Types/Unreal/Factories/UE4_27_2/UnrealFactory.cs @@ -3,6 +3,7 @@ using System.Runtime.InteropServices; using UE.Toolkit.Core.Common; using UE.Toolkit.Core.Types.Unreal.Factories.Interfaces; +using UE.Toolkit.Core.Types.Unreal.UE5_4_4; using EFunctionFlags = UE.Toolkit.Core.Types.Unreal.UE5_4_4.EFunctionFlags; using EPropertyFlags = UE.Toolkit.Core.Types.Unreal.UE5_4_4.EPropertyFlags; using EPropertyGenFlags = UE.Toolkit.Core.Types.Unreal.UE5_4_4.EPropertyGenFlags; @@ -29,7 +30,9 @@ using FSetProperty = UE.Toolkit.Core.Types.Unreal.UE4_27_2.FSetProperty; using FStructParams = UE.Toolkit.Core.Types.Unreal.UE4_27_2.FStructParams; using FStructProperty = UE.Toolkit.Core.Types.Unreal.UE4_27_2.FStructProperty; +using FWorldContext = UE.Toolkit.Core.Types.Unreal.UE4_27_2.FWorldContext; using UClass = UE.Toolkit.Core.Types.Unreal.UE4_27_2.UClass; +using UEngine = UE.Toolkit.Core.Types.Unreal.UE4_27_2.UEngine; using UEnum = UE.Toolkit.Core.Types.Unreal.UE4_27_2.UEnum; using UField = UE.Toolkit.Core.Types.Unreal.UE4_27_2.UField; using UFunction = UE.Toolkit.Core.Types.Unreal.UE4_27_2.UFunction; @@ -127,6 +130,9 @@ public override nint SizeOf() public override IFPropertyParams CreateFPropertyParams(nint ptr) => new FPropertyParamsUE4_27_2(ptr, this); public override IFGenericPropertyParams CreateFGenericPropertyParams(nint ptr) => new FGenericPropertyParamsUE4_27_2(ptr, this); + + public override IFWorldContext CreateFWorldContext(nint ptr) => new FWorldContextUE4_27_2(ptr, this); + public override IUEngine CreateUEngine(nint ptr) => new UEngineUE4_27_2(ptr, this); } public unsafe class FSetPropertyUE4_27_2(nint ptr, IUnrealFactory factory) @@ -601,4 +607,51 @@ public unsafe class FGenericPropertyParamsUE4_27_2(nint ptr, IUnrealFactory fact public int ArrayDim => _self->Super.ArrayDim; public int Offset => _self->Super.Offset; +} + +public unsafe class FWorldContextUE4_27_2(nint ptr, IUnrealFactory factory) : IFWorldContext +{ + protected readonly IUnrealFactory _factory = factory; + private readonly FWorldContext* _self = (FWorldContext*)ptr; + public nint Ptr => (nint)_self; + public WorldType GetWorldType() => _self->WorldType; + public nint GetWorld() => (nint)_self->ThisCurrentWorld; +} + +public unsafe class FWorldContextEnumerator(UEngineUE4_27_2 owner, IUnrealFactory factory) + : IEnumerator, IEnumerable +{ + private int CurrentIndex = -1; + + #region impl IEnumerator + + public bool MoveNext() => ++CurrentIndex < owner.GetWorldListInner()->ArrayNum; + + public void Reset() => CurrentIndex = -1; + + public IFWorldContext Current => factory.CreateFWorldContext((nint)owner.GetWorldListInner()->AllocatorInstance[CurrentIndex].Value); + + object? IEnumerator.Current => Current; + + public void Dispose() {} + + #endregion + + #region impl IEnumerable + + public IEnumerator GetEnumerator() => this; + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + #endregion +} + +public unsafe class UEngineUE4_27_2(nint ptr, IUnrealFactory factory) + : UObjectUE4_27_2(ptr, factory), IUEngine +{ + private readonly UEngine* _self = (UEngine*)ptr; + + internal TArray>* GetWorldListInner() => &_self->WorldList; + + public IEnumerable GetWorldList() => new FWorldContextEnumerator(this, factory); } \ No newline at end of file diff --git a/UE.Toolkit.Core/Types/Unreal/Factories/UE5_4_4/UnrealFactory.cs b/UE.Toolkit.Core/Types/Unreal/Factories/UE5_4_4/UnrealFactory.cs index e704500..89c6c96 100644 --- a/UE.Toolkit.Core/Types/Unreal/Factories/UE5_4_4/UnrealFactory.cs +++ b/UE.Toolkit.Core/Types/Unreal/Factories/UE5_4_4/UnrealFactory.cs @@ -65,9 +65,11 @@ public override nint SizeOf() public override IUFunction CreateUFunction(nint ptr) => new UFunction_UE5_4_4(ptr, this); public override IFFieldClass CreateFFieldClass(nint ptr) => new FFieldClass_UE5_4_4(ptr, this); public override IFField CreateFField(nint ptr) => new FField_UE5_4_4(ptr, this); - public override IFStructParams CreateFStructParams(nint ptr) => new FStructParamsUE5_4_4(ptr, this); - public override IFPropertyParams CreateFPropertyParams(nint ptr) => new FPropertyParamsUE5_4_4(ptr, this); - public override IFGenericPropertyParams CreateFGenericPropertyParams(nint ptr) => new FGenericPropertyParamsUE5_4_4(ptr, this); + public override IFStructParams CreateFStructParams(nint ptr) => new FStructParams_UE5_4_4(ptr, this); + public override IFPropertyParams CreateFPropertyParams(nint ptr) => new FPropertyParams_UE5_4_4(ptr, this); + public override IFGenericPropertyParams CreateFGenericPropertyParams(nint ptr) => new FGenericPropertyParams_UE5_4_4(ptr, this); + public override IFWorldContext CreateFWorldContext(nint ptr) => new FWorldContext_UE5_4_4(ptr, this); + public override IUEngine CreateUEngine(nint ptr) => new UEngine_UE5_4_4(ptr, this); } public unsafe class FOptionalProperty_UE5_4_4(nint ptr, IUnrealFactory factory) @@ -529,7 +531,7 @@ public void Dispose() {} #endregion } -public unsafe class FStructParamsUE5_4_4(nint ptr, IUnrealFactory factory) : IFStructParams +public unsafe class FStructParams_UE5_4_4(nint ptr, IUnrealFactory factory) : IFStructParams { private readonly FStructParams* _self = (FStructParams*)ptr; protected readonly IUnrealFactory _factory = factory; @@ -554,7 +556,7 @@ public unsafe class FStructParamsUE5_4_4(nint ptr, IUnrealFactory factory) : IFS public IEnumerable Properties => new FPropertyParamEnumerator(this); } -public unsafe class FPropertyParamsUE5_4_4(nint ptr, IUnrealFactory factory) : IFPropertyParams +public unsafe class FPropertyParams_UE5_4_4(nint ptr, IUnrealFactory factory) : IFPropertyParams { private readonly FPropertyParamsBase* _self = (FPropertyParamsBase*)ptr; protected readonly IUnrealFactory _factory = factory; @@ -566,11 +568,58 @@ public unsafe class FPropertyParamsUE5_4_4(nint ptr, IUnrealFactory factory) : I public EObjectFlags ObjectFlags => _self->ObjectFlags; } -public unsafe class FGenericPropertyParamsUE5_4_4(nint ptr, IUnrealFactory factory) - : FPropertyParamsUE5_4_4(ptr, factory), IFGenericPropertyParams +public unsafe class FGenericPropertyParams_UE5_4_4(nint ptr, IUnrealFactory factory) + : FPropertyParams_UE5_4_4(ptr, factory), IFGenericPropertyParams { private readonly FGenericPropertyParams* _self = (FGenericPropertyParams*)ptr; public int ArrayDim => _self->Super.ArrayDim; public int Offset => _self->Super.Offset; +} + +public unsafe class FWorldContext_UE5_4_4(nint ptr, IUnrealFactory factory) : IFWorldContext +{ + protected readonly IUnrealFactory _factory = factory; + private readonly FWorldContext* _self = (FWorldContext*)ptr; + public nint Ptr => (nint)_self; + public WorldType GetWorldType() => _self->WorldType; + public nint GetWorld() => (nint)_self->ThisCurrentWorld; +} + +public unsafe class FWorldContextEnumerator(UEngine_UE5_4_4 owner, IUnrealFactory factory) + : IEnumerator, IEnumerable +{ + private int CurrentIndex = -1; + + #region impl IEnumerator + + public bool MoveNext() => ++CurrentIndex < owner.GetWorldListInner()->ArrayNum; + + public void Reset() => CurrentIndex = -1; + + public IFWorldContext Current => factory.CreateFWorldContext((nint)owner.GetWorldListInner()->AllocatorInstance[CurrentIndex].Value); + + object? IEnumerator.Current => Current; + + public void Dispose() {} + + #endregion + + #region impl IEnumerable + + public IEnumerator GetEnumerator() => this; + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + #endregion +} + +public unsafe class UEngine_UE5_4_4(nint ptr, IUnrealFactory factory) + : UObject_UE5_4_4(ptr, factory), IUEngine +{ + private readonly UEngine* _self = (UEngine*)ptr; + + internal TArray>* GetWorldListInner() => &_self->WorldList; + + public IEnumerable GetWorldList() => new FWorldContextEnumerator(this, factory); } \ No newline at end of file diff --git a/UE.Toolkit.Core/Types/Unreal/UE4_27_2/FWorldContext.cs b/UE.Toolkit.Core/Types/Unreal/UE4_27_2/FWorldContext.cs new file mode 100644 index 0000000..4c71217 --- /dev/null +++ b/UE.Toolkit.Core/Types/Unreal/UE4_27_2/FWorldContext.cs @@ -0,0 +1,11 @@ +using System.Runtime.InteropServices; +using WorldType = UE.Toolkit.Core.Types.Unreal.UE5_4_4.WorldType; + +namespace UE.Toolkit.Core.Types.Unreal.UE4_27_2; + +[StructLayout(LayoutKind.Explicit, Size = 0x288)] +public unsafe struct FWorldContext +{ + [FieldOffset(0x10a)] public WorldType WorldType; + [FieldOffset(0x280)] public UWorld* ThisCurrentWorld; +} \ No newline at end of file diff --git a/UE.Toolkit.Core/Types/Unreal/UE4_27_2/UEngine.cs b/UE.Toolkit.Core/Types/Unreal/UE4_27_2/UEngine.cs new file mode 100644 index 0000000..0b6eba9 --- /dev/null +++ b/UE.Toolkit.Core/Types/Unreal/UE4_27_2/UEngine.cs @@ -0,0 +1,10 @@ +using System.Runtime.InteropServices; +using UE.Toolkit.Core.Types.Unreal.UE5_4_4; + +namespace UE.Toolkit.Core.Types.Unreal.UE4_27_2; + +[StructLayout(LayoutKind.Explicit, Size = 0xd20)] +public struct UEngine +{ + [FieldOffset(0xc38)] public TArray> WorldList; +} \ No newline at end of file diff --git a/UE.Toolkit.Core/Types/Unreal/UE4_27_2/UWorld.cs b/UE.Toolkit.Core/Types/Unreal/UE4_27_2/UWorld.cs new file mode 100644 index 0000000..1e9305b --- /dev/null +++ b/UE.Toolkit.Core/Types/Unreal/UE4_27_2/UWorld.cs @@ -0,0 +1,8 @@ +using System.Runtime.InteropServices; + +namespace UE.Toolkit.Core.Types.Unreal.UE4_27_2; + +[StructLayout(LayoutKind.Explicit, Size = 0x798)] +public struct UWorld +{ +} \ No newline at end of file diff --git a/UE.Toolkit.Core/Types/Unreal/UE5_4_4/FWorldContext.cs b/UE.Toolkit.Core/Types/Unreal/UE5_4_4/FWorldContext.cs new file mode 100644 index 0000000..e8729e0 --- /dev/null +++ b/UE.Toolkit.Core/Types/Unreal/UE5_4_4/FWorldContext.cs @@ -0,0 +1,38 @@ +using System.Runtime.InteropServices; + +namespace UE.Toolkit.Core.Types.Unreal.UE5_4_4; + + +public enum WorldType : byte +{ + /** An untyped world, in most cases this will be the vestigial worlds of streamed in sub-levels */ + None, + + /** The game world */ + Game, + + /** A world being edited in the editor */ + Editor, + + /** A Play In Editor world */ + PIE, + + /** A preview world for an editor tool */ + EditorPreview, + + /** A preview world for a game */ + GamePreview, + + /** A minimal RPC world for a game */ + GameRPC, + + /** An editor world that was loaded but not currently being edited in the level editor */ + Inactive +} + +[StructLayout(LayoutKind.Explicit, Size = 0x2c8)] +public unsafe struct FWorldContext +{ + [FieldOffset(0x0)] public WorldType WorldType; + [FieldOffset(0x2c0)] public UWorld* ThisCurrentWorld; +} \ No newline at end of file diff --git a/UE.Toolkit.Core/Types/Unreal/UE5_4_4/TMap.cs b/UE.Toolkit.Core/Types/Unreal/UE5_4_4/TMap.cs index a6765d7..ad2195b 100644 --- a/UE.Toolkit.Core/Types/Unreal/UE5_4_4/TMap.cs +++ b/UE.Toolkit.Core/Types/Unreal/UE5_4_4/TMap.cs @@ -163,16 +163,19 @@ internal unsafe TArray>* Self get => Elements; set => Elements = value; } + internal unsafe TMapElementHashable* Allocation { get => Elements->AllocatorInstance; set => Elements->AllocatorInstance = value; } + internal unsafe int Size { get => Elements->ArrayNum; set => Elements->ArrayNum = value; } + internal unsafe int Capacity { get => Elements->ArrayMax; @@ -184,6 +187,7 @@ internal unsafe TElemValue* this[int Index] get => &Allocation[Index].Value; set => Allocation[Index].Value = *value; } + internal unsafe TElemKey GetKey(int Index) => Allocation[Index].Key; internal unsafe TElemKey SetKey(int Index, TElemKey Key) => Allocation[Index].Key = Key; internal unsafe int GetNextHashId(int Index) => Allocation[Index].HashNextId; @@ -335,35 +339,25 @@ private TArray>* ElementsRaw get => (TArray>*)Self; } - private byte* BitAllocatorRaw - { - get => (byte*)(Self + MapConstants.SIZE_OF_ARRAY); - } + private byte* BitAllocatorRaw => (byte*)(Self + MapConstants.SIZE_OF_ARRAY); - private int* FirstFreeIndexPtr - { - get => (int*)(Self + MapConstants.SIZE_OF_ARRAY + MapConstants.SIZE_OF_BIT_ALLOCATOR); - } + private int* FirstFreeIndexPtr => (int*)(Self + MapConstants.SIZE_OF_ARRAY + MapConstants.SIZE_OF_BIT_ALLOCATOR); + private int FirstFreeIndex { get => *FirstFreeIndexPtr; set => *FirstFreeIndexPtr = value; } - private int* NumFreeIndicesPtr - { - get => (int*)(Self + MapConstants.SIZE_OF_ARRAY + MapConstants.SIZE_OF_BIT_ALLOCATOR + sizeof(int)); - } + private int* NumFreeIndicesPtr => (int*)(Self + MapConstants.SIZE_OF_ARRAY + MapConstants.SIZE_OF_BIT_ALLOCATOR + sizeof(int)); + private int NumFreeIndices { get => *NumFreeIndicesPtr; set => *NumFreeIndicesPtr = value; } - private TMapFreeListIndex* FreeList - { - get => (TMapFreeListIndex*)(Self + MapConstants.SIZE_OF_ARRAY + MapConstants.SIZE_OF_BIT_ALLOCATOR + sizeof(nint)); - } + private TMapFreeListIndex* FreeList => (TMapFreeListIndex*)(Self + MapConstants.SIZE_OF_ARRAY + MapConstants.SIZE_OF_BIT_ALLOCATOR + sizeof(nint)); private int** HashesPtr { @@ -375,10 +369,8 @@ private int* Hashes set => *HashesPtr = value; } - private int* HashSizePtr - { - get => (int*)(Self + MapConstants.SIZE_OF_ARRAY + MapConstants.SIZE_OF_BIT_ALLOCATOR + MapConstants.SIZE_OF_FREE_LIST + sizeof(nint)); - } + private int* HashSizePtr => (int*)(Self + MapConstants.SIZE_OF_ARRAY + MapConstants.SIZE_OF_BIT_ALLOCATOR + MapConstants.SIZE_OF_FREE_LIST + sizeof(nint)); + private int HashSize { get => *HashSizePtr; @@ -389,10 +381,10 @@ private int HashSize private static int SizeOf => MapConstants.SIZE_OF_ARRAY + MapConstants.SIZE_OF_BIT_ALLOCATOR + MapConstants.SIZE_OF_FREE_LIST + sizeof(nint) + sizeof(nint); /// - /// Wraps a TArrayList around an existing TArray created in C++ + /// Wraps a TMapDictionary around an existing TMap created in C++ /// - /// Pointer to an existing TArray - /// The Unreal allocator, used for methods that modify the TArray + /// Pointer to an existing TMap + /// The Unreal allocator, used for methods that modify the TMap public TMapDictionary(TMap* _Self, IUnrealMemoryInternal _Allocator, Action? _DebugCallback = null) { Self = (nint)_Self; @@ -661,7 +653,7 @@ protected virtual void Dispose(bool disposing) // Disposed unmanaged resources (for Unreal) if (OwnsInstance) { - if (FreeList!= null) + if (FreeList != null) { Allocator.Free((nint)FreeList); } @@ -699,4 +691,314 @@ public unsafe TMapAccessorEnumerator(TArray ++Position < Self->ArrayNum; public void Reset() => Position = -1; +} + +/// INTERNAL USE +/// Used in cases where the value type is not known at compile time, such as when processing Object XML. +/// The only operations supported are ones required to work with Object XML. +public class TMapDynamicElementAccessor : IDisposable + where TElemKey : unmanaged, IEquatable, IMapHashable +{ + protected unsafe TArray* Elements; + private Type ValueType; + protected IUnrealMemoryInternal Allocator; + protected bool OwnsInstance = false; + private bool Disposed = false; + + public unsafe TMapDynamicElementAccessor(TArray* _Self, Type _ValueType, IUnrealMemoryInternal _Allocator, bool _OwnsInstance = false) + { + Self = _Self; + ValueType = _ValueType; + Allocator = _Allocator; + OwnsInstance = _OwnsInstance; + } + + internal unsafe TArray* Self + { + get => Elements; + set => Elements = value; + } + + internal unsafe byte* Allocation + { + get => Elements->AllocatorInstance; + set => Elements->AllocatorInstance = value; + } + + internal unsafe int Size + { + get => Elements->ArrayNum; + set => Elements->ArrayNum = value; + } + + internal unsafe int Capacity + { + get => Elements->ArrayMax; + set => Elements->ArrayMax = value; + } + + internal int ValueSize => Marshal.SizeOf(ValueType); + + internal unsafe nint this[int Index] + { + get => (nint)Allocation + (Index * ValueSize); + } + + // TMapElement layout: + // public KeyType Key; + // public ValueType Value; + // public int HashNextId; + // public int HashIndex; + + internal unsafe int SizeOf() => sizeof(TElemKey) + ValueSize + 2 * sizeof(int); + private int GetKeyOffset(int Index) => Index * SizeOf(); + private unsafe int GetValueOffset(int Index) => GetKeyOffset(Index) + sizeof(TElemKey); + private int GetNextHashIdOffset(int Index) => GetKeyOffset(Index + 1) - 2 * sizeof(int); + private int GetHashIndexOffset(int Index) => GetKeyOffset(Index + 1) - sizeof(int); + + internal unsafe TElemKey GetKey(int Index) => *(TElemKey*)(Allocation + GetKeyOffset(Index)); + internal unsafe TElemKey SetKey(int Index, TElemKey Key) => *(TElemKey*)(Allocation + GetKeyOffset(Index)) = Key; + internal unsafe int GetNextHashId(int Index) => *(int*)(Allocation + GetNextHashIdOffset(Index)); + internal unsafe int SetNextHashId(int Index, int Value) => *(int*)(Allocation + GetNextHashIdOffset(Index)) = Value; + internal unsafe int GetHashIndex(int Index) => *(int*)(Allocation + GetHashIndexOffset(Index)); + internal unsafe int SetHashIndex(int Index, int Value) => *(int*)(Allocation + GetHashIndexOffset(Index)) = Value; + internal unsafe nint GetAddress(int Index) => (nint)(Allocation + GetValueOffset(Index)); + + #region DISPOSE INTERFACE + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + // Same destructor as TArray + protected virtual void Dispose(bool disposing) + { + if (!Disposed) + { + if (disposing) { } + // Disposed unmanaged resources (for Unreal) + if (OwnsInstance) + { + unsafe + { + if (Allocation != null) + { + Allocator.Free((nint)Allocation); + } + Allocator.Free((nint)Elements); + } + } + Disposed = true; + } + } + + ~TMapDynamicElementAccessor() => Dispose(false); + + #endregion +} + +/// INTERNAL USE +/// Used in cases where the value type is not known at compile time, such as when processing Object XML. +/// The only operations supported are ones required to work with Object XML. +public unsafe class TMapDynamicDictionary : IDisposable + where TElemKey : unmanaged, IEquatable, IMapHashable +{ + public nint Self { get; private set; } + + protected IUnrealMemoryInternal Allocator; + protected TMapDynamicElementAccessor Elements; + private Type ValueType; + protected TBitArrayList BitAllocator; + protected bool OwnsInstance; + protected bool Disposed = false; + + private TArray* ElementsRaw => (TArray*)Self; + + private byte* BitAllocatorRaw => (byte*)(Self + MapConstants.SIZE_OF_ARRAY); + + private int* FirstFreeIndexPtr => (int*)(Self + MapConstants.SIZE_OF_ARRAY + MapConstants.SIZE_OF_BIT_ALLOCATOR); + + private int FirstFreeIndex + { + get => *FirstFreeIndexPtr; + set => *FirstFreeIndexPtr = value; + } + + private int* NumFreeIndicesPtr => (int*)(Self + MapConstants.SIZE_OF_ARRAY + MapConstants.SIZE_OF_BIT_ALLOCATOR + sizeof(int)); + + private int NumFreeIndices + { + get => *NumFreeIndicesPtr; + set => *NumFreeIndicesPtr = value; + } + + private TMapFreeListIndex* FreeList => (TMapFreeListIndex*)(Self + MapConstants.SIZE_OF_ARRAY + MapConstants.SIZE_OF_BIT_ALLOCATOR + sizeof(nint)); + + private int** HashesPtr + { + get => (int**)(Self + MapConstants.SIZE_OF_ARRAY + MapConstants.SIZE_OF_BIT_ALLOCATOR + MapConstants.SIZE_OF_FREE_LIST); + } + private int* Hashes + { + get => *HashesPtr; + set => *HashesPtr = value; + } + + private int* HashSizePtr => (int*)(Self + MapConstants.SIZE_OF_ARRAY + MapConstants.SIZE_OF_BIT_ALLOCATOR + MapConstants.SIZE_OF_FREE_LIST + sizeof(nint)); + + private int HashSize + { + get => *HashSizePtr; + set => *HashSizePtr = value; + } + + // 0x50 + private static int SizeOf => MapConstants.SIZE_OF_ARRAY + MapConstants.SIZE_OF_BIT_ALLOCATOR + MapConstants.SIZE_OF_FREE_LIST + sizeof(nint) + sizeof(nint); + + /// + /// Wraps a TMapDictionary around an existing TMap created in C++ + /// + /// Pointer to an existing TMap + /// The Unreal allocator, used for methods that modify the TMap + public TMapDynamicDictionary(TMap* _Self, Type _ValueType, IUnrealMemoryInternal _Allocator) + { + Self = (nint)_Self; + ValueType = _ValueType; + Allocator = _Allocator; + Elements = new(ElementsRaw, ValueType, Allocator); + OwnsInstance = false; + BitAllocator = new(BitAllocatorRaw, Allocator); + } + + // ValueType* + private nint TryGetLinear(TElemKey key) + { + if (Elements.Size == 0) return nint.Zero; + for (int i = 0; i < Elements.Size; i++) + { + if (Elements.GetKey(i).Equals(key)) + { + return Elements.GetAddress(i); + } + } + return nint.Zero; + } + + private nint TryGetByHash(TElemKey key) + { + var value = nint.Zero; + // Hash alloc doesn't exist for single element maps, + // so fallback to linear search + if (Hashes == null) return TryGetLinear(key); + var elementTarget = Hashes[key.GetTypeHash() & (HashSize - 1)]; + while (elementTarget != MapConstants.INVALID_HASH_ID) + { + if (Elements.GetKey(elementTarget).Equals(key)) + { + value = Elements.GetAddress(elementTarget); + break; + } + elementTarget = Elements.GetNextHashId(elementTarget); + } + return value; + } + private int GetBucketListTail(int HashIndex) + { + int currentIndex = Hashes[HashIndex]; + while (true) + { + if (Elements.GetNextHashId(currentIndex) == MapConstants.INVALID_HASH_ID) break; + currentIndex = Elements.GetNextHashId(currentIndex); + } + return currentIndex; + } + + public bool TryGetValue(TElemKey key, out nint pValue) + { + pValue = new(TryGetByHash(key)); + return pValue != nint.Zero; + } + + public int Count => Elements.Size; + + /* + private void Rehash(int NewSize) + { + + int* NewHashAlloc = (int*)Allocator.Malloc(sizeof(int) * NewSize); + NativeMemory.Fill(NewHashAlloc, (nuint)(NewSize * sizeof(int)), 0xff); + if (Hashes != null) Allocator.Free((nint)Hashes); + Hashes = NewHashAlloc; + for (int i = 0; i < Elements.Size; i++) + { + var newHashIndex = (int)Elements.GetKey(i).GetTypeHash() & (NewSize - 1); + Elements.SetHashIndex(i, newHashIndex); + if (Hashes[newHashIndex] == MapConstants.INVALID_HASH_ID) Hashes[newHashIndex] = i; + else Elements.SetNextHashId(GetBucketListTail(newHashIndex), i); + Elements.SetNextHashId(i, MapConstants.INVALID_HASH_ID); + } + HashSize = NewSize; + } + + private void ResizeTo(int NewSize) + { + + nint NewElementAlloc = Allocator.Malloc(NewSize * Elements.SizeOf()); + if (Elements.Allocation != null) + { + NativeMemory.Copy((byte*)Elements.Allocation, (byte*)NewElementAlloc, (nuint)(Elements.Size * Elements.SizeOf())); + Allocator.Free((nint)Elements.Allocation); + } + Elements.Allocation = (TMapElementHashable*)NewElementAlloc; + Elements.Capacity = NewSize; + } + + int CalculateNewArraySize() => (Elements.Allocation != null) ? Elements.Capacity * 2 : MapConstants.DEFAULT_ARRAY_SIZE; + /// + /// Relinquishes ownership of the TMap so that C#'s garbage collector doesn't delete this. + /// + public void Leak() => OwnsInstance = false; + + bool InBounds(int index) => index >= 0 && index < Elements.Size; + */ + + #region DISPOSE INTERFACE + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (!Disposed) + { + if (disposing) // Dispose managed resources + { + Elements.Dispose(); + BitAllocator.Dispose(); + } + // Disposed unmanaged resources (for Unreal) + if (OwnsInstance) + { + if (FreeList != null) + { + Allocator.Free((nint)FreeList); + } + if (Hashes != null) + { + Allocator.Free((nint)Hashes); + } + Allocator.Free(Self); + } + Disposed = true; + } + } + + ~TMapDynamicDictionary() => Dispose(false); + + #endregion } \ No newline at end of file diff --git a/UE.Toolkit.Core/Types/Unreal/UE5_4_4/UEngine.cs b/UE.Toolkit.Core/Types/Unreal/UE5_4_4/UEngine.cs new file mode 100644 index 0000000..39d70ee --- /dev/null +++ b/UE.Toolkit.Core/Types/Unreal/UE5_4_4/UEngine.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace UE.Toolkit.Core.Types.Unreal.UE5_4_4; + +[StructLayout(LayoutKind.Explicit, Size = 0x10a0)] +public struct UEngine +{ + [FieldOffset(0xfa0)] public TArray> WorldList; +} \ No newline at end of file diff --git a/UE.Toolkit.Core/Types/Unreal/UE5_4_4/UWorld.cs b/UE.Toolkit.Core/Types/Unreal/UE5_4_4/UWorld.cs new file mode 100644 index 0000000..f63dcd8 --- /dev/null +++ b/UE.Toolkit.Core/Types/Unreal/UE5_4_4/UWorld.cs @@ -0,0 +1,8 @@ +using System.Runtime.InteropServices; + +namespace UE.Toolkit.Core.Types.Unreal.UE5_4_4; + +[StructLayout(LayoutKind.Explicit, Size = 0x8f8)] +public struct UWorld +{ +} \ No newline at end of file diff --git a/UE.Toolkit.DumperMod/Dumper.cs b/UE.Toolkit.DumperMod/Dumper.cs index fb76f0e..40b5d27 100644 --- a/UE.Toolkit.DumperMod/Dumper.cs +++ b/UE.Toolkit.DumperMod/Dumper.cs @@ -112,6 +112,15 @@ public void DumpObjects() { sb = new(); AddHeader(sb); + AddPtrDefinition(sb); + } + else + { + var outputFile = Path.Join(_dumpDir, "Builtin_Ptr.cs"); + var sbPtr = new StringBuilder(); + AddHeader(sbPtr); + AddPtrDefinition(sbPtr); + File.WriteAllText(outputFile, sbPtr.ToString()); } var numDumped = 0; @@ -267,6 +276,20 @@ private static void AddHeader(StringBuilder sb) } } + private static void AddPtrDefinition(StringBuilder sb) + { + sb.AppendLine("public readonly unsafe struct Ptr(T* value) : IEquatable>"); + sb.AppendLine(" where T : unmanaged"); + sb.AppendLine("{"); + sb.AppendLine(" public readonly T* Value = value;"); + sb.AppendLine(); + sb.AppendLine(" public bool Equals(Ptr other) => Value == other.Value;"); + sb.AppendLine(" public static bool operator ==(Ptr a, Ptr b) => a.Equals(b);"); + sb.AppendLine(" public static bool operator !=(Ptr a, Ptr b) => !a.Equals(b);"); + sb.AppendLine("}"); + sb.AppendLine(); + } + private void GenerateObjectDefinition(IUClass uclass) { var className = uclass.NamePrivate.ToString(); @@ -499,15 +522,13 @@ private Func GetPropertyTypeNameLazy(IFProperty prop) _UStructDefinitions.TryGetValue(mapPropKeyType.TrimEnd('*'), out var knownKeyStruct); _UStructDefinitions.TryGetValue(mapPropValueType.TrimEnd('*'), out var knownValueStruct); - - var keyType = isKeyPtr ? "nint" : SanitizeName(knownKeyStruct?.DisplayName ?? mapPropKeyType); - var valueType = isValuePtr ? "nint" : SanitizeName(knownValueStruct?.DisplayName ?? mapPropValueType); - if (isKeyPtr || isValuePtr) - { - return $"TMap<{keyType}, {valueType}> /* TMap<{mapPropKeyType}, {mapPropValueType}> */"; - } + var keyName = SanitizeName(knownKeyStruct?.DisplayName ?? mapPropKeyType); + var valueName = SanitizeName(knownValueStruct?.DisplayName ?? mapPropValueType); + var keyType = isKeyPtr ? $"Ptr<{keyName}>" : keyName; + var valueType = isValuePtr ? $"Ptr<{valueName}>" : valueName; + return $"TMap<{keyType}, {valueType}>"; }; case "InterfaceProperty": @@ -519,9 +540,10 @@ private Func GetPropertyTypeNameLazy(IFProperty prop) { var isPtrType = arrayPropType.EndsWith('*') || arrayPropType.Contains('<'); // Use nint for pointers and generic types. _UStructDefinitions.TryGetValue(arrayPropType.TrimEnd('*'), out var knownStruct); + var arrTypeSanitized = SanitizeName(knownStruct?.DisplayName ?? arrayPropType); return isPtrType ? - $"TArray /* TArray<{knownStruct?.DisplayName ?? arrayPropType}> */" : - $"TArray<{SanitizeName(knownStruct?.DisplayName ?? arrayPropType)}>"; + $"TArray>" : + $"TArray<{arrTypeSanitized}>"; }; case "SetProperty": var setPropType = GetPropertyTypeName(_factory.Cast(prop).ElementProp); diff --git a/UE.Toolkit.Interfaces/IUnrealState.cs b/UE.Toolkit.Interfaces/IUnrealState.cs new file mode 100644 index 0000000..e0d77ad --- /dev/null +++ b/UE.Toolkit.Interfaces/IUnrealState.cs @@ -0,0 +1,15 @@ +using UE.Toolkit.Core.Types.Unreal.Factories.Interfaces; + +namespace UE.Toolkit.Interfaces; + +public interface IUnrealState +{ + /// + /// Try to get the currently active world. It's recommended to use a World Context Object - a UObject that + /// belongs to a particular world and call GetWorld() from there. This is useful in cases where there is no + /// World Context Object. + /// + /// The target world. + /// If a target world could be found. + public bool GetCurrentPlayWorld(out IUObject? TargetWorld); +} \ No newline at end of file diff --git a/UE.Toolkit.Reloaded/Mod.cs b/UE.Toolkit.Reloaded/Mod.cs index b2a2893..cdb93a3 100644 --- a/UE.Toolkit.Reloaded/Mod.cs +++ b/UE.Toolkit.Reloaded/Mod.cs @@ -44,6 +44,7 @@ public class Mod : ModBase, IExports private readonly UnrealClasses _classes; private readonly Common.ResolveAddress _address; private readonly UnrealMethods _methods; + private readonly UnrealState _state; public Mod(ModContext context) { @@ -74,6 +75,7 @@ public Mod(ModContext context) _address = new(); _classes = new(_factory, _memory, _hooks, _address); _methods = new(_factory, _memory, _classes, _objects, _hooks); + _state = new(_factory); _modLoader.AddOrReplaceController(_owner, _memory); _modLoader.AddOrReplaceController(_owner, _tables); @@ -85,6 +87,7 @@ public Mod(ModContext context) _modLoader.AddOrReplaceController(_owner, _classes); _modLoader.AddOrReplaceController(_owner, _factory); _modLoader.AddOrReplaceController(_owner, _methods); + _modLoader.AddOrReplaceController(_owner, _state); _modLoader.ModLoaded += OnModLoaded; } @@ -123,7 +126,8 @@ public Mod() public Type[] GetTypes() => [ - typeof(IDataTables), typeof(IUnrealObjects), typeof(IToolkit),typeof(ITypeRegistry), typeof(UObjectBase), + typeof(IDataTables), typeof(IUnrealObjects), typeof(IToolkit), typeof(ITypeRegistry), typeof(UObjectBase), typeof(IUnrealFactory), typeof(IUnrealNames), typeof(IUnrealMemory), typeof(IUnrealStrings), + typeof(IUnrealClasses), typeof(IUnrealMethods), typeof(IUnrealState) ]; } \ No newline at end of file diff --git a/UE.Toolkit.Reloaded/ObjectWriters/Nodes/DataTableFieldNode.cs b/UE.Toolkit.Reloaded/ObjectWriters/Nodes/DataTableFieldNode.cs index e9cf183..2c4699e 100644 --- a/UE.Toolkit.Reloaded/ObjectWriters/Nodes/DataTableFieldNode.cs +++ b/UE.Toolkit.Reloaded/ObjectWriters/Nodes/DataTableFieldNode.cs @@ -50,7 +50,6 @@ public unsafe void ConsumeNode(XmlReader reader) { tempTable.Add(new (id), new Ptr((UObjectBase*)nodeFactory.Memory.MallocZeroed(itemSize))); Log.Debug($"{nameof(DataTableFieldNode)} || Added row with ID '{id}' into '{fieldName}'."); - // Log.Error($"{nameof(DataTableFieldNode)} || Failed to find row with ID '{id}' in '{fieldName}'."); break; } } diff --git a/UE.Toolkit.Reloaded/ObjectWriters/Nodes/FieldNodeFactory.cs b/UE.Toolkit.Reloaded/ObjectWriters/Nodes/FieldNodeFactory.cs index 0c59427..b69bd5b 100644 --- a/UE.Toolkit.Reloaded/ObjectWriters/Nodes/FieldNodeFactory.cs +++ b/UE.Toolkit.Reloaded/ObjectWriters/Nodes/FieldNodeFactory.cs @@ -1,4 +1,5 @@ using System.Diagnostics.CodeAnalysis; +using System.Runtime.InteropServices; using UE.Toolkit.Core.Types.Interfaces; using UE.Toolkit.Core.Types.Unreal.UE5_4_4; using UE.Toolkit.Interfaces.ObjectWriters; @@ -18,6 +19,9 @@ public bool TryCreate(string fieldName, nint fieldPtr, Type fieldType, [NotNullW private IFieldNode? Create(string fieldName, nint fieldPtr, Type fieldType) { + + Log.Debug($"{nameof(FieldNodeFactory)} || Create Node '{fieldName}' with type '{fieldType.Name}'."); + if (fieldType.IsPrimitive || fieldType.IsEnum || fieldType == typeof(string) @@ -36,6 +40,21 @@ public bool TryCreate(string fieldName, nint fieldPtr, Type fieldType, [NotNullW if (fieldType.Name.StartsWith("UDataTable")) { return new DataTableFieldNode(fieldName, fieldPtr, fieldType, this); + } + + if (fieldType.Name.StartsWith("TMap")) + { + var keyType = fieldType.GetGenericArguments()[0]; + if (keyType == typeof(int)) + { + return new TMapIntFieldNode(fieldName, fieldPtr, fieldType, this); + } + Log.Warning($"{nameof(FieldNodeFactory)} || TODO: Field '{fieldName}' with type '{fieldType.Name}'."); + foreach (var Generic in fieldType.GenericTypeArguments) + { + Log.Warning($"{nameof(FieldNodeFactory)} || TMap Argument: {Generic.Name}"); + } + return null; } if (fieldType.IsValueType) diff --git a/UE.Toolkit.Reloaded/ObjectWriters/Nodes/StructFieldNode.cs b/UE.Toolkit.Reloaded/ObjectWriters/Nodes/StructFieldNode.cs index cb9a3e5..065e0bd 100644 --- a/UE.Toolkit.Reloaded/ObjectWriters/Nodes/StructFieldNode.cs +++ b/UE.Toolkit.Reloaded/ObjectWriters/Nodes/StructFieldNode.cs @@ -3,16 +3,36 @@ namespace UE.Toolkit.Reloaded.ObjectWriters.Nodes; +internal record FieldData(Type type, nint offset); + public class StructFieldNode : IFieldNode { - private static readonly Dictionary> CachedStructsFields = []; + private static readonly Dictionary> CachedStructsFields = []; - private readonly Dictionary _fields; + private readonly Dictionary _fields; private readonly string _structName; private readonly nint _structPtr; private readonly Type _structType; private readonly FieldNodeFactory _nodeFactory; + private static Dictionary GetFieldsFromStruct(Type structType) + { + var FieldList = new Dictionary(); + foreach (var Field in structType.GetFields().SelectMany((x, i) => + { + if (i == 0 && x.Name == "Super") return GetFieldsFromStruct(x.FieldType).AsEnumerable(); + return Enumerable.Repeat>(new( + x.Name, new(x.FieldType, Marshal.OffsetOf(structType, x.Name))), 1); + })) + { + // ToDictionary() implementation will crash if there are duplicate field names + // While this shouldn't be the case with new struct dumps, make sure that the old + // format still works + FieldList.TryAdd(Field.Key, Field.Value); + } + return FieldList; + } + public StructFieldNode(string structName, nint structPtr, Type structType, FieldNodeFactory nodeFactory) { _structName = structName; @@ -27,7 +47,7 @@ public StructFieldNode(string structName, nint structPtr, Type structType, Field } else { - _fields = structType.GetFields().ToDictionary(x => x.Name, x => x.FieldType); + _fields = GetFieldsFromStruct(structType); CachedStructsFields[structType.Name] = _fields; } } @@ -43,9 +63,11 @@ public void ConsumeNode(XmlReader reader) anyElementFound = true; var fieldName = reader.Name; - if (_fields.TryGetValue(fieldName, out var fieldType)) + if (_fields.TryGetValue(fieldName, out var fieldData)) { - var fieldPtr = _structPtr + Marshal.OffsetOf(_structType, fieldName); + var fieldType = fieldData.type; + // var fieldPtr = _structPtr + Marshal.OffsetOf(_structType, fieldName); + var fieldPtr = _structPtr + fieldData.offset; if (_nodeFactory.TryCreate(fieldName, fieldPtr, fieldType, out var fieldNode)) { fieldNode.ConsumeNode(reader); diff --git a/UE.Toolkit.Reloaded/ObjectWriters/Nodes/TMapFieldNode.cs b/UE.Toolkit.Reloaded/ObjectWriters/Nodes/TMapFieldNode.cs new file mode 100644 index 0000000..1726984 --- /dev/null +++ b/UE.Toolkit.Reloaded/ObjectWriters/Nodes/TMapFieldNode.cs @@ -0,0 +1,66 @@ +using System.Runtime.InteropServices; +using System.Xml; +using UE.Toolkit.Core.Types; +using UE.Toolkit.Core.Types.Unreal.UE5_4_4; + +namespace UE.Toolkit.Reloaded.ObjectWriters.Nodes; + +public class TMapIntFieldNode(string fieldName, nint fieldPtr, Type fieldType, FieldNodeFactory nodeFactory) : IFieldNode +{ + public unsafe void ConsumeNode(XmlReader reader) + { + // TMap item type + var valueType = fieldType.GetGenericArguments()[1]; + var valueSize = Marshal.SizeOf(valueType); + + // TMap values are always by-reference. + var tempMap = new TMapDynamicDictionary((TMap*)fieldPtr, valueType, nodeFactory.Memory); + + // Get any item nodes. + using var subReader = reader.ReadSubtree(); + subReader.MoveToContent(); + + while (subReader.Read()) + { + if (subReader.NodeType != XmlNodeType.Element) continue; + if (subReader.Name != WriterConstants.ItemTag) throw new($"Only '{WriterConstants.ItemTag}' elements can be directly inside a map. Found: {subReader.Name}"); + + var id = subReader.GetAttribute(WriterConstants.ItemIdAttr); + if (id == null) + { + Log.Error($"{nameof(TArrayFieldNode)} || '{WriterConstants.ItemTag}' is missing an ID."); + break; + } + + if (!int.TryParse(id, out var itemIdx)) + { + Log.Warning($"{nameof(TMapIntFieldNode)} || Invalid ID: {id}"); + break; + } + + // Get existing value + + /* + if (!tempMap.ContainsKey(new HashableInt8(itemIdx))) + { + var newEntry = new Ptr((nint*)nodeFactory.Memory.MallocZeroed(valueSize)); + tempMap.Add(new HashableInt8(itemIdx), newEntry); + Log.Debug($"{nameof(TMapIntFieldNode)} || Added entry at 0x{(nint)newEntry.Value:x} with key '{itemIdx}' into '{fieldName}'"); + } + */ + + if (tempMap.TryGetValue(new HashableInt8(itemIdx), out var valuePtr)) + { + if (nodeFactory.TryCreate($"{fieldName} (Key: {itemIdx})", valuePtr, valueType, out var itemNode)) + { + var itemTree = subReader.ReadSubtree(); + itemTree.MoveToContent(); + + itemNode.ConsumeNode(itemTree); + } + } + } + + Log.Verbose($"{nameof(TMapIntFieldNode)} || Field '{fieldName}' node consumed."); + } +} \ No newline at end of file diff --git a/UE.Toolkit.Reloaded/ObjectWriters/Writers/PrimitiveFieldWriter.cs b/UE.Toolkit.Reloaded/ObjectWriters/Writers/PrimitiveFieldWriter.cs index e91a3ec..3c2427c 100644 --- a/UE.Toolkit.Reloaded/ObjectWriters/Writers/PrimitiveFieldWriter.cs +++ b/UE.Toolkit.Reloaded/ObjectWriters/Writers/PrimitiveFieldWriter.cs @@ -30,6 +30,10 @@ public void SetField(string value) SetField(intValue); } } + else if (bool.TryParse(value, out var boolValue)) + { + SetField(boolValue); + } else if (double.TryParse(value, out var numValue)) { SetField(numValue); diff --git a/UE.Toolkit.Reloaded/Project/UE.Toolkit.Reloaded/p3r/scans.ini b/UE.Toolkit.Reloaded/Project/UE.Toolkit.Reloaded/p3r/scans.ini index 1640018..80dd5c2 100644 --- a/UE.Toolkit.Reloaded/Project/UE.Toolkit.Reloaded/p3r/scans.ini +++ b/UE.Toolkit.Reloaded/Project/UE.Toolkit.Reloaded/p3r/scans.ini @@ -47,4 +47,7 @@ GetStaticStruct=48 89 5C 24 ?? 48 89 6C 24 ?? 56 57 41 56 48 83 EC 40 4D 8B F0 4 ;GetStaticStruct_RESULT= ConstructUScriptStruct=40 53 56 57 41 54 41 55 41 56 48 81 EC 08 03 00 00 -;ConstructUScriptStruct_RESULT= \ No newline at end of file +;ConstructUScriptStruct_RESULT= + +GEngine=48 89 05 ?? ?? ?? ?? 48 85 C9 74 ?? E8 ?? ?? ?? ?? 48 8D 4D ?? +GEngine_RESULT=GetGlobalAddress(result + 3) \ No newline at end of file diff --git a/UE.Toolkit.Reloaded/Project/UE.Toolkit.Reloaded/p3r/unreal.ini b/UE.Toolkit.Reloaded/Project/UE.Toolkit.Reloaded/p3r/unreal.ini index e771c20..4b74f1a 100644 --- a/UE.Toolkit.Reloaded/Project/UE.Toolkit.Reloaded/p3r/unreal.ini +++ b/UE.Toolkit.Reloaded/Project/UE.Toolkit.Reloaded/p3r/unreal.ini @@ -8,8 +8,8 @@ QuantizeSize=0x38 [UObject] ProcessEvent=0x220 -[UStruct] -Link=0x288 - [UPackage] -GamePackage=/Script/xrd777 \ No newline at end of file +GamePackage=/Script/xrd777 + +[UStruct] +Link=0x288 \ No newline at end of file diff --git a/UE.Toolkit.Reloaded/Project/UE.Toolkit.Reloaded/scans.ini b/UE.Toolkit.Reloaded/Project/UE.Toolkit.Reloaded/scans.ini index 1e9d534..d3cb1e2 100644 --- a/UE.Toolkit.Reloaded/Project/UE.Toolkit.Reloaded/scans.ini +++ b/UE.Toolkit.Reloaded/Project/UE.Toolkit.Reloaded/scans.ini @@ -49,4 +49,7 @@ GetStaticStruct=48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 57 48 83 EC 50 49 8 ;GetStaticStruct_RESULT= ConstructUScriptStruct=40 53 56 57 41 54 41 55 41 56 48 81 EC 98 03 00 00 -;ConstructUScriptStruct \ No newline at end of file +;ConstructUScriptStruct + +GEngine=48 89 05 ?? ?? ?? ?? 48 85 C9 74 ?? E8 ?? ?? ?? ?? 48 8D 4D ?? +GEngine_RESULT=GetGlobalAddress(result + 3) \ No newline at end of file diff --git a/UE.Toolkit.Reloaded/Project/UE.Toolkit.Reloaded/unreal.ini b/UE.Toolkit.Reloaded/Project/UE.Toolkit.Reloaded/unreal.ini index 4c4736a..6068556 100644 --- a/UE.Toolkit.Reloaded/Project/UE.Toolkit.Reloaded/unreal.ini +++ b/UE.Toolkit.Reloaded/Project/UE.Toolkit.Reloaded/unreal.ini @@ -10,8 +10,8 @@ QuantizeSize=0x50 [UObject] ProcessEvent=0x268 -[UStruct] -Link=0x2d0 - [UPackage] -GamePackage=/Script/SandFall \ No newline at end of file +GamePackage=/Script/SandFall + +[UStruct] +Link=0x2d0 \ No newline at end of file diff --git a/UE.Toolkit.Reloaded/Unreal/UnrealState.cs b/UE.Toolkit.Reloaded/Unreal/UnrealState.cs new file mode 100644 index 0000000..e179afc --- /dev/null +++ b/UE.Toolkit.Reloaded/Unreal/UnrealState.cs @@ -0,0 +1,34 @@ +using UE.Toolkit.Core.Types.Unreal.Factories; +using UE.Toolkit.Core.Types.Unreal.Factories.Interfaces; +using WorldType = UE.Toolkit.Core.Types.Unreal.UE5_4_4.WorldType; +using UE.Toolkit.Interfaces; + +namespace UE.Toolkit.Reloaded.Unreal; + +public class UnrealState : IUnrealState +{ + private IUnrealFactory Factory; + + private nint GEngine; + + // See UEngine::GetCurrentPlayWorld + public unsafe bool GetCurrentPlayWorld(out IUObject? TargetWorld) + { + TargetWorld = null; + if (GEngine == nint.Zero) return false; + var World = Factory.CreateUEngine(*(nint*)GEngine); + foreach (var WorldContext in World.GetWorldList()) + { + if (WorldContext.GetWorldType() == WorldType.Game && WorldContext.GetWorld() != nint.Zero) + { + TargetWorld = Factory.CreateUObject(WorldContext.GetWorld()); + } + } + return TargetWorld != null; + } + + public UnrealState(IUnrealFactory _Factory) + { + Project.Scans.AddListener("GEngine", x => GEngine = x); + } +} \ No newline at end of file From c0864f5b008ac944af3d968fc3634f639643b770 Mon Sep 17 00:00:00 2001 From: Rirurin Date: Tue, 25 Nov 2025 21:59:44 +0800 Subject: [PATCH 08/12] Write correct values for bit booleans (Object XML), add object spawning and GetSubsystem for UGameInstance --- .../Unreal/Factories/BaseUnrealFactory.cs | 4 + .../Types/Unreal/Factories/IUnrealFactory.cs | 4 + .../Interfaces/IFActorSpawnParameters.cs | 8 ++ .../IFStaticConstructObjectParameters.cs | 8 ++ .../Factories/Interfaces/IUGameInstance.cs | 6 + .../Factories/UE4_27_2/UnrealFactory.cs | 120 +++++++++++++++++- .../Unreal/Factories/UE5_4_4/UnrealFactory.cs | 103 +++++++++++++++ .../Unreal/UE4_27_2/FActorSpawnParameters.cs | 14 ++ .../FStaticConstructObjectParameters.cs | 25 ++++ .../Types/Unreal/UE4_27_2/UGameInstance.cs | 15 +++ .../Unreal/UE5_4_4/FActorSpawnParameters.cs | 13 ++ .../FStaticConstructObjectParameters.cs | 22 ++++ UE.Toolkit.Core/Types/Unreal/UE5_4_4/TMap.cs | 82 ++++++++++-- .../Types/Unreal/UE5_4_4/UGameInstance.cs | 15 +++ UE.Toolkit.Interfaces/IUnrealSpawning.cs | 22 ++++ UE.Toolkit.Interfaces/IUnrealState.cs | 5 + UE.Toolkit.Reloaded/Mod.cs | 2 +- .../ObjectWriters/Nodes/DataTableFieldNode.cs | 2 +- .../ObjectWriters/Nodes/FieldNodeFactory.cs | 52 ++++++-- .../ObjectWriters/Nodes/PrimitiveFieldNode.cs | 5 +- .../ObjectWriters/Nodes/StructFieldNode.cs | 13 +- .../ObjectWriters/Nodes/TArrayFieldNode.cs | 2 +- .../ObjectWriters/Nodes/TMapFieldNode.cs | 114 ++++++++++++----- .../ObjectWriters/ObjectWriter.cs | 2 +- .../Writers/FieldWriterFactory.cs | 5 +- .../Writers/PrimitiveFieldWriter.cs | 6 +- .../Project/UE.Toolkit.Reloaded/p3r/scans.ini | 5 +- .../Project/UE.Toolkit.Reloaded/scans.ini | 5 +- UE.Toolkit.Reloaded/Unreal/UnrealSpawning.cs | 59 +++++++++ UE.Toolkit.Reloaded/Unreal/UnrealState.cs | 43 ++++++- 30 files changed, 703 insertions(+), 78 deletions(-) create mode 100644 UE.Toolkit.Core/Types/Unreal/Factories/Interfaces/IFActorSpawnParameters.cs create mode 100644 UE.Toolkit.Core/Types/Unreal/Factories/Interfaces/IFStaticConstructObjectParameters.cs create mode 100644 UE.Toolkit.Core/Types/Unreal/Factories/Interfaces/IUGameInstance.cs create mode 100644 UE.Toolkit.Core/Types/Unreal/UE4_27_2/FActorSpawnParameters.cs create mode 100644 UE.Toolkit.Core/Types/Unreal/UE4_27_2/FStaticConstructObjectParameters.cs create mode 100644 UE.Toolkit.Core/Types/Unreal/UE4_27_2/UGameInstance.cs create mode 100644 UE.Toolkit.Core/Types/Unreal/UE5_4_4/FActorSpawnParameters.cs create mode 100644 UE.Toolkit.Core/Types/Unreal/UE5_4_4/FStaticConstructObjectParameters.cs create mode 100644 UE.Toolkit.Core/Types/Unreal/UE5_4_4/UGameInstance.cs create mode 100644 UE.Toolkit.Interfaces/IUnrealSpawning.cs create mode 100644 UE.Toolkit.Reloaded/Unreal/UnrealSpawning.cs diff --git a/UE.Toolkit.Core/Types/Unreal/Factories/BaseUnrealFactory.cs b/UE.Toolkit.Core/Types/Unreal/Factories/BaseUnrealFactory.cs index 75e5c10..aac98c5 100644 --- a/UE.Toolkit.Core/Types/Unreal/Factories/BaseUnrealFactory.cs +++ b/UE.Toolkit.Core/Types/Unreal/Factories/BaseUnrealFactory.cs @@ -90,4 +90,8 @@ public T Cast(IPtr obj) public abstract IFWorldContext CreateFWorldContext(nint ptr); public abstract IUEngine CreateUEngine(nint ptr); + public abstract IUGameInstance CreateUGameInstance(nint ptr); + + public abstract IFStaticConstructObjectParameters CreateFStaticConstructObjectParameters(); + public abstract IFActorSpawnParameters CreateFActorSpawnParameters(); } \ No newline at end of file diff --git a/UE.Toolkit.Core/Types/Unreal/Factories/IUnrealFactory.cs b/UE.Toolkit.Core/Types/Unreal/Factories/IUnrealFactory.cs index 8640be8..35388de 100644 --- a/UE.Toolkit.Core/Types/Unreal/Factories/IUnrealFactory.cs +++ b/UE.Toolkit.Core/Types/Unreal/Factories/IUnrealFactory.cs @@ -46,4 +46,8 @@ public interface IUnrealFactory IFWorldContext CreateFWorldContext(nint ptr); IUEngine CreateUEngine(nint ptr); + IUGameInstance CreateUGameInstance(nint ptr); + + IFStaticConstructObjectParameters CreateFStaticConstructObjectParameters(); + IFActorSpawnParameters CreateFActorSpawnParameters(); } \ No newline at end of file diff --git a/UE.Toolkit.Core/Types/Unreal/Factories/Interfaces/IFActorSpawnParameters.cs b/UE.Toolkit.Core/Types/Unreal/Factories/Interfaces/IFActorSpawnParameters.cs new file mode 100644 index 0000000..5645901 --- /dev/null +++ b/UE.Toolkit.Core/Types/Unreal/Factories/Interfaces/IFActorSpawnParameters.cs @@ -0,0 +1,8 @@ +using UE.Toolkit.Core.Types.Unreal.UE5_4_4; + +namespace UE.Toolkit.Core.Types.Unreal.Factories.Interfaces; + +public interface IFActorSpawnParameters : IPtr +{ + void SetParams(EObjectFlags Flags); +} \ No newline at end of file diff --git a/UE.Toolkit.Core/Types/Unreal/Factories/Interfaces/IFStaticConstructObjectParameters.cs b/UE.Toolkit.Core/Types/Unreal/Factories/Interfaces/IFStaticConstructObjectParameters.cs new file mode 100644 index 0000000..d497051 --- /dev/null +++ b/UE.Toolkit.Core/Types/Unreal/Factories/Interfaces/IFStaticConstructObjectParameters.cs @@ -0,0 +1,8 @@ +using UE.Toolkit.Core.Types.Unreal.UE5_4_4; + +namespace UE.Toolkit.Core.Types.Unreal.Factories.Interfaces; + +public interface IFStaticConstructObjectParameters : IPtr +{ + void SetParams(IUClass Class, IUObject? Owner, FName Name); +} \ No newline at end of file diff --git a/UE.Toolkit.Core/Types/Unreal/Factories/Interfaces/IUGameInstance.cs b/UE.Toolkit.Core/Types/Unreal/Factories/Interfaces/IUGameInstance.cs new file mode 100644 index 0000000..09781a0 --- /dev/null +++ b/UE.Toolkit.Core/Types/Unreal/Factories/Interfaces/IUGameInstance.cs @@ -0,0 +1,6 @@ +namespace UE.Toolkit.Core.Types.Unreal.Factories.Interfaces; + +public interface IUGameInstance : IUObject +{ + bool TryGetSubsystem(IUClass Class, out IUObject? Subsystem); +} \ No newline at end of file diff --git a/UE.Toolkit.Core/Types/Unreal/Factories/UE4_27_2/UnrealFactory.cs b/UE.Toolkit.Core/Types/Unreal/Factories/UE4_27_2/UnrealFactory.cs index c51b71c..e89b7ce 100644 --- a/UE.Toolkit.Core/Types/Unreal/Factories/UE4_27_2/UnrealFactory.cs +++ b/UE.Toolkit.Core/Types/Unreal/Factories/UE4_27_2/UnrealFactory.cs @@ -1,5 +1,4 @@ using System.Collections; -using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; using UE.Toolkit.Core.Common; using UE.Toolkit.Core.Types.Unreal.Factories.Interfaces; @@ -28,6 +27,7 @@ using FProperty = UE.Toolkit.Core.Types.Unreal.UE4_27_2.FProperty; using FPropertyParamsBase = UE.Toolkit.Core.Types.Unreal.UE4_27_2.FPropertyParamsBase; using FSetProperty = UE.Toolkit.Core.Types.Unreal.UE4_27_2.FSetProperty; +using FStaticConstructObjectParameters = UE.Toolkit.Core.Types.Unreal.UE4_27_2.FStaticConstructObjectParameters; using FStructParams = UE.Toolkit.Core.Types.Unreal.UE4_27_2.FStructParams; using FStructProperty = UE.Toolkit.Core.Types.Unreal.UE4_27_2.FStructProperty; using FWorldContext = UE.Toolkit.Core.Types.Unreal.UE4_27_2.FWorldContext; @@ -36,6 +36,7 @@ using UEnum = UE.Toolkit.Core.Types.Unreal.UE4_27_2.UEnum; using UField = UE.Toolkit.Core.Types.Unreal.UE4_27_2.UField; using UFunction = UE.Toolkit.Core.Types.Unreal.UE4_27_2.UFunction; +using UGameInstance = UE.Toolkit.Core.Types.Unreal.UE4_27_2.UGameInstance; using UObjectBase = UE.Toolkit.Core.Types.Unreal.UE4_27_2.UObjectBase; using UScriptStruct = UE.Toolkit.Core.Types.Unreal.UE4_27_2.UScriptStruct; using UStruct = UE.Toolkit.Core.Types.Unreal.UE4_27_2.UStruct; @@ -64,14 +65,14 @@ public override nint SizeOf() nameof(IFBoolProperty) => sizeof(FBoolProperty), nameof(IFEnumProperty) => sizeof(FEnumProperty), nameof(IFObjectProperty) => sizeof(FObjectProperty), - nameof(IFSoftClassProperty) => sizeof(UE.Toolkit.Core.Types.Unreal.UE5_4_4.FSoftClassProperty), + nameof(IFSoftClassProperty) => sizeof(FSoftClassProperty), nameof(IFClassProperty) => sizeof(FClassProperty), nameof(IFStructProperty) => sizeof(FStructProperty), nameof(IFMapProperty) => sizeof(FMapProperty), - nameof(IFInterfaceProperty) => sizeof(UE.Toolkit.Core.Types.Unreal.UE5_4_4.FInterfaceProperty), + nameof(IFInterfaceProperty) => sizeof(FInterfaceProperty), nameof(IFArrayProperty) => sizeof(FArrayProperty), nameof(IFSetProperty) => sizeof(FSetProperty), - nameof(IFOptionalProperty) => sizeof(UE.Toolkit.Core.Types.Unreal.UE5_4_4.FOptionalProperty), + nameof(IFOptionalProperty) => sizeof(FOptionalProperty), _ => throw new NotSupportedException(TypeName) }; } @@ -132,7 +133,16 @@ public override nint SizeOf() public override IFGenericPropertyParams CreateFGenericPropertyParams(nint ptr) => new FGenericPropertyParamsUE4_27_2(ptr, this); public override IFWorldContext CreateFWorldContext(nint ptr) => new FWorldContextUE4_27_2(ptr, this); + public override IUEngine CreateUEngine(nint ptr) => new UEngineUE4_27_2(ptr, this); + + public override IUGameInstance CreateUGameInstance(nint ptr) => new UGameInstanceUE4_27_2(ptr, this); + + public override IFStaticConstructObjectParameters CreateFStaticConstructObjectParameters() + => new FStaticConstructObjectParametersUE4_27_2(this); + + public override IFActorSpawnParameters CreateFActorSpawnParameters() + => new FActorSpawnParametersUE4_27_2(this); } public unsafe class FSetPropertyUE4_27_2(nint ptr, IUnrealFactory factory) @@ -474,8 +484,8 @@ public unsafe class UClassUE4_27_2(nint ptr, IUnrealFactory factory) public IUFunction? GetFunction(string Name) { - var FuncMapDict = new UE.Toolkit.Core.Types.Unreal.UE5_4_4.TMapDictionary>( - (UE.Toolkit.Core.Types.Unreal.UE5_4_4.TMap>*)(&_self->func_map), + var FuncMapDict = new TMapDictionary>( + (TMap>*)(&_self->func_map), _factory.Memory ); return FuncMapDict.TryGetValue(new(Name), out var Function) @@ -654,4 +664,102 @@ public unsafe class UEngineUE4_27_2(nint ptr, IUnrealFactory factory) internal TArray>* GetWorldListInner() => &_self->WorldList; public IEnumerable GetWorldList() => new FWorldContextEnumerator(this, factory); +} + +public unsafe class FStaticConstructObjectParametersUE4_27_2 + : IFStaticConstructObjectParameters, IDisposable +{ + private readonly FStaticConstructObjectParameters* _self; + public nint Ptr => (nint)_self; + protected readonly IUnrealFactory _factory; + private bool Disposed = false; + + public FStaticConstructObjectParametersUE4_27_2(IUnrealFactory factory) + { + _factory = factory; + _self = (FStaticConstructObjectParameters*)_factory.Memory.MallocZeroed(sizeof(FStaticConstructObjectParameters)); + } + + public void SetParams(IUClass Class, IUObject? Owner, FName Name) + { + _self->Class = (UClass*)Class.Ptr; + _self->Outer = (UObjectBase*)Owner?.Ptr; + _self->Name = Name; + } + + #region DISPOSE INTERFACE + + public void Dispose() + { + Disposing(); + GC.SuppressFinalize(this); + } + + protected virtual void Disposing() + { + if (Disposed) return; + _factory.Memory.Free(Ptr); + Disposed = true; + } + + ~FStaticConstructObjectParametersUE4_27_2() => Disposing(); + + #endregion +} + +public unsafe class FActorSpawnParametersUE4_27_2 + : IFActorSpawnParameters, IDisposable +{ + private readonly FActorSpawnParameters* _self; + public nint Ptr => (nint)_self; + protected readonly IUnrealFactory _factory; + private bool Disposed = false; + + public FActorSpawnParametersUE4_27_2(IUnrealFactory factory) + { + _factory = factory; + _self = (FActorSpawnParameters*)_factory.Memory.MallocZeroed(sizeof(FActorSpawnParameters)); + } + + public void SetParams(EObjectFlags Flags) + { + _self->ObjectFlags = Flags; + } + + #region DISPOSE INTERFACE + + public void Dispose() + { + Disposing(); + GC.SuppressFinalize(this); + } + + protected virtual void Disposing() + { + if (Disposed) return; + _factory.Memory.Free(Ptr); + Disposed = true; + } + + ~FActorSpawnParametersUE4_27_2() => Disposing(); + + #endregion +} + +public unsafe class UGameInstanceUE4_27_2(nint ptr, IUnrealFactory factory) + : UObjectUE4_27_2(ptr, factory), IUGameInstance +{ + private readonly UGameInstance* _self = (UGameInstance*)ptr; + + public bool TryGetSubsystem(IUClass Class, out IUObject? Subsystem) + { + var Subsystems = new TMapDictionary, HashablePtr>( + (TMap, HashablePtr>*)(&_self->SubsystemCollection.Subsystems), + factory.Memory); + Subsystem = Subsystems.TryGetValue(new HashablePtr(new Ptr((UClass*)Class.Ptr)), + out var pSubsystem) + ? _factory.CreateUObject((nint)pSubsystem.Value->Ptr.Value) + : null; + return Subsystem != null; + } } \ No newline at end of file diff --git a/UE.Toolkit.Core/Types/Unreal/Factories/UE5_4_4/UnrealFactory.cs b/UE.Toolkit.Core/Types/Unreal/Factories/UE5_4_4/UnrealFactory.cs index 89c6c96..f280305 100644 --- a/UE.Toolkit.Core/Types/Unreal/Factories/UE5_4_4/UnrealFactory.cs +++ b/UE.Toolkit.Core/Types/Unreal/Factories/UE5_4_4/UnrealFactory.cs @@ -70,6 +70,11 @@ public override nint SizeOf() public override IFGenericPropertyParams CreateFGenericPropertyParams(nint ptr) => new FGenericPropertyParams_UE5_4_4(ptr, this); public override IFWorldContext CreateFWorldContext(nint ptr) => new FWorldContext_UE5_4_4(ptr, this); public override IUEngine CreateUEngine(nint ptr) => new UEngine_UE5_4_4(ptr, this); + public override IUGameInstance CreateUGameInstance(nint ptr) => new UGameInstance_UE5_4_4(ptr, this); + public override IFStaticConstructObjectParameters CreateFStaticConstructObjectParameters() + => new FStaticConstructObjectParameters_UE5_4_4(this); + public override IFActorSpawnParameters CreateFActorSpawnParameters() + => new FActorSpawnParameters_UE5_4_4(this); } public unsafe class FOptionalProperty_UE5_4_4(nint ptr, IUnrealFactory factory) @@ -622,4 +627,102 @@ public unsafe class UEngine_UE5_4_4(nint ptr, IUnrealFactory factory) internal TArray>* GetWorldListInner() => &_self->WorldList; public IEnumerable GetWorldList() => new FWorldContextEnumerator(this, factory); +} + +public unsafe class FStaticConstructObjectParameters_UE5_4_4 + : IFStaticConstructObjectParameters, IDisposable +{ + private readonly FStaticConstructObjectParameters* _self; + public nint Ptr => (nint)_self; + protected readonly IUnrealFactory _factory; + private bool Disposed = false; + + public FStaticConstructObjectParameters_UE5_4_4(IUnrealFactory factory) + { + _factory = factory; + _self = (FStaticConstructObjectParameters*)_factory.Memory.MallocZeroed(sizeof(FStaticConstructObjectParameters)); + } + + public void SetParams(IUClass Class, IUObject? Owner, FName Name) + { + _self->Class = (UClass*)Class.Ptr; + _self->Outer = (UObjectBase*)Owner?.Ptr; + _self->Name = Name; + } + + #region DISPOSE INTERFACE + + public void Dispose() + { + Disposing(); + GC.SuppressFinalize(this); + } + + protected virtual void Disposing() + { + if (Disposed) return; + _factory.Memory.Free(Ptr); + Disposed = true; + } + + ~FStaticConstructObjectParameters_UE5_4_4() => Disposing(); + + #endregion +} + +public unsafe class FActorSpawnParameters_UE5_4_4 + : IFActorSpawnParameters, IDisposable +{ + private readonly FActorSpawnParameters* _self; + public nint Ptr => (nint)_self; + protected readonly IUnrealFactory _factory; + private bool Disposed = false; + + public FActorSpawnParameters_UE5_4_4(IUnrealFactory factory) + { + _factory = factory; + _self = (FActorSpawnParameters*)_factory.Memory.MallocZeroed(sizeof(FActorSpawnParameters)); + } + + public void SetParams(EObjectFlags Flags) + { + _self->ObjectFlags = Flags; + } + + #region DISPOSE INTERFACE + + public void Dispose() + { + Disposing(); + GC.SuppressFinalize(this); + } + + protected virtual void Disposing() + { + if (Disposed) return; + _factory.Memory.Free(Ptr); + Disposed = true; + } + + ~FActorSpawnParameters_UE5_4_4() => Disposing(); + + #endregion +} + +public unsafe class UGameInstance_UE5_4_4(nint ptr, IUnrealFactory factory) + : UObject_UE5_4_4(ptr, factory), IUGameInstance +{ + private readonly UGameInstance* _self = (UGameInstance*)ptr; + + public bool TryGetSubsystem(IUClass Class, out IUObject? Subsystem) + { + var Subsystems = new TMapDictionary, HashablePtr>( + (TMap, HashablePtr>*)(&_self->SubsystemCollection.Subsystems), + factory.Memory); + Subsystem = Subsystems.TryGetValue(new HashablePtr(new Ptr((UClass*)Class.Ptr)), + out var pSubsystem) + ? _factory.CreateUObject((nint)pSubsystem.Value->Ptr.Value) + : null; + return Subsystem != null; + } } \ No newline at end of file diff --git a/UE.Toolkit.Core/Types/Unreal/UE4_27_2/FActorSpawnParameters.cs b/UE.Toolkit.Core/Types/Unreal/UE4_27_2/FActorSpawnParameters.cs new file mode 100644 index 0000000..5e1beb8 --- /dev/null +++ b/UE.Toolkit.Core/Types/Unreal/UE4_27_2/FActorSpawnParameters.cs @@ -0,0 +1,14 @@ +using System.Runtime.InteropServices; +using UE.Toolkit.Core.Types.Unreal.UE5_4_4; + +namespace UE.Toolkit.Core.Types.Unreal.UE4_27_2; + +[StructLayout(LayoutKind.Explicit, Size = 0x30)] +public unsafe struct FActorSpawnParameters +{ + /* A name to assign as the Name of the Actor being spawned. If no value is specified, the name of the spawned Actor will be automatically generated using the form [Class]_[Number]. */ + [FieldOffset(0x0)] public FName Name; + + /* The parent component to set the Actor in. */ + [FieldOffset(0x2c)] public EObjectFlags ObjectFlags; +} \ No newline at end of file diff --git a/UE.Toolkit.Core/Types/Unreal/UE4_27_2/FStaticConstructObjectParameters.cs b/UE.Toolkit.Core/Types/Unreal/UE4_27_2/FStaticConstructObjectParameters.cs new file mode 100644 index 0000000..8e6897a --- /dev/null +++ b/UE.Toolkit.Core/Types/Unreal/UE4_27_2/FStaticConstructObjectParameters.cs @@ -0,0 +1,25 @@ +using System.Runtime.InteropServices; +using EInternalObjectFlags = UE.Toolkit.Core.Types.Unreal.UE5_4_4.EInternalObjectFlags; +using EObjectFlags = UE.Toolkit.Core.Types.Unreal.UE5_4_4.EObjectFlags; +using FName = UE.Toolkit.Core.Types.Unreal.UE5_4_4.FName; + +namespace UE.Toolkit.Core.Types.Unreal.UE4_27_2; + +[StructLayout(LayoutKind.Explicit, Size = 0x40)] +public unsafe struct FStaticConstructObjectParameters +{ + /** The class of the object to create */ + [FieldOffset(0x0)] public UClass* Class; + + /** The object to create this object within (the Outer property for the new object will be set to the value specified here). */ + [FieldOffset(0x8)] public UObjectBase* Outer; + + /** The name to give the new object.If no value(NAME_None) is specified, the object will be given a unique name in the form of ClassName_#. */ + [FieldOffset(0x10)] public FName Name; + + /** The ObjectFlags to assign to the new object. some flags can affect the behavior of constructing the object. */ + [FieldOffset(0x18)] public EObjectFlags SetFlags; + + /** The InternalObjectFlags to assign to the new object. some flags can affect the behavior of constructing the object. */ + [FieldOffset(0x1c)] public EInternalObjectFlags InternalSetFlags; +} \ No newline at end of file diff --git a/UE.Toolkit.Core/Types/Unreal/UE4_27_2/UGameInstance.cs b/UE.Toolkit.Core/Types/Unreal/UE4_27_2/UGameInstance.cs new file mode 100644 index 0000000..0d6ff4b --- /dev/null +++ b/UE.Toolkit.Core/Types/Unreal/UE4_27_2/UGameInstance.cs @@ -0,0 +1,15 @@ +using System.Runtime.InteropServices; + +namespace UE.Toolkit.Core.Types.Unreal.UE4_27_2; + +[StructLayout(LayoutKind.Explicit, Size = 0x1a8)] +public struct UGameInstance +{ + [FieldOffset(0xe0)] public FSubsystemCollection_UGameInstanceSubsystem SubsystemCollection; +} + +[StructLayout(LayoutKind.Explicit, Size = 0xc8)] +public struct FSubsystemCollection_UGameInstanceSubsystem +{ + [FieldOffset(0x10)] public UE5_4_4.TMap, Ptr> Subsystems; +} \ No newline at end of file diff --git a/UE.Toolkit.Core/Types/Unreal/UE5_4_4/FActorSpawnParameters.cs b/UE.Toolkit.Core/Types/Unreal/UE5_4_4/FActorSpawnParameters.cs new file mode 100644 index 0000000..c76d93a --- /dev/null +++ b/UE.Toolkit.Core/Types/Unreal/UE5_4_4/FActorSpawnParameters.cs @@ -0,0 +1,13 @@ +using System.Runtime.InteropServices; + +namespace UE.Toolkit.Core.Types.Unreal.UE5_4_4; + +[StructLayout(LayoutKind.Explicit, Size = 0x80)] +public unsafe struct FActorSpawnParameters +{ + /* A name to assign as the Name of the Actor being spawned. If no value is specified, the name of the spawned Actor will be automatically generated using the form [Class]_[Number]. */ + [FieldOffset(0x0)] public FName Name; + + /* The parent component to set the Actor in. */ + [FieldOffset(0x34)] public EObjectFlags ObjectFlags; +} \ No newline at end of file diff --git a/UE.Toolkit.Core/Types/Unreal/UE5_4_4/FStaticConstructObjectParameters.cs b/UE.Toolkit.Core/Types/Unreal/UE5_4_4/FStaticConstructObjectParameters.cs new file mode 100644 index 0000000..e73136f --- /dev/null +++ b/UE.Toolkit.Core/Types/Unreal/UE5_4_4/FStaticConstructObjectParameters.cs @@ -0,0 +1,22 @@ +using System.Runtime.InteropServices; + +namespace UE.Toolkit.Core.Types.Unreal.UE5_4_4; + +[StructLayout(LayoutKind.Explicit, Size = 0x90)] +public unsafe struct FStaticConstructObjectParameters +{ + /** The class of the object to create */ + [FieldOffset(0x0)] public UClass* Class; + + /** The object to create this object within (the Outer property for the new object will be set to the value specified here). */ + [FieldOffset(0x8)] public UObjectBase* Outer; + + /** The name to give the new object.If no value(NAME_None) is specified, the object will be given a unique name in the form of ClassName_#. */ + [FieldOffset(0x10)] public FName Name; + + /** The ObjectFlags to assign to the new object. some flags can affect the behavior of constructing the object. */ + [FieldOffset(0x18)] public EObjectFlags SetFlags; + + /** The InternalObjectFlags to assign to the new object. some flags can affect the behavior of constructing the object. */ + [FieldOffset(0x1c)] public EInternalObjectFlags InternalSetFlags; +} \ No newline at end of file diff --git a/UE.Toolkit.Core/Types/Unreal/UE5_4_4/TMap.cs b/UE.Toolkit.Core/Types/Unreal/UE5_4_4/TMap.cs index ad2195b..b12e67a 100644 --- a/UE.Toolkit.Core/Types/Unreal/UE5_4_4/TMap.cs +++ b/UE.Toolkit.Core/Types/Unreal/UE5_4_4/TMap.cs @@ -62,13 +62,26 @@ public struct TMapElementHashable public int HashIndex; } -public unsafe struct HashablePtr : IMapHashable, IEquatable> where T: unmanaged +public unsafe struct HashablePtr(Ptr ptr) : IMapHashable, IEquatable> where T: unmanaged { - public Ptr Ptr; - public HashablePtr(Ptr ptr) { Ptr = ptr; } - public uint GetTypeHash() // FUN_140904980 + public Ptr Ptr = ptr; + public uint GetTypeHash() => HashablePtrUtils.GetPtrTypeHash((nint)Ptr.Value); + public bool Equals(HashablePtr other) => Ptr.Value == other.Ptr.Value; +} + +public struct HashableUntypedPtr(nint ptr) : IMapHashable, IEquatable +{ + public nint Ptr = ptr; + public uint GetTypeHash() => HashablePtrUtils.GetPtrTypeHash(Ptr); + + public bool Equals(HashableUntypedPtr other) => Ptr == other.Ptr; +} + +internal static class HashablePtrUtils +{ + internal static uint GetPtrTypeHash(nint Ptr) // FUN_140904980 { - uint iVar4 = (uint)((nint)Ptr.Value >> 4); + uint iVar4 = (uint)(Ptr >> 4); uint uVar3 = 0x9e3779b9U - iVar4 ^ iVar4 << 8; uint uVar1 = (uint)-(uVar3 + iVar4) ^ uVar3 >> 0xd; uint uVar5 = (iVar4 - uVar3) - uVar1 ^ uVar1 >> 0xc; @@ -77,10 +90,10 @@ public uint GetTypeHash() // FUN_140904980 uVar5 = (uVar5 - uVar3) - uVar1 ^ uVar1 >> 3; uVar3 = (uVar3 - uVar5) - uVar1 ^ uVar5 << 10; uint ret = ((uVar1 - uVar3) - uVar5) ^ (uVar3 >> 0xf); - return ret; - } - public bool Equals(HashablePtr other) => Ptr.Value == other.Ptr.Value; + return ret; + } } + public struct HashableInt : IMapHashable, IEquatable { public int Value; @@ -521,7 +534,7 @@ public ICollection> Values public Ptr this[TElemKey key] { - get => new Ptr(TryGetByHash(key)); + get => new(TryGetByHash(key)); set => *TryGetByHash(key) = *value.Value; } @@ -920,10 +933,52 @@ public bool TryGetValue(TElemKey key, out nint pValue) pValue = new(TryGetByHash(key)); return pValue != nint.Zero; } + + public void AddIndirect(TElemKey key, nint pNewItem) + { + if (ContainsKey(key)) return; // Don't allow duplicate keys + if (Hashes == null && Elements.Size + 1 >= MapConstants.MIN_SIZE_FOR_HASH_LIST) Rehash(MapConstants.HASH_INITIAL_SIZE); + else if (Hashes != null && Elements.Size == HashSize) Rehash(HashSize * 2); + if (Elements.Size == Elements.Capacity) ResizeTo(CalculateNewArraySize()); + // Get hash index for new key + if (Hashes != null) + { + var hashIndex = (int)(key.GetTypeHash() & (HashSize - 1)); + if (Hashes[hashIndex] == MapConstants.INVALID_HASH_ID) Hashes[hashIndex] = Elements.Size; + else Elements.SetNextHashId(GetBucketListTail(hashIndex), Elements.Size); + Elements.SetNextHashId(Elements.Size, MapConstants.INVALID_HASH_ID); + Elements.SetHashIndex(Elements.Size, hashIndex); + } + else + { + Elements.SetNextHashId(Elements.Size, Elements.Size - 1); + Elements.SetHashIndex(Elements.Size, 0); + } + // Add a new element to the array + Elements.SetKey(Elements.Size, key); + NativeMemory.Copy((void*)pNewItem, (void*)Elements.GetAddress(Elements.Size), (nuint)Elements.ValueSize); + // Update the bit allocator + BitAllocator.Add(true); + Elements.Size++; + } + + public ICollection Keys + { + get + { + ICollection Keys = new List(); + for (int i = 0; i < Elements.Size; i++) + { + Keys.Add(Elements.GetKey(i)); + } + return Keys; + } + } + + public bool ContainsKey(TElemKey key) => Keys.Contains(key); public int Count => Elements.Size; - /* private void Rehash(int NewSize) { @@ -945,13 +1000,13 @@ private void Rehash(int NewSize) private void ResizeTo(int NewSize) { - nint NewElementAlloc = Allocator.Malloc(NewSize * Elements.SizeOf()); + var NewElementAlloc = (byte*)Allocator.Malloc(NewSize * Elements.SizeOf()); if (Elements.Allocation != null) { - NativeMemory.Copy((byte*)Elements.Allocation, (byte*)NewElementAlloc, (nuint)(Elements.Size * Elements.SizeOf())); + NativeMemory.Copy(Elements.Allocation, NewElementAlloc, (nuint)(Elements.Size * Elements.SizeOf())); Allocator.Free((nint)Elements.Allocation); } - Elements.Allocation = (TMapElementHashable*)NewElementAlloc; + Elements.Allocation = NewElementAlloc; Elements.Capacity = NewSize; } @@ -962,7 +1017,6 @@ private void ResizeTo(int NewSize) public void Leak() => OwnsInstance = false; bool InBounds(int index) => index >= 0 && index < Elements.Size; - */ #region DISPOSE INTERFACE diff --git a/UE.Toolkit.Core/Types/Unreal/UE5_4_4/UGameInstance.cs b/UE.Toolkit.Core/Types/Unreal/UE5_4_4/UGameInstance.cs new file mode 100644 index 0000000..79692eb --- /dev/null +++ b/UE.Toolkit.Core/Types/Unreal/UE5_4_4/UGameInstance.cs @@ -0,0 +1,15 @@ +using System.Runtime.InteropServices; + +namespace UE.Toolkit.Core.Types.Unreal.UE5_4_4; + +[StructLayout(LayoutKind.Explicit, Size = 0x1c0)] +public struct UGameInstance +{ + [FieldOffset(0x100)] public FSubsystemCollection_UGameInstanceSubsystem SubsystemCollection; +} + +[StructLayout(LayoutKind.Explicit, Size = 0xc0)] +public struct FSubsystemCollection_UGameInstanceSubsystem +{ + [FieldOffset(0x10)] public TMap, Ptr> Subsystems; +} \ No newline at end of file diff --git a/UE.Toolkit.Interfaces/IUnrealSpawning.cs b/UE.Toolkit.Interfaces/IUnrealSpawning.cs new file mode 100644 index 0000000..3321190 --- /dev/null +++ b/UE.Toolkit.Interfaces/IUnrealSpawning.cs @@ -0,0 +1,22 @@ +using UE.Toolkit.Core.Types.Unreal.Factories.Interfaces; + +namespace UE.Toolkit.Interfaces; + +public interface IUnrealSpawning +{ + IUObject? SpawnObject(string Name, IUObject? Owner) where TObject : unmanaged; + + IUObject? SpawnObject(string Name, IUClass Class, IUObject? Owner); + + /* + + IUObject? SpawnActor(string Name) where TObject : unmanaged; + + IUObject? SpawnActor(string Name, IUClass Class); + + IUObject? SpawnActor(string Name, IUObject World) where TObject : unmanaged; + + IUObject? SpawnActor(string Name, IUClass Class, IUObject World); + + */ +} \ No newline at end of file diff --git a/UE.Toolkit.Interfaces/IUnrealState.cs b/UE.Toolkit.Interfaces/IUnrealState.cs index e0d77ad..d9c010b 100644 --- a/UE.Toolkit.Interfaces/IUnrealState.cs +++ b/UE.Toolkit.Interfaces/IUnrealState.cs @@ -12,4 +12,9 @@ public interface IUnrealState /// The target world. /// If a target world could be found. public bool GetCurrentPlayWorld(out IUObject? TargetWorld); + + public bool GetSubsystem(IUGameInstance? GameInstance, out IUObject? Subsystem) + where TSubsystem : unmanaged; + + public bool GetSubsystem(IUGameInstance? GameInstance, IUClass? SubsystemType, out IUObject? SubsystemObj); } \ No newline at end of file diff --git a/UE.Toolkit.Reloaded/Mod.cs b/UE.Toolkit.Reloaded/Mod.cs index cdb93a3..f7c5a06 100644 --- a/UE.Toolkit.Reloaded/Mod.cs +++ b/UE.Toolkit.Reloaded/Mod.cs @@ -75,7 +75,7 @@ public Mod(ModContext context) _address = new(); _classes = new(_factory, _memory, _hooks, _address); _methods = new(_factory, _memory, _classes, _objects, _hooks); - _state = new(_factory); + _state = new(_factory, _classes); _modLoader.AddOrReplaceController(_owner, _memory); _modLoader.AddOrReplaceController(_owner, _tables); diff --git a/UE.Toolkit.Reloaded/ObjectWriters/Nodes/DataTableFieldNode.cs b/UE.Toolkit.Reloaded/ObjectWriters/Nodes/DataTableFieldNode.cs index 2c4699e..137e36c 100644 --- a/UE.Toolkit.Reloaded/ObjectWriters/Nodes/DataTableFieldNode.cs +++ b/UE.Toolkit.Reloaded/ObjectWriters/Nodes/DataTableFieldNode.cs @@ -38,7 +38,7 @@ public unsafe void ConsumeNode(XmlReader reader) if (tempTable.TryGetValue(new(id), out var row)) { var rowValuePtr = (nint)row.Value; - if (nodeFactory.TryCreate($"{fieldName} (ID: {id})", rowValuePtr, itemType, out var itemNode)) + if (nodeFactory.TryCreate($"{fieldName} (ID: {id})", rowValuePtr, 0, itemType, out var itemNode)) { var itemTree = subReader.ReadSubtree(); itemTree.MoveToContent(); diff --git a/UE.Toolkit.Reloaded/ObjectWriters/Nodes/FieldNodeFactory.cs b/UE.Toolkit.Reloaded/ObjectWriters/Nodes/FieldNodeFactory.cs index b69bd5b..0af20fa 100644 --- a/UE.Toolkit.Reloaded/ObjectWriters/Nodes/FieldNodeFactory.cs +++ b/UE.Toolkit.Reloaded/ObjectWriters/Nodes/FieldNodeFactory.cs @@ -11,13 +11,13 @@ public class FieldNodeFactory(ITypeRegistry typeReg, IObjectCreator objCreator, public ITypeRegistry TypeRegistry { get; } = typeReg; public IUnrealMemoryInternal Memory { get; } = memory; - public bool TryCreate(string fieldName, nint fieldPtr, Type fieldType, [NotNullWhen(true)]out IFieldNode? node) + public bool TryCreate(string fieldName, nint fieldPtr, int fieldBit, Type fieldType, [NotNullWhen(true)]out IFieldNode? node) { - node = Create(fieldName, fieldPtr, fieldType); + node = Create(fieldName, fieldPtr, fieldBit, fieldType); return node != null; } - private IFieldNode? Create(string fieldName, nint fieldPtr, Type fieldType) + private IFieldNode? Create(string fieldName, nint fieldPtr, int fieldBit, Type fieldType) { Log.Debug($"{nameof(FieldNodeFactory)} || Create Node '{fieldName}' with type '{fieldType.Name}'."); @@ -29,7 +29,7 @@ public bool TryCreate(string fieldName, nint fieldPtr, Type fieldType, [NotNullW || fieldType == typeof(FString) || fieldType == typeof(FName)) { - return new PrimitiveFieldNode(fieldName, fieldPtr, fieldType, objCreator); + return new PrimitiveFieldNode(fieldName, fieldPtr, fieldBit, fieldType, objCreator); } if (fieldType.Name.StartsWith("TArray")) @@ -47,13 +47,49 @@ public bool TryCreate(string fieldName, nint fieldPtr, Type fieldType, [NotNullW var keyType = fieldType.GetGenericArguments()[0]; if (keyType == typeof(int)) { - return new TMapIntFieldNode(fieldName, fieldPtr, fieldType, this); + var valueType = fieldType.GetGenericArguments()[1]; + // Check if it has a StructLayout with an explicit alignment value, this lets us avoid iterating + // through each field to determine alignment + // Every type defined in the extension mod has an alignment value in it's StructLayout + if (valueType.StructLayoutAttribute != null) + { + return valueType.StructLayoutAttribute!.Pack switch + { + <= 4 => new TMapIntFieldNode(fieldName, fieldPtr, fieldType, this), + _ => new TMapInt8FieldNode(fieldName, fieldPtr, fieldType, this) + }; + } + // Fallback: Iterate through fields (this should not be necessary) + var LastOffset = 0; + var bUseInt8 = false; + foreach (var Field in valueType.GetFields()) + { + // Check that the size for each field doesn't exceed 4 bytes + if (Marshal.SizeOf(Field.GetType()) > 4) + { + bUseInt8 = true; + break; + } + } + return bUseInt8 switch + { + true => new TMapInt8FieldNode(fieldName, fieldPtr, fieldType, this), + false => new TMapIntFieldNode(fieldName, fieldPtr, fieldType, this), + }; } - Log.Warning($"{nameof(FieldNodeFactory)} || TODO: Field '{fieldName}' with type '{fieldType.Name}'."); - foreach (var Generic in fieldType.GenericTypeArguments) + + if (keyType == typeof(FName)) + { + return new TMapNameFieldNode(fieldName, fieldPtr, fieldType, this); + } + + /* + if (keyType.Name.StartsWith("Ptr")) // TODO { - Log.Warning($"{nameof(FieldNodeFactory)} || TMap Argument: {Generic.Name}"); + } + */ + Log.Warning($"{nameof(FieldNodeFactory)} || Field '{fieldName}' with type '{fieldType.Name}' is not currently supported for map editing operations."); return null; } diff --git a/UE.Toolkit.Reloaded/ObjectWriters/Nodes/PrimitiveFieldNode.cs b/UE.Toolkit.Reloaded/ObjectWriters/Nodes/PrimitiveFieldNode.cs index c17d113..6cdd19b 100644 --- a/UE.Toolkit.Reloaded/ObjectWriters/Nodes/PrimitiveFieldNode.cs +++ b/UE.Toolkit.Reloaded/ObjectWriters/Nodes/PrimitiveFieldNode.cs @@ -4,9 +4,10 @@ namespace UE.Toolkit.Reloaded.ObjectWriters.Nodes; -public class PrimitiveFieldNode(string fieldName, nint fieldPtr, Type fieldType, IObjectCreator objCreator, string? baseObjsDir = null) : IFieldNode +public class PrimitiveFieldNode(string fieldName, nint fieldPtr, int fieldBit, Type fieldType, + IObjectCreator objCreator, string? baseObjsDir = null) : IFieldNode { - private readonly IFieldWriter? _writer = FieldWriterFactory.Create(fieldName, fieldPtr, fieldType, objCreator); + private readonly IFieldWriter? _writer = FieldWriterFactory.Create(fieldName, fieldPtr, fieldBit, fieldType, objCreator); public void ConsumeNode(XmlReader reader) { diff --git a/UE.Toolkit.Reloaded/ObjectWriters/Nodes/StructFieldNode.cs b/UE.Toolkit.Reloaded/ObjectWriters/Nodes/StructFieldNode.cs index 065e0bd..fafb9e1 100644 --- a/UE.Toolkit.Reloaded/ObjectWriters/Nodes/StructFieldNode.cs +++ b/UE.Toolkit.Reloaded/ObjectWriters/Nodes/StructFieldNode.cs @@ -3,7 +3,7 @@ namespace UE.Toolkit.Reloaded.ObjectWriters.Nodes; -internal record FieldData(Type type, nint offset); +internal record FieldData(Type type, nint offset, int bit); public class StructFieldNode : IFieldNode { @@ -18,11 +18,18 @@ public class StructFieldNode : IFieldNode private static Dictionary GetFieldsFromStruct(Type structType) { var FieldList = new Dictionary(); + int? LastOffset = null; + int CurrentBit = 0; foreach (var Field in structType.GetFields().SelectMany((x, i) => { if (i == 0 && x.Name == "Super") return GetFieldsFromStruct(x.FieldType).AsEnumerable(); + // Handle cases where sequences of booleans are in bitflag form (1 bit) instead of C form (1 byte) + // We'll need to save the current bit as well to correctly write values for Object XML + var CurrentOffset = (int)Marshal.OffsetOf(structType, x.Name); + CurrentBit = x.FieldType == typeof(bool) && LastOffset == CurrentOffset ? CurrentBit + 1 : 0; + LastOffset = CurrentOffset; return Enumerable.Repeat>(new( - x.Name, new(x.FieldType, Marshal.OffsetOf(structType, x.Name))), 1); + x.Name, new(x.FieldType, CurrentOffset, CurrentBit)), 1); })) { // ToDictionary() implementation will crash if there are duplicate field names @@ -68,7 +75,7 @@ public void ConsumeNode(XmlReader reader) var fieldType = fieldData.type; // var fieldPtr = _structPtr + Marshal.OffsetOf(_structType, fieldName); var fieldPtr = _structPtr + fieldData.offset; - if (_nodeFactory.TryCreate(fieldName, fieldPtr, fieldType, out var fieldNode)) + if (_nodeFactory.TryCreate(fieldName, fieldPtr, fieldData.bit, fieldType, out var fieldNode)) { fieldNode.ConsumeNode(reader); } diff --git a/UE.Toolkit.Reloaded/ObjectWriters/Nodes/TArrayFieldNode.cs b/UE.Toolkit.Reloaded/ObjectWriters/Nodes/TArrayFieldNode.cs index 06f6b55..e175cc9 100644 --- a/UE.Toolkit.Reloaded/ObjectWriters/Nodes/TArrayFieldNode.cs +++ b/UE.Toolkit.Reloaded/ObjectWriters/Nodes/TArrayFieldNode.cs @@ -59,7 +59,7 @@ public unsafe void ConsumeNode(XmlReader reader) } var itemPtr = itemIdx * itemSize + (nint)tempArray->AllocatorInstance; - if (nodeFactory.TryCreate($"{fieldName} (ID: {id})", itemPtr, itemType, out var itemNode)) + if (nodeFactory.TryCreate($"{fieldName} (ID: {id})", itemPtr, 0, itemType, out var itemNode)) { itemNode.ConsumeNode(subReader); } diff --git a/UE.Toolkit.Reloaded/ObjectWriters/Nodes/TMapFieldNode.cs b/UE.Toolkit.Reloaded/ObjectWriters/Nodes/TMapFieldNode.cs index 1726984..6d104b4 100644 --- a/UE.Toolkit.Reloaded/ObjectWriters/Nodes/TMapFieldNode.cs +++ b/UE.Toolkit.Reloaded/ObjectWriters/Nodes/TMapFieldNode.cs @@ -1,20 +1,21 @@ using System.Runtime.InteropServices; using System.Xml; using UE.Toolkit.Core.Types; +using UE.Toolkit.Core.Types.Interfaces; using UE.Toolkit.Core.Types.Unreal.UE5_4_4; +using UE.Toolkit.Interfaces; namespace UE.Toolkit.Reloaded.ObjectWriters.Nodes; -public class TMapIntFieldNode(string fieldName, nint fieldPtr, Type fieldType, FieldNodeFactory nodeFactory) : IFieldNode +public abstract class TMapBaseFieldNode(string fieldName, nint fieldPtr, Type fieldType, FieldNodeFactory nodeFactory) + : IFieldNode where TKeyValue : unmanaged, IMapHashable, IEquatable { - public unsafe void ConsumeNode(XmlReader reader) + public void ConsumeNode(XmlReader reader) { - // TMap item type var valueType = fieldType.GetGenericArguments()[1]; var valueSize = Marshal.SizeOf(valueType); - - // TMap values are always by-reference. - var tempMap = new TMapDynamicDictionary((TMap*)fieldPtr, valueType, nodeFactory.Memory); + + var tempMap = CreateTempMap(fieldPtr, valueType, nodeFactory.Memory); // Get any item nodes. using var subReader = reader.ReadSubtree(); @@ -23,44 +24,99 @@ public unsafe void ConsumeNode(XmlReader reader) while (subReader.Read()) { if (subReader.NodeType != XmlNodeType.Element) continue; - if (subReader.Name != WriterConstants.ItemTag) throw new($"Only '{WriterConstants.ItemTag}' elements can be directly inside a map. Found: {subReader.Name}"); - + if (subReader.Name != WriterConstants.ItemTag) + throw new( + $"Only '{WriterConstants.ItemTag}' elements can be directly inside a map. Found: {subReader.Name}"); + var id = subReader.GetAttribute(WriterConstants.ItemIdAttr); if (id == null) { Log.Error($"{nameof(TArrayFieldNode)} || '{WriterConstants.ItemTag}' is missing an ID."); break; } - - if (!int.TryParse(id, out var itemIdx)) + + if (!CreateKeyValue(id, out var KeyMaybe)) { - Log.Warning($"{nameof(TMapIntFieldNode)} || Invalid ID: {id}"); - break; + Log.Error($"{nameof(TArrayFieldNode)} || Could not process map key {id}"); + break; } - + var Key = KeyMaybe!.Value; // Get existing value - /* - if (!tempMap.ContainsKey(new HashableInt8(itemIdx))) + if (!ContainsKey(tempMap, Key)) { - var newEntry = new Ptr((nint*)nodeFactory.Memory.MallocZeroed(valueSize)); - tempMap.Add(new HashableInt8(itemIdx), newEntry); - Log.Debug($"{nameof(TMapIntFieldNode)} || Added entry at 0x{(nint)newEntry.Value:x} with key '{itemIdx}' into '{fieldName}'"); + var newEntry = nodeFactory.Memory.MallocZeroed(valueSize); + tempMap.AddIndirect(Key, newEntry); + nodeFactory.Memory.Free(newEntry); + Log.Debug($"{nameof(TMapBaseFieldNode)} || Added entry at 0x{newEntry:x} with key '{Key}' into '{fieldName}'"); } - */ - if (tempMap.TryGetValue(new HashableInt8(itemIdx), out var valuePtr)) + if (tempMap.TryGetValue(Key, out var valuePtr) && + nodeFactory.TryCreate($"{fieldName} (Key: {Key})", valuePtr, 0, valueType, out var itemNode)) { - if (nodeFactory.TryCreate($"{fieldName} (Key: {itemIdx})", valuePtr, valueType, out var itemNode)) - { - var itemTree = subReader.ReadSubtree(); - itemTree.MoveToContent(); - - itemNode.ConsumeNode(itemTree); - } + var itemTree = subReader.ReadSubtree(); + itemTree.MoveToContent(); + itemNode.ConsumeNode(itemTree); } } - - Log.Verbose($"{nameof(TMapIntFieldNode)} || Field '{fieldName}' node consumed."); + } + + public abstract TMapDynamicDictionary CreateTempMap( + nint fieldPtr, Type valueType, IUnrealMemoryInternal memory); + + public abstract bool CreateKeyValue(string id, out TKeyValue? Value); + + public virtual bool ContainsKey(TMapDynamicDictionary dict, TKeyValue key) + => dict.ContainsKey(key); +} + +public class TMapIntFieldNode(string fieldName, nint fieldPtr, Type fieldType, FieldNodeFactory nodeFactory) + : TMapBaseFieldNode(fieldName, fieldPtr, fieldType, nodeFactory) +{ + public override unsafe TMapDynamicDictionary CreateTempMap(nint fieldPtr, Type valueType, IUnrealMemoryInternal memory) + => new((TMap*)fieldPtr, valueType, nodeFactory.Memory); + + public override bool CreateKeyValue(string id, out HashableInt? Value) + { + Value = null; + if (!int.TryParse(id, out var itemIdx)) + { + Log.Warning($"{nameof(TMapIntFieldNode)} || Invalid ID: {id}"); + return false; + } + Value = new HashableInt(itemIdx); + return true; + } +} + +public class TMapInt8FieldNode(string fieldName, nint fieldPtr, Type fieldType, FieldNodeFactory nodeFactory) + : TMapBaseFieldNode(fieldName, fieldPtr, fieldType, nodeFactory) +{ + public override unsafe TMapDynamicDictionary CreateTempMap(nint fieldPtr, Type valueType, IUnrealMemoryInternal memory) + => new((TMap*)fieldPtr, valueType, nodeFactory.Memory); + + public override bool CreateKeyValue(string id, out HashableInt8? Value) + { + Value = null; + if (!int.TryParse(id, out var itemIdx)) + { + Log.Warning($"{nameof(TMapInt8FieldNode)} || Invalid ID: {id}"); + return false; + } + Value = new HashableInt8(itemIdx); + return true; + } +} + +public class TMapNameFieldNode(string fieldName, nint fieldPtr, Type fieldType, FieldNodeFactory nodeFactory) + : TMapBaseFieldNode(fieldName, fieldPtr, fieldType, nodeFactory) +{ + public override unsafe TMapDynamicDictionary CreateTempMap(nint fieldPtr, Type valueType, IUnrealMemoryInternal memory) + => new((TMap*)fieldPtr, valueType, nodeFactory.Memory); + + public override bool CreateKeyValue(string id, out FName? Value) + { + Value = new FName(id); + return true; } } \ No newline at end of file diff --git a/UE.Toolkit.Reloaded/ObjectWriters/ObjectWriter.cs b/UE.Toolkit.Reloaded/ObjectWriters/ObjectWriter.cs index a90af4e..02b9a19 100644 --- a/UE.Toolkit.Reloaded/ObjectWriters/ObjectWriter.cs +++ b/UE.Toolkit.Reloaded/ObjectWriters/ObjectWriter.cs @@ -54,7 +54,7 @@ public void WriteToObject(nint objPtr) // TODO: Possibly rework XML node tree creation to return // a collection of generated writers to allow resetting values on rewrites. - if (_nodeFactory.TryCreate(ObjectName, objPtr, _objType, out var rootNode)) + if (_nodeFactory.TryCreate(ObjectName, objPtr, 0, _objType, out var rootNode)) { rootNode.ConsumeNode(reader); } diff --git a/UE.Toolkit.Reloaded/ObjectWriters/Writers/FieldWriterFactory.cs b/UE.Toolkit.Reloaded/ObjectWriters/Writers/FieldWriterFactory.cs index a2d1225..18bc8d6 100644 --- a/UE.Toolkit.Reloaded/ObjectWriters/Writers/FieldWriterFactory.cs +++ b/UE.Toolkit.Reloaded/ObjectWriters/Writers/FieldWriterFactory.cs @@ -5,10 +5,11 @@ namespace UE.Toolkit.Reloaded.ObjectWriters.Writers; public static class FieldWriterFactory { - public static IFieldWriter? Create(string fieldName, nint fieldPtr, Type fieldType, IObjectCreator objCreator) + + public static IFieldWriter? Create(string fieldName, nint fieldPtr, int fieldBit, Type fieldType, IObjectCreator objCreator) { if (fieldType.IsPrimitive || fieldType.Name == nameof(String)) - return new PrimitiveFieldWriter(fieldName, fieldPtr, fieldType); + return new PrimitiveFieldWriter(fieldName, fieldPtr, fieldBit, fieldType); if (fieldType == typeof(FText) || fieldType == typeof(FString) || fieldType == typeof(FName)) return new TextFieldWriter(fieldName, fieldPtr, fieldType, objCreator); diff --git a/UE.Toolkit.Reloaded/ObjectWriters/Writers/PrimitiveFieldWriter.cs b/UE.Toolkit.Reloaded/ObjectWriters/Writers/PrimitiveFieldWriter.cs index 3c2427c..58f4aa8 100644 --- a/UE.Toolkit.Reloaded/ObjectWriters/Writers/PrimitiveFieldWriter.cs +++ b/UE.Toolkit.Reloaded/ObjectWriters/Writers/PrimitiveFieldWriter.cs @@ -1,9 +1,10 @@ namespace UE.Toolkit.Reloaded.ObjectWriters.Writers; -public unsafe class PrimitiveFieldWriter(string fieldName, nint fieldPtr, Type fieldType) : IFieldWriter +public unsafe class PrimitiveFieldWriter(string fieldName, nint fieldPtr, int fieldBit, Type fieldType) : IFieldWriter { private nint? _ogValue; private Type _fieldType = fieldType; + private int _fieldBit = fieldBit; public void Reset() { @@ -76,7 +77,8 @@ public void SetField(TValue value) *(sbyte*)fieldPtr = Convert.ToSByte(value); break; case "Boolean": - *(bool*)fieldPtr = Convert.ToBoolean(value); + var Value = Convert.ToBoolean(value); + *(byte*)fieldPtr |= (byte)(*(byte*)&Value << _fieldBit); break; case "Single": *(float*)fieldPtr = Convert.ToSingle(value); diff --git a/UE.Toolkit.Reloaded/Project/UE.Toolkit.Reloaded/p3r/scans.ini b/UE.Toolkit.Reloaded/Project/UE.Toolkit.Reloaded/p3r/scans.ini index 80dd5c2..747dacc 100644 --- a/UE.Toolkit.Reloaded/Project/UE.Toolkit.Reloaded/p3r/scans.ini +++ b/UE.Toolkit.Reloaded/Project/UE.Toolkit.Reloaded/p3r/scans.ini @@ -50,4 +50,7 @@ ConstructUScriptStruct=40 53 56 57 41 54 41 55 41 56 48 81 EC 08 03 00 00 ;ConstructUScriptStruct_RESULT= GEngine=48 89 05 ?? ?? ?? ?? 48 85 C9 74 ?? E8 ?? ?? ?? ?? 48 8D 4D ?? -GEngine_RESULT=GetGlobalAddress(result + 3) \ No newline at end of file +GEngine_RESULT=GetGlobalAddress(result + 3) + +UWorld_SpawnActor=40 55 53 56 57 41 54 41 55 41 56 41 57 48 8D AC 24 ?? ?? ?? ?? 48 81 EC F8 01 00 00 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 45 ?? +;UWorld_SpawnActor_RESULT= \ No newline at end of file diff --git a/UE.Toolkit.Reloaded/Project/UE.Toolkit.Reloaded/scans.ini b/UE.Toolkit.Reloaded/Project/UE.Toolkit.Reloaded/scans.ini index d3cb1e2..2a10aca 100644 --- a/UE.Toolkit.Reloaded/Project/UE.Toolkit.Reloaded/scans.ini +++ b/UE.Toolkit.Reloaded/Project/UE.Toolkit.Reloaded/scans.ini @@ -52,4 +52,7 @@ ConstructUScriptStruct=40 53 56 57 41 54 41 55 41 56 48 81 EC 98 03 00 00 ;ConstructUScriptStruct GEngine=48 89 05 ?? ?? ?? ?? 48 85 C9 74 ?? E8 ?? ?? ?? ?? 48 8D 4D ?? -GEngine_RESULT=GetGlobalAddress(result + 3) \ No newline at end of file +GEngine_RESULT=GetGlobalAddress(result + 3) + +UWorld_SpawnActor=40 55 53 56 57 41 54 41 55 41 56 41 57 48 8D AC 24 ?? ?? ?? ?? 48 81 EC A8 04 00 00 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 85 ?? ?? ?? ?? 33 C0 +;UWorld_SpawnActor_RESULT= \ No newline at end of file diff --git a/UE.Toolkit.Reloaded/Unreal/UnrealSpawning.cs b/UE.Toolkit.Reloaded/Unreal/UnrealSpawning.cs new file mode 100644 index 0000000..427e163 --- /dev/null +++ b/UE.Toolkit.Reloaded/Unreal/UnrealSpawning.cs @@ -0,0 +1,59 @@ +using UE.Toolkit.Core.Types.Unreal.Factories; +using UE.Toolkit.Core.Types.Unreal.Factories.Interfaces; +using UE.Toolkit.Core.Types.Unreal.UE5_4_4; +using UE.Toolkit.Interfaces; +// using UE.Toolkit.Reloaded.Common.GameConfigs; + +namespace UE.Toolkit.Reloaded.Unreal; + +public class UnrealSpawning : IUnrealSpawning +{ + private IUnrealClasses Classes; + private IUnrealFactory Factory; + private IUnrealState State; + + private delegate nint StaticConstructObject_Internal(nint pParams); + + private SHFunction _StaticConstructObjectInternal; + + private delegate nint UWorld_SpawnActor(nint pSelf, nint pActorClass, nint pTransform, nint spawnParams); + + private SHFunction _SpawnActor; + + public IUObject? SpawnObject(string Name, IUObject? Owner) where TObject : unmanaged + => Classes.GetClassInfoFromClass(out var Class) ? SpawnObject(Name, Class, Owner) : null; + + public IUObject? SpawnObject(string Name, IUClass Class, IUObject? Owner) + { + var Params = Factory.CreateFStaticConstructObjectParameters(); + Params.SetParams(Class, Owner, new FName(Name)); + return Factory.CreateUObject(_StaticConstructObjectInternal.Wrapper(Params.Ptr)); + } + + /* + public IUObject? SpawnActor(string Name) where TObject : unmanaged + => Classes.GetClassInfoFromClass(out var Class) ? SpawnActor(Name, Class) : null; + + public IUObject? SpawnActor(string Name, IUClass Class) + => State.GetCurrentPlayWorld(out var World) ? SpawnActor(Name, Class, World) : null; + + public IUObject? SpawnActor(string Name, IUObject World) where TObject : unmanaged + => Classes.GetClassInfoFromClass(out var Class) ? SpawnActor(Name, Class, World) : null; + + public IUObject? SpawnActor(string Name, IUClass Class, IUObject World) + { + var SpawnParams = Factory.CreateFActorSpawnParameters(); + SpawnParams.SetParams(EObjectFlags.RF_Transactional); + return Factory.CreateUObject(_SpawnActor.Wrapper(World.Ptr, Class.Ptr, nint.Zero, SpawnParams.Ptr)); + } + */ + + public UnrealSpawning(IUnrealClasses classes, IUnrealFactory factory, IUnrealState state) + { + Classes = classes; + Factory = factory; + State = state; + + _StaticConstructObjectInternal = new(); + } +} \ No newline at end of file diff --git a/UE.Toolkit.Reloaded/Unreal/UnrealState.cs b/UE.Toolkit.Reloaded/Unreal/UnrealState.cs index e179afc..33d320c 100644 --- a/UE.Toolkit.Reloaded/Unreal/UnrealState.cs +++ b/UE.Toolkit.Reloaded/Unreal/UnrealState.cs @@ -2,33 +2,64 @@ using UE.Toolkit.Core.Types.Unreal.Factories.Interfaces; using WorldType = UE.Toolkit.Core.Types.Unreal.UE5_4_4.WorldType; using UE.Toolkit.Interfaces; +using UE.Toolkit.Reloaded.Common.GameConfigs; namespace UE.Toolkit.Reloaded.Unreal; public class UnrealState : IUnrealState { private IUnrealFactory Factory; + private IUnrealClasses Classes; private nint GEngine; - // See UEngine::GetCurrentPlayWorld + // Based on UE's implementation of UEngine::GetCurrentPlayWorld public unsafe bool GetCurrentPlayWorld(out IUObject? TargetWorld) { TargetWorld = null; if (GEngine == nint.Zero) return false; - var World = Factory.CreateUEngine(*(nint*)GEngine); - foreach (var WorldContext in World.GetWorldList()) + var UEngine = Factory.CreateUEngine(*(nint*)GEngine); + IUObject? NoneWorld = null; + foreach (var WorldContext in UEngine.GetWorldList()) { - if (WorldContext.GetWorldType() == WorldType.Game && WorldContext.GetWorld() != nint.Zero) + if (WorldContext.GetWorld() == nint.Zero) continue; + switch (WorldContext.GetWorldType()) { - TargetWorld = Factory.CreateUObject(WorldContext.GetWorld()); + case WorldType.Game: + TargetWorld = Factory.CreateUObject(WorldContext.GetWorld()); + break; + case WorldType.None: + NoneWorld = Factory.CreateUObject(WorldContext.GetWorld()); + break; } } + // For Persona 3 Reload, there is only one active world containing a list of streamed sublevels. + // This may be true for other games as well, I haven't checked + if (GameConfig.Instance.Id == "P3R") + { + TargetWorld = NoneWorld; + } return TargetWorld != null; } - public UnrealState(IUnrealFactory _Factory) + public bool GetSubsystem(IUGameInstance? GameInstance, out IUObject? Subsystem) + where TSubsystem : unmanaged + { + if (!Classes.GetClassInfoFromClass(out var Class)) + { + Subsystem = null; + return false; + } + return GetSubsystem(GameInstance, Class, out Subsystem); + } + + public bool GetSubsystem(IUGameInstance? GameInstance, IUClass? SubsystemType, out IUObject? SubsystemObj) + => GameInstance.TryGetSubsystem(SubsystemType, out SubsystemObj); + + public UnrealState(IUnrealFactory _Factory, IUnrealClasses _Classes) { + Factory = _Factory; + Classes = _Classes; Project.Scans.AddListener("GEngine", x => GEngine = x); } } \ No newline at end of file From 714c15c5547c553867df0431583fab873e75c3d0 Mon Sep 17 00:00:00 2001 From: Rirurin Date: Wed, 26 Nov 2025 14:33:48 +0800 Subject: [PATCH 09/12] Actually create IUnrealSpawning interface --- UE.Toolkit.Reloaded/Mod.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/UE.Toolkit.Reloaded/Mod.cs b/UE.Toolkit.Reloaded/Mod.cs index f7c5a06..0863f9c 100644 --- a/UE.Toolkit.Reloaded/Mod.cs +++ b/UE.Toolkit.Reloaded/Mod.cs @@ -45,6 +45,7 @@ public class Mod : ModBase, IExports private readonly Common.ResolveAddress _address; private readonly UnrealMethods _methods; private readonly UnrealState _state; + private readonly UnrealSpawning _spawning; public Mod(ModContext context) { @@ -64,10 +65,11 @@ public Mod(ModContext context) GameConfig.SetGame(_modLoader.GetAppConfig().AppId); _factory = GameConfig.Instance.Factory; + _memory = GameConfig.Instance.Memory; + _factory.Memory = _memory; _names = new(); _objects = new(_factory); _tables = new(); - _memory = GameConfig.Instance.Memory; _typeRegistry = new(); _writer = new(_typeRegistry, _objects, _tables, _memory); _toolkit = new(_writer); @@ -76,6 +78,7 @@ public Mod(ModContext context) _classes = new(_factory, _memory, _hooks, _address); _methods = new(_factory, _memory, _classes, _objects, _hooks); _state = new(_factory, _classes); + _spawning = new(_classes, _factory, _state); _modLoader.AddOrReplaceController(_owner, _memory); _modLoader.AddOrReplaceController(_owner, _tables); @@ -88,6 +91,7 @@ public Mod(ModContext context) _modLoader.AddOrReplaceController(_owner, _factory); _modLoader.AddOrReplaceController(_owner, _methods); _modLoader.AddOrReplaceController(_owner, _state); + _modLoader.AddOrReplaceController(_owner, _spawning); _modLoader.ModLoaded += OnModLoaded; } @@ -128,6 +132,6 @@ public Type[] GetTypes() => [ typeof(IDataTables), typeof(IUnrealObjects), typeof(IToolkit), typeof(ITypeRegistry), typeof(UObjectBase), typeof(IUnrealFactory), typeof(IUnrealNames), typeof(IUnrealMemory), typeof(IUnrealStrings), - typeof(IUnrealClasses), typeof(IUnrealMethods), typeof(IUnrealState) + typeof(IUnrealClasses), typeof(IUnrealMethods), typeof(IUnrealState), typeof(IUnrealSpawning) ]; } \ No newline at end of file From 4beb30352404db3ba7b37320cbf06ded876a6a9c Mon Sep 17 00:00:00 2001 From: Rirurin Date: Fri, 5 Dec 2025 20:22:17 +0800 Subject: [PATCH 10/12] Handle FNames with non-zero Number value, fix insertion for Data Table nodes (Object XML) --- UE.Toolkit.Core/Types/Unreal/UE5_4_4/FName.cs | 9 ++++-- .../Types/Unreal/UE5_4_4/TBitArray.cs | 27 ++++++++-------- UE.Toolkit.Core/Types/Unreal/UE5_4_4/TMap.cs | 32 +++++++++++++++---- .../ObjectWriters/Nodes/DataTableFieldNode.cs | 28 ++++++++-------- .../ObjectWriters/Nodes/TArrayFieldNode.cs | 8 ++--- .../ObjectWriters/Nodes/TMapFieldNode.cs | 14 ++++++-- 6 files changed, 70 insertions(+), 48 deletions(-) diff --git a/UE.Toolkit.Core/Types/Unreal/UE5_4_4/FName.cs b/UE.Toolkit.Core/Types/Unreal/UE5_4_4/FName.cs index 67343b9..6578dbf 100644 --- a/UE.Toolkit.Core/Types/Unreal/UE5_4_4/FName.cs +++ b/UE.Toolkit.Core/Types/Unreal/UE5_4_4/FName.cs @@ -112,7 +112,10 @@ public override string ToString() return base.ToString()!; } - return GetFNameEntry()->ToString(); + string Output = GetFNameEntry()->ToString(); + if (Number.Value != 0) + Output += $"_{Number.Value - 1}"; + return Output; } private FNameEntry* GetFNameEntry() @@ -126,9 +129,9 @@ public override string ToString() private static nint GetPool(uint poolIdx) => *((nint*)(GFNamePool + 1) + poolIdx); - public uint GetTypeHash() => ComparisonIndex.GetTypeHash(); + public uint GetTypeHash() => ComparisonIndex.GetTypeHash() + Number.Value; - public bool Equals(FName other) => ComparisonIndex.Equals(other.ComparisonIndex); + public bool Equals(FName other) => ComparisonIndex.Equals(other.ComparisonIndex) && Number.Equals(other.Number); } public unsafe delegate FName FNameHelper_FindOrStoreString(FNameStringView* view, EFindName findType); diff --git a/UE.Toolkit.Core/Types/Unreal/UE5_4_4/TBitArray.cs b/UE.Toolkit.Core/Types/Unreal/UE5_4_4/TBitArray.cs index 5addc6b..ae64008 100644 --- a/UE.Toolkit.Core/Types/Unreal/UE5_4_4/TBitArray.cs +++ b/UE.Toolkit.Core/Types/Unreal/UE5_4_4/TBitArray.cs @@ -140,31 +140,29 @@ void InsertInner(int index, bool item, bool add) if (add) { // Allow assigning to this[ArrayNum] to add a new entry - if (!InBoundsForInsertion(index)) - { - return; - } - if (ArrayNum == ArrayMax) - { - Resize(); - } - // Shift elements to the right + if (!InBoundsForInsertion(index)) return; + if (ArrayNum == ArrayMax) Resize(); + // Shifts bits if we're inserting into an index other than ArrayNum + // Shift bytes to the right var StartIndex = ArrayNum / TBitArrayConstants.BITS_PER_BYTE; var EndIndex = index / TBitArrayConstants.BITS_PER_BYTE; - for (int i = StartIndex; i >= EndIndex; i--) + for (var i = StartIndex; i > EndIndex; i--) { Data[i + 1] |= (byte)(Data[i] >> 7); Data[i] <<= 1; } + // Shifts bits to the right + var EndBit = index % TBitArrayConstants.BITS_PER_BYTE; + byte BitMaskGen() => (byte)((1 << EndBit) - 1); + var NoShiftBits = Data[EndIndex] & BitMaskGen(); + var ShiftBits = (Data[EndIndex] & ~BitMaskGen()) << 1; + Data[EndIndex] = (byte)(NoShiftBits | ShiftBits); ArrayNum++; } else { // We're only replacing existing entries, so this[ArrayNum] is out of bounds - if (!InBounds(index)) - { - return; - } + if (!InBounds(index)) return; } if (item) { @@ -203,6 +201,7 @@ public void Clear() { NativeMemory.Clear(Data, (nuint)ArrayMax / TBitArrayConstants.BITS_PER_BYTE); ArrayNum = 0; + ArrayMax = InlineBits; } public bool Contains(bool item) diff --git a/UE.Toolkit.Core/Types/Unreal/UE5_4_4/TMap.cs b/UE.Toolkit.Core/Types/Unreal/UE5_4_4/TMap.cs index b12e67a..fe2c14e 100644 --- a/UE.Toolkit.Core/Types/Unreal/UE5_4_4/TMap.cs +++ b/UE.Toolkit.Core/Types/Unreal/UE5_4_4/TMap.cs @@ -311,16 +311,16 @@ public unsafe class TMapDictionary : IDictionary0x10 /// /// - /// TMapFreeListIndex* Free List + /// int FirstFreeIndex /// 0x30 /// /// - /// int FirstFreeIndex - /// 0x38 + /// int NumFreeIndices + /// 0x34 /// /// - /// int NumFreeIndices - /// 0x3c + /// TMapFreeListIndex* Free List + /// 0x38 /// /// /// @@ -338,11 +338,11 @@ public unsafe class TMapDictionary : IDictionary /// /// - public nint Self { get; private set; } + public nint Self { get; } protected IUnrealMemoryInternal Allocator; protected TMapElementAccessor Elements; - protected TBitArrayList BitAllocator; + protected TBitArrayList BitAllocator { get; } protected Action? DebugCallback; protected bool OwnsInstance; protected bool Disposed = false; @@ -408,6 +408,22 @@ public TMapDictionary(TMap* _Self, IUnrealMemoryInternal _ DebugCallback = _DebugCallback; } + /// + /// Allocate a new TMap owned by this TMapDictionary instance. It will be garbage collected once + /// taken out of scope. + /// + /// The Unreal allocator, used for methods that modify the TMap + public TMapDictionary(IUnrealMemoryInternal _Allocator) + { + Self = _Allocator.MallocZeroed(SizeOf); + Allocator = _Allocator; + Elements = new(ElementsRaw, Allocator); + OwnsInstance = true; + BitAllocator = new(BitAllocatorRaw, Allocator); + // Sets ArrayMax for BitAllocator to number of inline bits (128) + BitAllocator.Clear(); + } + private TElemValue* TryGetLinear(TElemKey key) { if (Elements.Size == 0) return null; @@ -566,6 +582,8 @@ public void Add(TElemKey key, Ptr value) Elements.Size++; } + public void AddValue(TElemKey key, TElemValue value) => Add(key, new(&value)); + public bool ContainsKey(TElemKey key) => Keys.Contains(key); public bool Remove(TElemKey key) diff --git a/UE.Toolkit.Reloaded/ObjectWriters/Nodes/DataTableFieldNode.cs b/UE.Toolkit.Reloaded/ObjectWriters/Nodes/DataTableFieldNode.cs index 137e36c..5632aa5 100644 --- a/UE.Toolkit.Reloaded/ObjectWriters/Nodes/DataTableFieldNode.cs +++ b/UE.Toolkit.Reloaded/ObjectWriters/Nodes/DataTableFieldNode.cs @@ -17,7 +17,7 @@ public unsafe void ConsumeNode(XmlReader reader) // DataTable map type is always FName + Pointer, can just use UObject. // var tempTable = new ToolkitDataTable((UDataTable*)fieldPtr); - var tempTable = new UDataTableManaged((UDataTable*)fieldPtr, nodeFactory.Memory); + var tempTable = new UDataTableManaged>((UDataTable>*)fieldPtr, nodeFactory.Memory); // Get any item nodes. using var subReader = reader.ReadSubtree(); @@ -34,23 +34,21 @@ public unsafe void ConsumeNode(XmlReader reader) Log.Error($"{nameof(DataTableFieldNode)} || '{WriterConstants.ItemTag}' in field '{fieldName}' is missing an ID."); break; } - - if (tempTable.TryGetValue(new(id), out var row)) + + var Key = new FName(id); + if (!tempTable.ContainsKey(Key)) { - var rowValuePtr = (nint)row.Value; - if (nodeFactory.TryCreate($"{fieldName} (ID: {id})", rowValuePtr, 0, itemType, out var itemNode)) - { - var itemTree = subReader.ReadSubtree(); - itemTree.MoveToContent(); - - itemNode.ConsumeNode(itemTree); - } + tempTable.AddRow(Key, new Ptr((UObjectBase*)nodeFactory.Memory.MallocZeroed(itemSize))); + // tempTable.Add(Key, new Ptr((UObjectBase*)nodeFactory.Memory.MallocZeroed(itemSize))); + Log.Debug($"{nameof(DataTableFieldNode)} || Added row with ID '{id}' into '{fieldName}'."); } - else + + if (tempTable.TryGetValue(Key, out var row) + && nodeFactory.TryCreate($"{fieldName} (ID: {id})", (nint)row.Value->Value, 0, itemType, out var itemNode)) { - tempTable.Add(new (id), new Ptr((UObjectBase*)nodeFactory.Memory.MallocZeroed(itemSize))); - Log.Debug($"{nameof(DataTableFieldNode)} || Added row with ID '{id}' into '{fieldName}'."); - break; + var itemTree = subReader.ReadSubtree(); + itemTree.MoveToContent(); + itemNode.ConsumeNode(itemTree); } } diff --git a/UE.Toolkit.Reloaded/ObjectWriters/Nodes/TArrayFieldNode.cs b/UE.Toolkit.Reloaded/ObjectWriters/Nodes/TArrayFieldNode.cs index e175cc9..1ec2a74 100644 --- a/UE.Toolkit.Reloaded/ObjectWriters/Nodes/TArrayFieldNode.cs +++ b/UE.Toolkit.Reloaded/ObjectWriters/Nodes/TArrayFieldNode.cs @@ -36,8 +36,7 @@ public unsafe void ConsumeNode(XmlReader reader) Log.Warning($"{nameof(TArrayFieldNode)} || Invalid ID: {id}"); break; } - - // Reserve -1 as a value for pushing a new value into the TArray. Choose a better method perhaps? + if (itemIdx != -1) { itemIdx -= 1; // We're doing 1 indexing because normal people can't handle 0... @@ -50,10 +49,7 @@ public unsafe void ConsumeNode(XmlReader reader) { Log.Verbose($"{nameof(TArrayFieldNode)} @ 0x{(nint)tempArray:x} || Old Size : {tempArray->ArrayNum} || Old Capacity: {tempArray->ArrayMax}"); if (tempArray->ArrayNum == tempArray->ArrayMax) - { - TArrayListStatic.ResizeToStatic(tempArray, - TArrayListStatic.CalculateNewArraySizeStatic(tempArray), itemSize, nodeFactory.Memory); - } + TArrayListStatic.ResizeToStatic(tempArray, TArrayListStatic.CalculateNewArraySizeStatic(tempArray), itemSize, nodeFactory.Memory); itemIdx = tempArray->ArrayNum; tempArray->ArrayNum++; } diff --git a/UE.Toolkit.Reloaded/ObjectWriters/Nodes/TMapFieldNode.cs b/UE.Toolkit.Reloaded/ObjectWriters/Nodes/TMapFieldNode.cs index 6d104b4..5ccdd2d 100644 --- a/UE.Toolkit.Reloaded/ObjectWriters/Nodes/TMapFieldNode.cs +++ b/UE.Toolkit.Reloaded/ObjectWriters/Nodes/TMapFieldNode.cs @@ -31,18 +31,26 @@ public void ConsumeNode(XmlReader reader) var id = subReader.GetAttribute(WriterConstants.ItemIdAttr); if (id == null) { - Log.Error($"{nameof(TArrayFieldNode)} || '{WriterConstants.ItemTag}' is missing an ID."); + Log.Error($"{nameof(TMapBaseFieldNode)} || '{WriterConstants.ItemTag}' is missing an ID."); break; } if (!CreateKeyValue(id, out var KeyMaybe)) { - Log.Error($"{nameof(TArrayFieldNode)} || Could not process map key {id}"); + Log.Error($"{nameof(TMapBaseFieldNode)} || Could not process map key {id}"); break; } var Key = KeyMaybe!.Value; - // Get existing value + unsafe + { + var tempMapBitAlloc = (TArray*)(tempMap.Self + 0x20); + // Ensure that TMap is properly initialized for newly allocated items - BitAllocator's capacity should + // be 0x80 (128) to account for inline bits (equivalent to Reset()). + if (tempMapBitAlloc->ArrayMax < 128) + tempMapBitAlloc->ArrayMax = 128; + } + // Get existing value if (!ContainsKey(tempMap, Key)) { var newEntry = nodeFactory.Memory.MallocZeroed(valueSize); From c5364e7d241b66aef7ac99a481f02d96b8706a7c Mon Sep 17 00:00:00 2001 From: Rirurin Date: Sat, 6 Dec 2025 14:15:18 +0800 Subject: [PATCH 11/12] Add TSoftObjectPtr node (for Ray), fix sibling structs treated as children --- .../ObjectWriters/Nodes/FieldNodeFactory.cs | 5 +- .../ObjectWriters/Nodes/StructFieldNode.cs | 2 +- .../Writers/FieldWriterFactory.cs | 3 ++ .../ObjectWriters/Writers/PtrFieldWriter.cs | 46 +++++++++++++++++++ 4 files changed, 54 insertions(+), 2 deletions(-) create mode 100644 UE.Toolkit.Reloaded/ObjectWriters/Writers/PtrFieldWriter.cs diff --git a/UE.Toolkit.Reloaded/ObjectWriters/Nodes/FieldNodeFactory.cs b/UE.Toolkit.Reloaded/ObjectWriters/Nodes/FieldNodeFactory.cs index 0af20fa..5eb5476 100644 --- a/UE.Toolkit.Reloaded/ObjectWriters/Nodes/FieldNodeFactory.cs +++ b/UE.Toolkit.Reloaded/ObjectWriters/Nodes/FieldNodeFactory.cs @@ -27,7 +27,10 @@ public bool TryCreate(string fieldName, nint fieldPtr, int fieldBit, Type fieldT || fieldType == typeof(string) || fieldType == typeof(FText) || fieldType == typeof(FString) - || fieldType == typeof(FName)) + || fieldType == typeof(FName) + || fieldType.Name.StartsWith("TSoftObjectPtr") + || fieldType.Name.StartsWith("TSoftClassPtr") + ) { return new PrimitiveFieldNode(fieldName, fieldPtr, fieldBit, fieldType, objCreator); } diff --git a/UE.Toolkit.Reloaded/ObjectWriters/Nodes/StructFieldNode.cs b/UE.Toolkit.Reloaded/ObjectWriters/Nodes/StructFieldNode.cs index fafb9e1..4d4f971 100644 --- a/UE.Toolkit.Reloaded/ObjectWriters/Nodes/StructFieldNode.cs +++ b/UE.Toolkit.Reloaded/ObjectWriters/Nodes/StructFieldNode.cs @@ -64,6 +64,7 @@ public void ConsumeNode(XmlReader reader) var anyElementFound = false; while (reader.Read()) { + if (reader.Name == _structName && reader.NodeType == XmlNodeType.EndElement) break; if (reader.NodeType != XmlNodeType.Element) continue; if (AtItemElement(reader)) Log.Warning($"{nameof(StructFieldNode)} || Unexpected '{WriterConstants.ItemTag}' element found. Error?"); @@ -73,7 +74,6 @@ public void ConsumeNode(XmlReader reader) if (_fields.TryGetValue(fieldName, out var fieldData)) { var fieldType = fieldData.type; - // var fieldPtr = _structPtr + Marshal.OffsetOf(_structType, fieldName); var fieldPtr = _structPtr + fieldData.offset; if (_nodeFactory.TryCreate(fieldName, fieldPtr, fieldData.bit, fieldType, out var fieldNode)) { diff --git a/UE.Toolkit.Reloaded/ObjectWriters/Writers/FieldWriterFactory.cs b/UE.Toolkit.Reloaded/ObjectWriters/Writers/FieldWriterFactory.cs index 18bc8d6..aa54b3a 100644 --- a/UE.Toolkit.Reloaded/ObjectWriters/Writers/FieldWriterFactory.cs +++ b/UE.Toolkit.Reloaded/ObjectWriters/Writers/FieldWriterFactory.cs @@ -13,6 +13,9 @@ public static class FieldWriterFactory if (fieldType == typeof(FText) || fieldType == typeof(FString) || fieldType == typeof(FName)) return new TextFieldWriter(fieldName, fieldPtr, fieldType, objCreator); + + if (fieldType.Name.StartsWith("TSoftObjectPtr") || fieldType.Name.StartsWith("TSoftClassPtr")) + return new PtrFieldWriter(fieldName, fieldPtr, fieldType, objCreator); Log.Error($"{nameof(FieldWriterFactory)} || No writer found for field '{fieldName}' of type '{fieldType.Name}'."); return null; diff --git a/UE.Toolkit.Reloaded/ObjectWriters/Writers/PtrFieldWriter.cs b/UE.Toolkit.Reloaded/ObjectWriters/Writers/PtrFieldWriter.cs new file mode 100644 index 0000000..2d369aa --- /dev/null +++ b/UE.Toolkit.Reloaded/ObjectWriters/Writers/PtrFieldWriter.cs @@ -0,0 +1,46 @@ +using System.Runtime.InteropServices; +using UE.Toolkit.Core.Types.Interfaces; +using UE.Toolkit.Core.Types.Unreal.UE5_4_4; + +namespace UE.Toolkit.Reloaded.ObjectWriters.Writers; + +public unsafe class PtrFieldWriter(string fieldName, nint fieldPtr, Type fieldType, IObjectCreator objCreator) : IFieldWriter +{ + + private byte[]? _ogData; + + public void Reset() + { + if (_ogData != null) Marshal.Copy(_ogData, 0, fieldPtr, _ogData.Length); + } + + public void SetField(string value) + { + var strPtr = Marshal.StringToHGlobalUni(value); + SetField(strPtr); + Marshal.FreeHGlobal(strPtr); + } + + public void SetField(TValue value) where TValue : unmanaged + { + var strValue = Marshal.PtrToStringUni(*(nint*)&value)!; + if (fieldType.Name.StartsWith("TSoftObjectPtr") || fieldType.Name.StartsWith("TSoftClassPtr")) + { + var SizeOf = Marshal.SizeOf(); + _ogData = new byte[SizeOf]; + Marshal.Copy(fieldPtr, _ogData, 0, SizeOf); + var ObjectPtr = (TSoftObjectPtr*)fieldPtr; + var PathPtr = &ObjectPtr->SoftObjectPtr.Super.ObjectId.AssetPath; + var SepIndex = strValue.LastIndexOf('.'); + var (Package, Asset) = (new FName(strValue[..SepIndex]), new FName(strValue[(SepIndex + 1)..])); + PathPtr->PackageName = Package; + PathPtr->AssetName = Asset; + Log.Debug($"{nameof(PtrFieldWriter)} || Field '{fieldName}' at 0x{fieldPtr:X} set to: {strValue}"); + } + else + { + Log.Error($"{nameof(PtrFieldWriter)} || Invalid type '{fieldType.Name}' for field '{fieldName}'."); + return; + } + } +} \ No newline at end of file From 5dac10de3b1a9aeb6480ebea7fe2775076086f83 Mon Sep 17 00:00:00 2001 From: Rirurin Date: Sat, 6 Dec 2025 16:06:22 +0800 Subject: [PATCH 12/12] Fix: Use AssetPath only for TSoftObjectPtr reference --- UE.Toolkit.Reloaded/ObjectWriters/Writers/PtrFieldWriter.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/UE.Toolkit.Reloaded/ObjectWriters/Writers/PtrFieldWriter.cs b/UE.Toolkit.Reloaded/ObjectWriters/Writers/PtrFieldWriter.cs index 2d369aa..127768b 100644 --- a/UE.Toolkit.Reloaded/ObjectWriters/Writers/PtrFieldWriter.cs +++ b/UE.Toolkit.Reloaded/ObjectWriters/Writers/PtrFieldWriter.cs @@ -30,11 +30,7 @@ public void SetField(TValue value) where TValue : unmanaged _ogData = new byte[SizeOf]; Marshal.Copy(fieldPtr, _ogData, 0, SizeOf); var ObjectPtr = (TSoftObjectPtr*)fieldPtr; - var PathPtr = &ObjectPtr->SoftObjectPtr.Super.ObjectId.AssetPath; - var SepIndex = strValue.LastIndexOf('.'); - var (Package, Asset) = (new FName(strValue[..SepIndex]), new FName(strValue[(SepIndex + 1)..])); - PathPtr->PackageName = Package; - PathPtr->AssetName = Asset; + ObjectPtr->SoftObjectPtr.Super.ObjectId.AssetPath.AssetName = new FName(strValue); Log.Debug($"{nameof(PtrFieldWriter)} || Field '{fieldName}' at 0x{fieldPtr:X} set to: {strValue}"); } else