Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,15 @@
| Object Logging |✅|✅|✅|✅|✅|✅|✅|✅|✅
| Object Editing |✅|❔|❔|✅|❔|✅|❔|❔|❔
| `FMemory` Functions |✅|✅|✅|✅|✅|✅|✅|✅|✅
| Dumper |✅|✅|✅|✅|️️️️️️✅|✅|✅|✅️|️️️️✅️
| Dumper |✅|✅|✅|✅|✅|✅|✅|✅|️️️️️️✅
| Property Editing (Object XML) |✅|❔|❔|✅|❔|✅|❔|❔|❔
| Add List Entry (Object XML) |✅|❔|❔|❔|❔|❔|❔|❔|❔
| Add Map Entry (Object XML) |✅|❔|❔|❔|❔|❔|❔|❔|❔
| Type Information |✅|✅|✅|✅|✅|✅|✅|✅|✅
| Custom Constructor |✅|✅|✅|✅|✅|✅|✅|✅|✅
| Add Properties |✅|✅|✅|✅|✅|✅|✅|✅|✅
| Register Struct |✅|✅|✅|✅|✅|✅|✅|✅|✅
| Call Blueprint Methods |✅|✅|✅|✅|✅|✅|✅|✅|✅

Features marked with ❔ are currently untested.

Expand Down Expand Up @@ -167,10 +168,10 @@ First, we add an `Item` element with an `id` attribute. The ID is which item in
Inside the `Item` element, we're back to what's already been covered. In this example, items have an `EquipID` property and we're setting the `EquipID` of the __2nd__ item to `100`.

#### Editing Items in a DataTable
Finally, _DataTables_! There's only two notable differences: the `row-struct` attribute and `id`s are now row names. This example is from _Clair Obscur_'s `DT_jRPG_CharacterDefinitions.uasset`.
Finally, _DataTables_! There's only one notable difference: the `id`s are now row names. This example is from _Clair Obscur_'s `DT_jRPG_CharacterDefinitions.uasset`.

```xml
<DataTable row-struct="S_jRPG_CharacterDefinition">
<DataTable>
<Item id="Lune">
<CharacterDisplayName value="TEST1"/>
</Item>
Expand All @@ -185,10 +186,10 @@ Finally, _DataTables_! There's only two notable differences: the `row-struct` a

A DataTable's __Class__ is... `DataTable`! Who could've guessed? As such, the __Root Element__ in our XML has to be `DataTable`.

But, we also need to specify the __Class (Struct)__ for the __Items (Rows)__ in the DataTable so we can edit them. You do that by adding a `row-struct` attribute with the DataTable's __RowStruct__ name, `S_jRPG_CharacterDefinition` in this example.

For our `Item` element's ID, instead of a number we use the __Row's name__ that we want to edit. Inside the `Item` element, it's no different than lists from earlier.

You can optionally override the data type for a DataTable by specifying a struct name for the `row-struct` attribute on the root element. This used to be required in previous versions of UE Toolkit, but the data type is automatically retrived now.

That covers everything, congrats on finishing this 🎉🎉🎉!

#### Misc. Stuff (Enums, Type Hints)
Expand All @@ -205,6 +206,8 @@ Some Unreal types may not be generated and need to be supplied. `UE.Toolkit.Core
that were missing in my testing. Add it to your project using **NuGet** and add `UE.Toolkit.Core.Types.Unreal;`
in the `File Usings` config before dumping.

The dumper supports two different schemas: a `Structs Only` schema that only generates the types as unmanaged structs and `Structs and Classes`, which generates C# classes for types that inherit from UObject. Classes will also generate methods that are exposed to blueprints.

## Code Only Features

### Using Unreal's Memory Allocator `FMemory`
Expand Down
126 changes: 126 additions & 0 deletions UE.Toolkit.Core/Types/Interfaces/IFunctionParam.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
using UE.Toolkit.Core.Types.Unreal.Common.FunctionParam;
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.Core.Types.Unreal.UE5_6_1;

namespace UE.Toolkit.Core.Types.Interfaces;

public interface IFunctionParam
{
string PropertyType { get; }
void Write(nint Destination);
void Read(nint Destination);
}

public abstract class FunctionParamCopyable<T>(Ptr<T> rvalue, string propertyType, IUnrealMemoryInternal? memory = null)
: IFunctionParam, IDisposable where T : unmanaged
{
public unsafe T Value => *RValue.Value;

protected Ptr<T> RValue => rvalue;

protected IUnrealMemoryInternal? Memory => memory;

public string PropertyType => propertyType;

private static unsafe void Copy(T* source, T* destination) => *destination = *source;

public unsafe void Write(nint Destination) => Copy(RValue.Value, (T*)Destination);

public unsafe void Read(nint Destination) => Copy((T*)Destination, RValue.Value);

protected bool IsDisposed;

public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}

