diff --git a/.gitignore b/.gitignore index c9e3499..d9356ea 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,9 @@ riderModule.iml /_ReSharper.Caches/ # Visual Studio build files -.vs \ No newline at end of file +.vs +*.sln.DotSettings.user + +# Jetbrains IDE +.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/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/Interfaces/IUnrealClassesInternal.cs b/UE.Toolkit.Core/Types/Interfaces/IUnrealClassesInternal.cs new file mode 100644 index 0000000..74f824a --- /dev/null +++ b/UE.Toolkit.Core/Types/Interfaces/IUnrealClassesInternal.cs @@ -0,0 +1,23 @@ +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); +} + +[Flags] +public enum StructType +{ + None = 0, + Class = 1 << 0, + ScriptStruct = 1 << 1 +} \ 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..aac98c5 100644 --- a/UE.Toolkit.Core/Types/Unreal/Factories/BaseUnrealFactory.cs +++ b/UE.Toolkit.Core/Types/Unreal/Factories/BaseUnrealFactory.cs @@ -1,9 +1,13 @@ +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; @@ -19,8 +23,11 @@ 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): + return (T)CreateFBoolProperty(obj.Ptr); case nameof(IFEnumProperty): return (T)CreateFEnumProperty(obj.Ptr); case nameof(IFObjectProperty): @@ -41,12 +48,15 @@ public T Cast(IPtr obj) return (T)CreateFSetProperty(obj.Ptr); case nameof(IFOptionalProperty): return (T)CreateFOptionalProperty(obj.Ptr); + default: 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); @@ -58,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); @@ -66,6 +78,20 @@ 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 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); + 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 53cd4ff..35388de 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; @@ -8,7 +9,11 @@ namespace UE.Toolkit.Core.Types.Unreal.Factories; 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); IFEnumProperty CreateFEnumProperty(nint ptr); IFObjectProperty CreateFObjectProperty(nint ptr); @@ -20,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); @@ -28,6 +34,20 @@ 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); + + IFStructParams CreateFStructParams(nint ptr); + IFPropertyParams CreateFPropertyParams(nint ptr); + IFGenericPropertyParams CreateFGenericPropertyParams(nint ptr); + + 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/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/IFPropertyParams.cs b/UE.Toolkit.Core/Types/Unreal/Factories/Interfaces/IFPropertyParams.cs new file mode 100644 index 0000000..5d63b0f --- /dev/null +++ b/UE.Toolkit.Core/Types/Unreal/Factories/Interfaces/IFPropertyParams.cs @@ -0,0 +1,17 @@ +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; } +} + +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/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/IFStructParams.cs b/UE.Toolkit.Core/Types/Unreal/Factories/Interfaces/IFStructParams.cs new file mode 100644 index 0000000..3f6e019 --- /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; } + EStructFlags 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/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/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/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/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/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/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/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/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/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 366d226..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,25 +1,88 @@ 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 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; 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; 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; +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 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 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; +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; +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; +using UUserDefinedEnum = UE.Toolkit.Core.Types.Unreal.UE4_27_2.UUserDefinedEnum; // ReSharper disable InconsistentNaming 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(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 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); @@ -56,10 +119,30 @@ public class UnrealFactory : BaseUnrealFactory 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); 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 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) @@ -125,11 +208,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; @@ -225,6 +309,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 { @@ -388,6 +481,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 TMapDictionary>( + (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 @@ -405,4 +526,240 @@ 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; + } +} + + +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 EStructFlags 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; } + +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; +} + +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); +} + +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 fc6bb06..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 @@ -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; @@ -9,8 +10,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); @@ -29,8 +62,19 @@ public class UnrealFactory : BaseUnrealFactory 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 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 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) @@ -103,6 +147,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 +273,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) @@ -401,6 +447,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 @@ -417,4 +490,239 @@ 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; + } +} + +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 FStructParams_UE5_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 EStructFlags 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 FPropertyParams_UE5_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; +} + +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); +} + +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/FPropertyParams.cs b/UE.Toolkit.Core/Types/Unreal/UE4_27_2/FPropertyParams.cs new file mode 100644 index 0000000..d6c6729 --- /dev/null +++ b/UE.Toolkit.Core/Types/Unreal/UE4_27_2/FPropertyParams.cs @@ -0,0 +1,36 @@ +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)] +public struct FPropertyParamsBase +{ + public nint NameUTF8; + public nint RepNotifyFuncUTF8; + public EPropertyFlags PropertyFlags; + public EPropertyGenFlags PropertyGenFlags; + public EObjectFlags ObjectFlags; + public int 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 int ArrayDim; + public int Offset; +} + +[StructLayout(LayoutKind.Sequential)] +public struct FGenericPropertyParams +{ + public FPropertyParamsBaseWithOffset Super; +} \ 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/FStructParams.cs b/UE.Toolkit.Core/Types/Unreal/UE4_27_2/FStructParams.cs new file mode 100644 index 0000000..865ba8a --- /dev/null +++ b/UE.Toolkit.Core/Types/Unreal/UE4_27_2/FStructParams.cs @@ -0,0 +1,26 @@ +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; + +[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 EStructFlags 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/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/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/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/UE4_27_2/Unreal.cs b/UE.Toolkit.Core/Types/Unreal/UE4_27_2/Unreal.cs index 93ea598..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; @@ -181,6 +186,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/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/EInternalObjectFlags.cs b/UE.Toolkit.Core/Types/Unreal/UE5_4_4/EInternalObjectFlags.cs new file mode 100644 index 0000000..e28eb56 --- /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/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/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/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/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.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/FPropertyParams.cs b/UE.Toolkit.Core/Types/Unreal/UE5_4_4/FPropertyParams.cs new file mode 100644 index 0000000..c03c277 --- /dev/null +++ b/UE.Toolkit.Core/Types/Unreal/UE5_4_4/FPropertyParams.cs @@ -0,0 +1,37 @@ +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; +} + +[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/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/FStructParams.cs b/UE.Toolkit.Core/Types/Unreal/UE5_4_4/FStructParams.cs new file mode 100644 index 0000000..0d8965e --- /dev/null +++ b/UE.Toolkit.Core/Types/Unreal/UE5_4_4/FStructParams.cs @@ -0,0 +1,24 @@ +using System.Runtime.InteropServices; + +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 EStructFlags 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/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/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/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.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 a6765d7..fe2c14e 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; @@ -163,16 +176,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 +200,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; @@ -294,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 /// /// /// @@ -321,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; @@ -335,35 +352,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 +382,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 +394,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; @@ -403,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; @@ -529,7 +550,7 @@ public ICollection> Values public Ptr this[TElemKey key] { - get => new Ptr(TryGetByHash(key)); + get => new(TryGetByHash(key)); set => *TryGetByHash(key) = *value.Value; } @@ -561,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) @@ -661,7 +684,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 +722,355 @@ 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 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) + { + + 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) + { + + var NewElementAlloc = (byte*)Allocator.Malloc(NewSize * Elements.SizeOf()); + if (Elements.Allocation != null) + { + NativeMemory.Copy(Elements.Allocation, NewElementAlloc, (nuint)(Elements.Size * Elements.SizeOf())); + Allocator.Free((nint)Elements.Allocation); + } + Elements.Allocation = 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/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/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/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/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.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.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.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.DumperMod/Dumper.cs b/UE.Toolkit.DumperMod/Dumper.cs index cd21969..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; @@ -242,7 +251,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 */"); @@ -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(); @@ -277,6 +300,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 +308,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 +325,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 +397,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. @@ -492,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": @@ -512,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.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 new file mode 100644 index 0000000..ba1f90e --- /dev/null +++ b/UE.Toolkit.Interfaces/IUnrealClasses.cs @@ -0,0 +1,370 @@ +using UE.Toolkit.Core.Types.Interfaces; +using UE.Toolkit.Core.Types.Unreal.Factories.Interfaces; + +namespace UE.Toolkit.Interfaces; + +public interface IUnrealClasses : IUnrealClassesInternal +{ + + #region Class/Struct Getters + + /// + /// 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. + /// 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. + /// 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); + + #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 + /// exposable to blueprints and Object XML. + /// + /// 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; + + /// + /// 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. + /// Return value. + /// Object type. + 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 + /// exposable to blueprints and Object XML. + /// + /// 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; + + /// + /// 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 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 + /// exposable to blueprints and Object XML. + /// + /// 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; + + /// + /// 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. + /// Return value. + /// Object type. + 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 + /// exposable to blueprints and Object XML. + /// + /// 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; + + /// + /// 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. + /// Return value. + /// Object type. + 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 + /// exposable to blueprints and Object XML. + /// + /// 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; + + /// + /// 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. + /// Return value. + /// Object type. + 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. + /// Return value. + /// 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. + /// Return value. + /// 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. + /// Return value. + /// 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. + /// Return value. + /// 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. + /// Return value. + /// 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. + /// Return value. + /// 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. + /// Return value. + /// 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. + /// Return value. + /// 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. + /// 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; + + #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.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 new file mode 100644 index 0000000..d9c010b --- /dev/null +++ b/UE.Toolkit.Interfaces/IUnrealState.cs @@ -0,0 +1,20 @@ +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); + + 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.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/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/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/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 e894660..0863f9c 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,15 +31,21 @@ 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; private readonly ObjectWriterService _writer; private readonly ToolkitApi _toolkit; private readonly UnrealStrings _strings; + private readonly UnrealClasses _classes; + private readonly Common.ResolveAddress _address; + private readonly UnrealMethods _methods; + private readonly UnrealState _state; + private readonly UnrealSpawning _spawning; public Mod(ModContext context) { @@ -58,23 +65,33 @@ 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 = new(); _typeRegistry = new(); - _writer = new(_typeRegistry, _objects, _tables); + _writer = new(_typeRegistry, _objects, _tables, _memory); _toolkit = new(_writer); _strings = new(); + _address = new(); + _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, _memory); _modLoader.AddOrReplaceController(_owner, _tables); _modLoader.AddOrReplaceController(_owner, _objects); _modLoader.AddOrReplaceController(_owner, _typeRegistry); _modLoader.AddOrReplaceController(_owner, _toolkit); _modLoader.AddOrReplaceController(_owner, _names); _modLoader.AddOrReplaceController(_owner, _strings); + _modLoader.AddOrReplaceController(_owner, _classes); _modLoader.AddOrReplaceController(_owner, _factory); + _modLoader.AddOrReplaceController(_owner, _methods); + _modLoader.AddOrReplaceController(_owner, _state); + _modLoader.AddOrReplaceController(_owner, _spawning); _modLoader.ModLoaded += OnModLoaded; } @@ -113,7 +130,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), typeof(IUnrealSpawning) ]; } \ No newline at end of file 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/ObjectWriters/Nodes/DataTableFieldNode.cs b/UE.Toolkit.Reloaded/ObjectWriters/Nodes/DataTableFieldNode.cs index 31f27df..5632aa5 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(); @@ -31,22 +35,20 @@ public unsafe void ConsumeNode(XmlReader reader) break; } - if (tempTable.TryGetValue(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, 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)) { - - Log.Error($"{nameof(DataTableFieldNode)} || Failed to find row with ID '{id}' in '{fieldName}'.");; - break; + var itemTree = subReader.ReadSubtree(); + itemTree.MoveToContent(); + itemNode.ConsumeNode(itemTree); } } diff --git a/UE.Toolkit.Reloaded/ObjectWriters/Nodes/FieldNodeFactory.cs b/UE.Toolkit.Reloaded/ObjectWriters/Nodes/FieldNodeFactory.cs index 52ff3eb..5eb5476 100644 --- a/UE.Toolkit.Reloaded/ObjectWriters/Nodes/FieldNodeFactory.cs +++ b/UE.Toolkit.Reloaded/ObjectWriters/Nodes/FieldNodeFactory.cs @@ -1,30 +1,38 @@ 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; 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) + 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}'."); + if (fieldType.IsPrimitive || fieldType.IsEnum || 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, fieldType, objCreator); + return new PrimitiveFieldNode(fieldName, fieldPtr, fieldBit, fieldType, objCreator); } if (fieldType.Name.StartsWith("TArray")) @@ -35,6 +43,57 @@ 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)) + { + 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), + }; + } + + if (keyType == typeof(FName)) + { + return new TMapNameFieldNode(fieldName, fieldPtr, fieldType, this); + } + + /* + if (keyType.Name.StartsWith("Ptr")) // TODO + { + + } + */ + Log.Warning($"{nameof(FieldNodeFactory)} || Field '{fieldName}' with type '{fieldType.Name}' is not currently supported for map editing operations."); + return null; } if (fieldType.IsValueType) 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 cb9a3e5..4d4f971 100644 --- a/UE.Toolkit.Reloaded/ObjectWriters/Nodes/StructFieldNode.cs +++ b/UE.Toolkit.Reloaded/ObjectWriters/Nodes/StructFieldNode.cs @@ -3,16 +3,43 @@ namespace UE.Toolkit.Reloaded.ObjectWriters.Nodes; +internal record FieldData(Type type, nint offset, int bit); + 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(); + 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, CurrentOffset, CurrentBit)), 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 +54,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; } } @@ -37,16 +64,18 @@ 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?"); 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); - if (_nodeFactory.TryCreate(fieldName, fieldPtr, fieldType, out var fieldNode)) + var fieldType = fieldData.type; + var fieldPtr = _structPtr + fieldData.offset; + 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 6ba4f4f..1ec2a74 100644 --- a/UE.Toolkit.Reloaded/ObjectWriters/Nodes/TArrayFieldNode.cs +++ b/UE.Toolkit.Reloaded/ObjectWriters/Nodes/TArrayFieldNode.cs @@ -36,16 +36,26 @@ public unsafe void ConsumeNode(XmlReader reader) Log.Warning($"{nameof(TArrayFieldNode)} || Invalid ID: {id}"); break; } - - itemIdx -= 1; // We're doing 1 indexing because normal people can't handle 0... - if (itemIdx < 0 || itemIdx > tempArray->ArrayNum) + + 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; - 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 new file mode 100644 index 0000000..5ccdd2d --- /dev/null +++ b/UE.Toolkit.Reloaded/ObjectWriters/Nodes/TMapFieldNode.cs @@ -0,0 +1,130 @@ +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 abstract class TMapBaseFieldNode(string fieldName, nint fieldPtr, Type fieldType, FieldNodeFactory nodeFactory) + : IFieldNode where TKeyValue : unmanaged, IMapHashable, IEquatable +{ + public void ConsumeNode(XmlReader reader) + { + var valueType = fieldType.GetGenericArguments()[1]; + var valueSize = Marshal.SizeOf(valueType); + + var tempMap = CreateTempMap(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(TMapBaseFieldNode)} || '{WriterConstants.ItemTag}' is missing an ID."); + break; + } + + if (!CreateKeyValue(id, out var KeyMaybe)) + { + Log.Error($"{nameof(TMapBaseFieldNode)} || Could not process map key {id}"); + break; + } + var Key = KeyMaybe!.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); + 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(Key, out var valuePtr) && + nodeFactory.TryCreate($"{fieldName} (Key: {Key})", valuePtr, 0, valueType, out var itemNode)) + { + var itemTree = subReader.ReadSubtree(); + itemTree.MoveToContent(); + itemNode.ConsumeNode(itemTree); + } + } + } + + 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/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/ObjectWriters/Writers/FieldWriterFactory.cs b/UE.Toolkit.Reloaded/ObjectWriters/Writers/FieldWriterFactory.cs index a2d1225..aa54b3a 100644 --- a/UE.Toolkit.Reloaded/ObjectWriters/Writers/FieldWriterFactory.cs +++ b/UE.Toolkit.Reloaded/ObjectWriters/Writers/FieldWriterFactory.cs @@ -5,13 +5,17 @@ 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); + + 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/PrimitiveFieldWriter.cs b/UE.Toolkit.Reloaded/ObjectWriters/Writers/PrimitiveFieldWriter.cs index e91a3ec..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() { @@ -30,6 +31,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); @@ -72,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/ObjectWriters/Writers/PtrFieldWriter.cs b/UE.Toolkit.Reloaded/ObjectWriters/Writers/PtrFieldWriter.cs new file mode 100644 index 0000000..127768b --- /dev/null +++ b/UE.Toolkit.Reloaded/ObjectWriters/Writers/PtrFieldWriter.cs @@ -0,0 +1,42 @@ +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; + ObjectPtr->SoftObjectPtr.Super.ObjectId.AssetPath.AssetName = new FName(strValue); + 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 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..747dacc 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,25 @@ 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= + +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= + +GEngine=48 89 05 ?? ?? ?? ?? 48 85 C9 74 ?? E8 ?? ?? ?? ?? 48 8D 4D ?? +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/p3r/unreal.ini b/UE.Toolkit.Reloaded/Project/UE.Toolkit.Reloaded/p3r/unreal.ini index 49a24bc..4b74f1a 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,12 @@ GetAllocSize=0x40 Malloc=0x10 Realloc=0x20 QuantizeSize=0x38 + +[UObject] +ProcessEvent=0x220 + +[UPackage] +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 854b568..2a10aca 100644 --- a/UE.Toolkit.Reloaded/Project/UE.Toolkit.Reloaded/scans.ini +++ b/UE.Toolkit.Reloaded/Project/UE.Toolkit.Reloaded/scans.ini @@ -35,3 +35,24 @@ 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= + +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 + +GEngine=48 89 05 ?? ?? ?? ?? 48 85 C9 74 ?? E8 ?? ?? ?? ?? 48 8D 4D ?? +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/Project/UE.Toolkit.Reloaded/unreal.ini b/UE.Toolkit.Reloaded/Project/UE.Toolkit.Reloaded/unreal.ini index 277ceaf..6068556 100644 --- a/UE.Toolkit.Reloaded/Project/UE.Toolkit.Reloaded/unreal.ini +++ b/UE.Toolkit.Reloaded/Project/UE.Toolkit.Reloaded/unreal.ini @@ -6,3 +6,12 @@ GetAllocSize=0x58 Malloc=0x28 Realloc=0x38 QuantizeSize=0x50 + +[UObject] +ProcessEvent=0x268 + +[UPackage] +GamePackage=/Script/SandFall + +[UStruct] +Link=0x2d0 \ No newline at end of file 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 new file mode 100644 index 0000000..76d1b57 --- /dev/null +++ b/UE.Toolkit.Reloaded/Reflection/PropertyFactory.cs @@ -0,0 +1,277 @@ +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; +using UE.Toolkit.Reloaded.Reflection.UE5_4_4; + +namespace UE.Toolkit.Reloaded.Reflection; + +public abstract class BasePropertyFactory(IUnrealFactory factory, IUnrealMemory memory, + IUnrealClasses classes, IPropertyFlagsBuilder flags) +{ + 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) }, + }; + + 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; + 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 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 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; + } + + 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 + + #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; + protected readonly IUnrealMemory Memory = memory; + protected readonly IUnrealClasses Classes = classes; + protected readonly IPropertyFlagsBuilder Flags = flags; + + protected const int FIELD_ALIGNMENT = 0x80; +} + +[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/TypeFactory.cs b/UE.Toolkit.Reloaded/Reflection/TypeFactory.cs new file mode 100644 index 0000000..606ff4b --- /dev/null +++ b/UE.Toolkit.Reloaded/Reflection/TypeFactory.cs @@ -0,0 +1,40 @@ +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, IPropertyFlagsBuilder flags) +{ + + // 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 IPropertyFlagsBuilder Flags = flags; + + #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 new file mode 100644 index 0000000..e593680 --- /dev/null +++ b/UE.Toolkit.Reloaded/Reflection/UE4_27_2/PropertyFactory.cs @@ -0,0 +1,215 @@ +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, IPropertyFlagsBuilder flags) + : BasePropertyFactory(factory, memory, classes, 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; + ((UStruct*)pClass)->child_properties = (FField*)pProperty; + } + else + { + 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; + } + } + } + + 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; + } + + private unsafe void SetPropertyFieldDefaults(FProperty* pProperty, int Offset) + { + pProperty->rep_index = 0; + pProperty->blueprint_rep_cond = 0; + pProperty->offset_internal = Offset; + pProperty->rep_notify_func = new(); + } + + private unsafe void SetPropertyFieldsInner(IFProperty Property, int Offset, + PropertyVisibility Visibility, PropertyBuilderFlags PropertyFlags) + { + var pProperty = (FProperty*)Property.Ptr; + pProperty->array_dim = 1; + pProperty->element_size = Marshal.SizeOf(); + pProperty->property_flags = 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); + + 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, "FloatProperty", Visibility); + + public override bool CreateF64(out IFProperty? NewProperty, string Name, int Offset, PropertyVisibility 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 = 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 = 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 = 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/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 new file mode 100644 index 0000000..acea6c4 --- /dev/null +++ b/UE.Toolkit.Reloaded/Reflection/UE4_27_2/TypeFactory.cs @@ -0,0 +1,166 @@ +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.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, IPropertyFlagsBuilder flags) + : BaseTypeFactory(factory, memory, classes, flags) +{ + + 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 Alloc = (ICppStructOps*)FMemory_Malloc_Static!(Marshal.SizeOf(), 0); + Alloc->VTable = CurrentCppStructOpsVtable; + Alloc->Size = CurrentCppStructOpsSize; + Alloc->Alignment = CPP_STRUCT_OPS_ALIGNMENT; + return (nint)Alloc; + } + + 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 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 new file mode 100644 index 0000000..39629af --- /dev/null +++ b/UE.Toolkit.Reloaded/Reflection/UE5_4_4/PropertyFactory.cs @@ -0,0 +1,206 @@ +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, IPropertyFlagsBuilder flags) + : BasePropertyFactory(factory, memory, classes, 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; + ((UStruct*)pClass)->ChildProperties = (FField*)pProperty; + } + else + { + 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; + } + } + } + + 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; + } + + 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(); + pProperty->PropertyFlags = 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->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); + + 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); + + 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 = Flags.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 = Flags.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 = 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/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 new file mode 100644 index 0000000..8da6ad5 --- /dev/null +++ b/UE.Toolkit.Reloaded/Reflection/UE5_4_4/TypeFactory.cs @@ -0,0 +1,77 @@ +using UE.Toolkit.Core.Types.Unreal.Factories; +using UE.Toolkit.Core.Types.Unreal.Factories.Interfaces; +using UE.Toolkit.Interfaces; + +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/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 new file mode 100644 index 0000000..fcc558e --- /dev/null +++ b/UE.Toolkit.Reloaded/Unreal/UnrealClasses.cs @@ -0,0 +1,491 @@ +using System.Collections.Concurrent; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.Marshalling; +using Reloaded.Hooks.Definitions; +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; + +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); + AddToClassList(name, returnClass); + TryHookStructLink(returnClass); + } + + // 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); + AddToClassList(name, returnClass); + TryHookStructLink(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; + if (classCtor != nint.Zero) + { + Extension.ConstructorHook = FollowThunkToGetAppropriateHook(classCtor, Extension.Constructor); + } + + 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 + (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; + } + + private void TryHookStructLink(UClass** ppClass) + { + 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 void UStruct_LinkImpl(UStruct* pThis, nint Ar, byte bRelinkExistingProperties) + { + 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}>"); + } + */ + } + } + } + + private delegate nint GetStaticStruct(nint pInRegister, nint pStructOuter, nint pStructName, nint Size, int Crc); + private SHFunction _GetStaticStruct; + 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) + { + // 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!(); + + [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) + { + if (Mod.Config.LogScriptStructEnabled) + { + 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); + } + + #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)))); + + public bool GetClassInfoFromClass(out IUClass? Value) where TObject : unmanaged + => 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 + { + 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 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) + { + 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); + return true; + } + + #endregion + + #region IUnrealClassesInternal implementation + + public bool GetFieldClassGlobal(FName Name, out FieldClassGlobal FieldClass) + => FieldTypes.TryGetValue(Name.ComparisonIndex.Value, out FieldClass); + + #endregion + + private ConcurrentDictionary ClassExtensions = new(); + private ConcurrentDictionary Classes = new(); + private ConcurrentDictionary ScriptStructs = new(); + private ConcurrentDictionary FieldTypes = new(); + + private IUnrealFactory Factory; + private IUnrealMemory Memory; + private IReloadedHooks Hooks; + private ResolveAddress Address; + + private BasePropertyFactory PropertyFactory; + private BaseTypeFactory TypeFactory; + + public UnrealClasses(IUnrealFactory _Factory, IUnrealMemory _Memory, IReloadedHooks _Hooks, ResolveAddress _Address) + { + Factory = _Factory; + 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, 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(); + } +} + +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/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 diff --git a/UE.Toolkit.Reloaded/Unreal/UnrealObjects.cs b/UE.Toolkit.Reloaded/Unreal/UnrealObjects.cs index 6c3019d..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()); @@ -93,20 +99,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/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 new file mode 100644 index 0000000..33d320c --- /dev/null +++ b/UE.Toolkit.Reloaded/Unreal/UnrealState.cs @@ -0,0 +1,65 @@ +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; +using UE.Toolkit.Reloaded.Common.GameConfigs; + +namespace UE.Toolkit.Reloaded.Unreal; + +public class UnrealState : IUnrealState +{ + private IUnrealFactory Factory; + private IUnrealClasses Classes; + + private nint GEngine; + + // Based on UE's implementation of UEngine::GetCurrentPlayWorld + public unsafe bool GetCurrentPlayWorld(out IUObject? TargetWorld) + { + TargetWorld = null; + if (GEngine == nint.Zero) return false; + var UEngine = Factory.CreateUEngine(*(nint*)GEngine); + IUObject? NoneWorld = null; + foreach (var WorldContext in UEngine.GetWorldList()) + { + if (WorldContext.GetWorld() == nint.Zero) continue; + switch (WorldContext.GetWorldType()) + { + 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 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 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);