protected virtual void Dispose(bool disposing)
{
if (!IsDisposed)
{
if (disposing) { }
unsafe { Memory?.Free((nint)RValue.Value); }
IsDisposed = true;
}
}

~FunctionParamCopyable() => Dispose(false);
}

public enum ProcessEventResult
{
Success,
CouldNotFindFunction,
ParameterTypeMismatch,
}

public static class FunctionParamFactory
{

private static Dictionary<string, string> PropertyNameToParamName = [];

private static IFunctionParam CreateStructParam(IFStructProperty property, IUnrealMemoryInternal? Memory)
=> new StructParam(Memory?.Malloc(property.ElementSize) ?? nint.Zero, property.ElementSize, Memory);

private static unsafe IFunctionParam CreateBoolParam(IFBoolProperty property, IUnrealMemoryInternal? Memory)
=> new BoolParam(new((bool*)(Memory?.Malloc(sizeof(bool)) ?? nint.Zero)), property.FieldMask, Memory);

public static unsafe IFunctionParam CreateParam(IFProperty property, IUnrealFactory factory,
ITypeReflectionInternal reflection, IUnrealMemoryInternal? Memory)
{
return property.ClassPrivate.Name switch
{
"BoolProperty" => CreateBoolParam(factory.CreateFBoolProperty(property.Ptr), Memory),
"Int8Property" => new Int8Param(new((byte*)(Memory?.Malloc(sizeof(byte)) ?? nint.Zero)), Memory),
"ByteProperty" => new ByteParam(new((byte*)(Memory?.Malloc(sizeof(byte)) ?? nint.Zero)), Memory),
"Int16Property" => new Int16Param(new((short*)(Memory?.Malloc(sizeof(short)) ?? nint.Zero)), Memory),
"Int32Property" => new Int32Param(new((int*)(Memory?.Malloc(sizeof(int)) ?? nint.Zero)), Memory),
"IntProperty" => new IntParam(new((int*)(Memory?.Malloc(sizeof(int)) ?? nint.Zero)), Memory),
"Int64Property" => new Int64Param(new((long*)(Memory?.Malloc(sizeof(long)) ?? nint.Zero)), Memory),
"UInt16Property" => new UInt16Param(new((ushort*)(Memory?.Malloc(sizeof(ushort)) ?? nint.Zero)), Memory),
"UInt32Property" => new UInt32Param(new((uint*)(Memory?.Malloc(sizeof(uint)) ?? nint.Zero)), Memory),
"UInt64Property" => new UInt64Param(new((ulong*)(Memory?.Malloc(sizeof(ulong)) ?? nint.Zero)), Memory),
"FloatProperty" => new FloatParam(new((float*)(Memory?.Malloc(sizeof(float)) ?? nint.Zero)), Memory),
"DoubleProperty" => new DoubleParam(new((double*)(Memory?.Malloc(sizeof(double)) ?? nint.Zero)), Memory),
"NameProperty" => new NameParam(new((FName*)(Memory?.Malloc(sizeof(FName)) ?? nint.Zero)), Memory),
"StrProperty" => new StringParam(new((FString*)(Memory?.Malloc(sizeof(FString)) ?? nint.Zero)), Memory),
"StructProperty" => CreateStructParam(factory.CreateFStructProperty(property.Ptr), Memory),
"TextProperty" => new TextParam(Memory?.Malloc(reflection.GetFTextSize()) ?? nint.Zero, reflection.GetFTextSize(), Memory),
"ObjectProperty" or "ClassProperty" or "ClassPtrProperty"
=> new ObjectParam(new((nint*)(Memory?.Malloc(sizeof(nint)) ?? nint.Zero)), Memory),
"ArrayProperty" => new ArrayParam(new((TArray<int>*)(Memory?.Malloc(sizeof(TArray<char>)) ?? nint.Zero)), Memory),
"EnumProperty" => new EnumParam(new(Memory?.Malloc(property.ElementSize) ?? nint.Zero), property.ElementSize, Memory),
"MapProperty" => new MapParam(new((TMap<int, int>*)(Memory?.Malloc(sizeof(TMap<int, int>)) ?? nint.Zero)), Memory),
"SetProperty" => new SetParam(new((TSet<int>*)(Memory?.Malloc(sizeof(TSet<int>)) ?? nint.Zero)), Memory),
"InterfaceProperty" => new InterfaceParam(new((TScriptInterface<int>*)(Memory?.Malloc(sizeof(TScriptInterface<char>)) ?? nint.Zero)), Memory),
"SoftClassProperty" => new SoftClassParam(new((TSoftClassPtr<int>*)(Memory?.Malloc(sizeof(TSoftClassPtr<char>)) ?? nint.Zero)), Memory),
"SoftObjectProperty" => new SoftObjectParam(new((TSoftObjectPtr<int>*)(Memory?.Malloc(sizeof(TSoftObjectPtr<char>)) ?? nint.Zero)), Memory),
"Utf8StrProperty" => new Utf8StringParam(new((FUtf8String*)(Memory?.Malloc(sizeof(FUtf8String)) ?? nint.Zero)), Memory),
"AnsiStrProperty" => new AnsiStringParam(new((FAnsiString*)(Memory?.Malloc(sizeof(FAnsiString)) ?? nint.Zero)), Memory),
"DelegateProperty" => new DelegateParam(new((FScriptDelegate*)(Memory?.Malloc(sizeof(FScriptDelegate)) ?? nint.Zero)), Memory),
"MulticastInlineDelegateProperty" => new MulticastInlineDelegateParam(new((FMulticastScriptDelegate*)(Memory?.Malloc(sizeof(FMulticastScriptDelegate)) ?? nint.Zero)), Memory),
"MulticastSparseDelegateProperty" => new MulticastSparseDelegateParam(new((FMulticastSparseDelegateProperty*)(Memory?.Malloc(sizeof(FMulticastSparseDelegateProperty)) ?? nint.Zero)), Memory),
_ => throw new NotSupportedException($"CreateParam with property {property.ClassPrivate.Name}")
};
}

public static string? GetParamNameFromProperty(IFProperty property, IUnrealFactory factory, ITypeReflectionInternal reflection)
{
try
{
var PropertyName = property.ClassPrivate.Name;
if (PropertyNameToParamName.TryGetValue(PropertyName, out var ParamName)) return ParamName;
var BlankParam = CreateParam(property, factory, reflection, null);
PropertyNameToParamName[PropertyName] = BlankParam.GetType().Name;
return PropertyNameToParamName[PropertyName];
}
catch (NotSupportedException ex)
{
return null;
}
}
}
21 changes: 21 additions & 0 deletions UE.Toolkit.Core/Types/Interfaces/ITypeReflectionInternal.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
namespace UE.Toolkit.Core.Types.Interfaces;

public interface ITypeReflectionInternal
{
#region FText

/// <summary>
/// Get the FText type used by the currently running version of the engine.
/// UE 5.4 and later have a significantly different FText implement compared to earlier versions.
/// </summary>
/// <returns>Type information for the FText.</returns>
Type GetFText();
/// <summary>
/// Get the size of the FText type used by the currently running version of the engine.
/// UE 5.4 and later have a significantly different FText implement compared to earlier versions.
/// </summary>
/// <returns>Size of FText.</returns>
int GetFTextSize();

#endregion
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
using UE.Toolkit.Core.Types.Interfaces;
using UE.Toolkit.Core.Types.Unreal.UE5_6_1;

namespace UE.Toolkit.Core.Types.Unreal.Common.FunctionParam;

public class AnsiStringParam(Ptr<FAnsiString> rvalue, IUnrealMemoryInternal? memory = null)
: FunctionParamCopyable<FAnsiString>(rvalue, "AnsiStrProperty", memory);
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
using UE.Toolkit.Core.Types.Interfaces;
using UE.Toolkit.Core.Types.Unreal.UE5_4_4;

namespace UE.Toolkit.Core.Types.Unreal.Common.FunctionParam;

public class ArrayParam(Ptr<TArray<int>> rvalue, IUnrealMemoryInternal? memory = null)
: FunctionParamCopyable<TArray<int>>(rvalue, "ArrayProperty", memory);
56 changes: 56 additions & 0 deletions UE.Toolkit.Core/Types/Unreal/Common/FunctionParam/BoolParam.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
using UE.Toolkit.Core.Types.Interfaces;

namespace UE.Toolkit.Core.Types.Unreal.Common.FunctionParam;

public class BoolParam(Ptr<bool> rvalue, int fieldMask, IUnrealMemoryInternal? memory = null) : IFunctionParam, IDisposable
{
public unsafe bool Value => *RValue.Value;

protected Ptr<bool> RValue => rvalue;

protected IUnrealMemoryInternal? Memory => memory;

public string PropertyType => "BoolProperty";

public int FieldMask => fieldMask;

private static unsafe void CopyByte(byte* source, byte* destination) => *destination = *source;

private unsafe bool IsStateDifferent(nint Destination)
{
var isDestTrue = (*(byte*)Destination & Convert.ToByte(FieldMask)) != 0;
return isDestTrue != Value;
}

public unsafe void Write(nint Destination)
{
if (FieldMask == byte.MaxValue) CopyByte((byte*)RValue.Value, (byte*)Destination);
else *(byte*)Destination ^= (byte)(Convert.ToByte(IsStateDifferent(Destination)) * FieldMask);
}

public unsafe void Read(nint Destination)
{
if (FieldMask == byte.MaxValue) CopyByte((byte*)Destination, (byte*)RValue.Value);
else *RValue.Value = IsStateDifferent(Destination);
}

protected bool IsDisposed;

public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}

protected virtual void Dispose(bool disposing)
{
if (!IsDisposed)
{
if (disposing) { }
unsafe { Memory?.Free((nint)RValue.Value); }
IsDisposed = true;
}
}

~BoolParam() => Dispose(false);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
using UE.Toolkit.Core.Types.Interfaces;

namespace UE.Toolkit.Core.Types.Unreal.Common.FunctionParam;

public class ByteParam(Ptr<byte> rvalue, IUnrealMemoryInternal? memory = null)
: FunctionParamCopyable<byte>(rvalue, "ByteProperty", memory);
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
using UE.Toolkit.Core.Types.Interfaces;
using UE.Toolkit.Core.Types.Unreal.UE5_4_4;

namespace UE.Toolkit.Core.Types.Unreal.Common.FunctionParam;

public class DelegateParam(Ptr<FScriptDelegate> rvalue, IUnrealMemoryInternal? memory = null)
: FunctionParamCopyable<FScriptDelegate>(rvalue, "DelegateProperty", memory);
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
using UE.Toolkit.Core.Types.Interfaces;

namespace UE.Toolkit.Core.Types.Unreal.Common.FunctionParam;

public class DoubleParam(Ptr<double> rvalue, IUnrealMemoryInternal? memory = null)
: FunctionParamCopyable<double>(rvalue, "DoubleProperty", memory);
41 changes: 41 additions & 0 deletions UE.Toolkit.Core/Types/Unreal/Common/FunctionParam/EnumParam.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
using System.Runtime.InteropServices;
using UE.Toolkit.Core.Types.Interfaces;

namespace UE.Toolkit.Core.Types.Unreal.Common.FunctionParam;

public class EnumParam(nint ptr, int size, IUnrealMemoryInternal? memory = null) : IFunctionParam, IDisposable
{
public string PropertyType => "EnumProperty";

private int Size => size;

private nint Ptr => ptr;

private IUnrealMemoryInternal? Memory => memory;

private unsafe void Copy(nint source, nint destination)
=> NativeMemory.Copy((void*)source, (void*)destination, (nuint)Size);

public void Write(nint Destination) => Copy(Ptr, Destination);
public void Read(nint Destination) => Copy(Destination, Ptr);

private bool IsDisposed;

public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}

protected virtual void Dispose(bool disposing)
{
if (!IsDisposed)
{
if (disposing) { }
Memory?.Free(Ptr);
IsDisposed = true;
}
}

~EnumParam() => Dispose(false);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
using UE.Toolkit.Core.Types.Interfaces;

namespace UE.Toolkit.Core.Types.Unreal.Common.FunctionParam;

public class FloatParam(Ptr<float> rvalue, IUnrealMemoryInternal? memory = null)
: FunctionParamCopyable<float>(rvalue, "FloatProperty", memory);
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
using UE.Toolkit.Core.Types.Interfaces;

namespace UE.Toolkit.Core.Types.Unreal.Common.FunctionParam;

public class Int16Param(Ptr<short> rvalue, IUnrealMemoryInternal? memory = null)
: FunctionParamCopyable<short>(rvalue, "Int16Property", memory);
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
using UE.Toolkit.Core.Types.Interfaces;

namespace UE.Toolkit.Core.Types.Unreal.Common.FunctionParam;

public class Int32Param(Ptr<int> rvalue, IUnrealMemoryInternal? memory = null)
: FunctionParamCopyable<int>(rvalue, "Int32Property", memory);
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
using UE.Toolkit.Core.Types.Interfaces;

namespace UE.Toolkit.Core.Types.Unreal.Common.FunctionParam;

public class Int64Param(Ptr<long> rvalue, IUnrealMemoryInternal? memory = null)
: FunctionParamCopyable<long>(rvalue, "Int64Property", memory);
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
using UE.Toolkit.Core.Types.Interfaces;

namespace UE.Toolkit.Core.Types.Unreal.Common.FunctionParam;

public class Int8Param(Ptr<byte> rvalue, IUnrealMemoryInternal? memory = null)
: FunctionParamCopyable<byte>(rvalue, "Int8Property", memory);
6 changes: 6 additions & 0 deletions UE.Toolkit.Core/Types/Unreal/Common/FunctionParam/IntParam.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
using UE.Toolkit.Core.Types.Interfaces;

namespace UE.Toolkit.Core.Types.Unreal.Common.FunctionParam;

public class IntParam(Ptr<int> rvalue, IUnrealMemoryInternal? memory = null)
: FunctionParamCopyable<int>(rvalue, "IntProperty", memory);
Loading
Loading