Skip to content
Open
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: 3 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,15 @@
- Add new properties to a class/struct's type information.
- Register a new struct into the type information system.

## Supported Engine Version and Games

Object XML requires game-specific support with an extension mod. Supported games [are listed here](#installing-the-ue-toolkit-extension-mod).
## Supported Engine Versions

| Feature | 4.27 | 5.0 | 5.1 | 5.2 | 5.3 | 5.4 | 5.5 | 5.6 | 5.7
| - | - | - | - | - | - | - | - | - | - |
| Object Logging |✅|✅|✅|✅|✅|✅|✅|✅|✅
| Object Editing |✅|❔|❔||❔|✅|❔|❔|❔
| Object Editing |✅|❔|❔||❔|✅|❔|❔|❔
| `FMemory` Functions |✅|✅|✅|✅|✅|✅|✅|✅|✅
| Dumper |✅|✅|✅|✅|️️️️️️✅|✅|✅|✅️|️️️️✅️
| Property Editing (Object XML) |✅|❔|❔||❔|✅|❔|❔|❔
| Property Editing (Object XML) |✅|❔|❔||❔|✅|❔|❔|❔
| Add List Entry (Object XML) |✅|❔|❔|❔|❔|❔|❔|❔|❔
| Add Map Entry (Object XML) |✅|❔|❔|❔|❔|❔|❔|❔|❔
| Type Information |✅|✅|✅|✅|✅|✅|✅|✅|✅
Expand All @@ -50,11 +48,6 @@ I recommend the P3R guides, since they're the newest and cover GamePass. __You o
1. Go to [Releases](https://github.com/RyoTune/UE.Toolkit/releases) and download the latest version of `UE.Toolkit.Reloaded.7z`
2. Drag and drop the `7z` file into Reloaded to install.

### Installing the UE Toolkit Extension Mod
Install the extension mod for your game to be able to use all features (Object XML Editing).
- __Clair Obscur__ - https://github.com/RyoTune/E33.UEToolkit/releases
- __Persona 3 Reload__ - https://github.com/RyoTune/P3R.UEToolkit/releases

## Editing Objects with XML
In supported games, you're able to edit any object with just a simple text file (XML). No Unreal Engine, no file unpacking/repacking/cooking, and no hex 🤮 editing. Just _Notepad++_ and a dream ✨! ~~Or Notepad if you hate yourself...~~

Expand Down
67 changes: 67 additions & 0 deletions UE.Toolkit.Core/Types/Unreal/Common/DynamicMap/Base.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using UE.Toolkit.Core.Types.Unreal.Factories;
using UE.Toolkit.Core.Types.Unreal.Factories.Interfaces;
using UE.Toolkit.Core.Types.Unreal.UE5_4_4;

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

public interface IDynamicMapSizedType
{
int DynSizeOf();
}

public interface IDynamicMapValueType : IDynamicMapSizedType;

public class DynamicMapValueSystemType(Type type) : IDynamicMapValueType
{
private Type Type => type;

public int DynSizeOf() => Marshal.SizeOf(type);
}

public class DynamicMapValueUnrealProperty(IFProperty type) : IDynamicMapValueType
{
private IFProperty Type => type;

public int DynSizeOf() => Type.ElementSize;
}

public interface IDynamicMapKeyType : IDynamicMapSizedType
{
bool FromString(string text, [NotNullWhen(true)] out IDynamicMapKey? key);
IDynamicMapKey FromPtr(nint ptr);
}

public abstract class BaseDynamicMapKeyType(IFMapProperty property, IUnrealFactory factory) : IDynamicMapKeyType
{
protected IUnrealFactory Factory => factory;
protected IFMapProperty Property => property;

public abstract bool FromString(string text, [NotNullWhen(true)] out IDynamicMapKey? key);
public abstract IDynamicMapKey FromPtr(nint ptr);
public abstract int DynSizeOf();
}

public interface IDynamicMapKey
{
void Write(nint ptr);
uint GetTypeHash();
}

public abstract class BaseDynamicMapKey<TInner>(TInner value)
: IDynamicMapKey, IMapHashable where TInner : IMapHashable
{
protected TInner Value { get; set; } = value;
public uint GetTypeHash() => Value!.GetTypeHash();
public abstract void Write(nint ptr);
public override string ToString() => Value.ToString();

public override bool Equals(object? obj)
{
if (obj is null) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != GetType()) return false;
return Value.Equals(((BaseDynamicMapKey<TInner>)obj).Value);
}
}
130 changes: 130 additions & 0 deletions UE.Toolkit.Core/Types/Unreal/Common/DynamicMap/Integer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
using System.Diagnostics.CodeAnalysis;
using UE.Toolkit.Core.Types.Unreal.Factories;
using UE.Toolkit.Core.Types.Unreal.Factories.Interfaces;
using UE.Toolkit.Core.Types.Unreal.UE5_4_4;

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

public struct HashableByte(byte value) : IMapHashable, IEquatable<HashableByte>
{
public byte Value = value;
public uint GetTypeHash() => Value;
public bool Equals(HashableByte other) => other.Value == Value;
public override string ToString() => Value.ToString();
}

public struct HashableShort(short value) : IMapHashable, IEquatable<HashableShort>
{
public short Value = value;
public uint GetTypeHash() => (uint)Value;
public bool Equals(HashableShort other) => other.Value == Value;
public override string ToString() => Value.ToString();
}

public struct HashableLong(long value) : IMapHashable, IEquatable<HashableLong>
{
public long Value = value;
public uint GetTypeHash() => (uint)(Value + (Value >> 32 * 23));
public bool Equals(HashableLong other) => other.Value == Value;
public override string ToString() => Value.ToString();
}

public class Int8DynamicMapKeyType(IFMapProperty property, IUnrealFactory factory)
: BaseDynamicMapKeyType(property, factory)
{
public override int DynSizeOf() => (int)Factory.GetAlignment(Property.ValueProp);

public override bool FromString(string text, [NotNullWhen(true)] out IDynamicMapKey? key)
{
key = null;
if (!byte.TryParse(text, out var value))
{
return false;
}
key = new Int8DynamicMapKey(new(value));
return true;
}

public override unsafe IDynamicMapKey FromPtr(nint ptr) => new Int8DynamicMapKey(new(*(byte*)ptr));
}

public class Int8DynamicMapKey(HashableByte value)
: BaseDynamicMapKey<HashableByte>(value)
{
public override unsafe void Write(nint ptr) => *(byte*)ptr = Value.Value;
}

public class Int16DynamicMapKeyType(IFMapProperty property, IUnrealFactory factory)
: BaseDynamicMapKeyType(property, factory)
{
public override int DynSizeOf() => int.Max((short)Factory.GetAlignment(Property.ValueProp), 2);

public override bool FromString(string text, [NotNullWhen(true)] out IDynamicMapKey? key)
{
key = null;
if (!short.TryParse(text, out var value))
{
return false;
}
key = new Int16DynamicMapKey(new(value));
return true;
}

public override unsafe IDynamicMapKey FromPtr(nint ptr) => new Int16DynamicMapKey(new(*(short*)ptr));
}

public class Int16DynamicMapKey(HashableShort value)
: BaseDynamicMapKey<HashableShort>(value)
{
public override unsafe void Write(nint ptr) => *(short*)ptr = Value.Value;
}

public class IntDynamicMapKeyType(IFMapProperty property, IUnrealFactory factory)
: BaseDynamicMapKeyType(property, factory)
{
public override int DynSizeOf() => int.Max((int)Factory.GetAlignment(Property.ValueProp), 4);

public override bool FromString(string text, [NotNullWhen(true)] out IDynamicMapKey? key)
{
key = null;
if (!int.TryParse(text, out var value))
{
return false;
}
key = new IntDynamicMapKey(new(value));
return true;
}

public override unsafe IDynamicMapKey FromPtr(nint ptr) => new IntDynamicMapKey(new(*(int*)ptr));
}

public class IntDynamicMapKey(HashableInt value)
: BaseDynamicMapKey<HashableInt>(value)
{
public override unsafe void Write(nint ptr) => *(int*)ptr = Value.Value;
}

public class Int64DynamicMapKeyType(IFMapProperty property, IUnrealFactory factory)
: BaseDynamicMapKeyType(property, factory)
{
public override int DynSizeOf() => 8;

public override bool FromString(string text, [NotNullWhen(true)] out IDynamicMapKey? key)
{
key = null;
if (!long.TryParse(text, out var value))
{
return false;
}
key = new Int64DynamicMapKey(new(value));
return true;
}

public override unsafe IDynamicMapKey FromPtr(nint ptr) => new Int64DynamicMapKey(new(*(long*)ptr));
}

public class Int64DynamicMapKey(HashableLong value)
: BaseDynamicMapKey<HashableLong>(value)
{
public override unsafe void Write(nint ptr) => *(long*)ptr = Value.Value;
}
26 changes: 26 additions & 0 deletions UE.Toolkit.Core/Types/Unreal/Common/DynamicMap/Name.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System.Diagnostics.CodeAnalysis;
using UE.Toolkit.Core.Types.Unreal.Factories;
using UE.Toolkit.Core.Types.Unreal.Factories.Interfaces;
using UE.Toolkit.Core.Types.Unreal.UE5_4_4;

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

public class NameDynamicMapKeyType(IFMapProperty property, IUnrealFactory factory)
: BaseDynamicMapKeyType(property, factory)
{
public override bool FromString(string text, [NotNullWhen(true)] out IDynamicMapKey? key)
{
key = new NameDynamicMapKey(new(text));
return true;
}

public override unsafe IDynamicMapKey FromPtr(nint ptr) => new NameDynamicMapKey(*(FName*)ptr);

public override int DynSizeOf() => 8;
}

public class NameDynamicMapKey(FName value)
: BaseDynamicMapKey<FName>(value)
{
public override unsafe void Write(nint ptr) => *(FName*)ptr = Value;
}
9 changes: 9 additions & 0 deletions UE.Toolkit.Core/Types/Unreal/Common/SoftObjectPath.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace UE.Toolkit.Core.Types.Unreal.Common;

public interface ISoftObjectPath
{
nint Ptr { get; }

void SetAssetPath(string Value);
int GetSizeOf();
}
14 changes: 14 additions & 0 deletions UE.Toolkit.Core/Types/Unreal/Factories/BaseUnrealFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,20 @@ public T Cast<T>(IPtr obj)
}
}
public abstract nint SizeOf<T>();
// Type alignment when placed inline, such as the value type in a TMap
public nint GetAlignment(IFProperty prop)
{
return prop.ClassPrivate.Name switch
{
"Int8Property" or "BoolProperty" => 1,
"Int16Property" or "UInt16Property" => 2,
"IntProperty" or "Int32Property" or "UInt32Property" or "FloatProperty" or "NameProperty" => 4,
"Int64Property" or "UInt64Property" or "DoubleProperty" or "StrProperty" or "TextProperty" or "ObjectProperty"
or "SoftObjectProperty" or "SoftClassProperty" or "ArrayProperty" => 8,
"StructProperty" => CreateFStructProperty(prop.Ptr).Struct.MinAlignment,
_ => throw new NotSupportedException(prop.ClassPrivate.Name)
};
}

public abstract IFProperty CreateFProperty(nint ptr);
public abstract IFBoolProperty CreateFBoolProperty(nint ptr);
Expand Down
2 changes: 2 additions & 0 deletions UE.Toolkit.Core/Types/Unreal/Factories/IUnrealFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ public interface IUnrealFactory
{
T Cast<T>(IPtr obj);
nint SizeOf<T>();
nint GetAlignment(IFProperty prop);

IUnrealMemoryInternal? Memory { get; set; }

IFProperty CreateFProperty(nint ptr);
Expand Down
3 changes: 3 additions & 0 deletions UE.Toolkit.Core/Types/Unreal/Factories/Interfaces/IUEnum.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Diagnostics.CodeAnalysis;
using UE.Toolkit.Core.Types.Unreal.UE5_4_4;

namespace UE.Toolkit.Core.Types.Unreal.Factories.Interfaces;
Expand All @@ -7,4 +8,6 @@ public interface IUEnum : IUField
string CppType { get; }

TArray<TPair<FName, long>> Names { get; }

bool TryParse(string name, bool ignoreCase, [NotNullWhen(true)] out long? value);
}
24 changes: 24 additions & 0 deletions UE.Toolkit.Core/Types/Unreal/Factories/UE4_27_2/UnrealFactory.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using UE.Toolkit.Core.Common;
using UE.Toolkit.Core.Types.Unreal.Factories.Interfaces;
Expand Down Expand Up @@ -328,7 +329,7 @@
}

public unsafe class UUserDefinedEnumUE4_27_2(nint ptr, IUnrealFactory factory)
: UEnumUE4_27_2(ptr, factory), IUUserDefinedEnum

Check warning on line 332 in UE.Toolkit.Core/Types/Unreal/Factories/UE4_27_2/UnrealFactory.cs

View workflow job for this annotation

GitHub Actions / build

Parameter 'nint ptr' is captured into the state of the enclosing type and its value is also passed to the base constructor. The value might be captured by the base class as well.
{
public TMap<FName, FText> DisplayNameMap => *(TMap<FName, FText>*)(&((UUserDefinedEnum*)ptr)->DisplayNameMap);
}
Expand All @@ -339,6 +340,29 @@
private readonly UEnum* _self = (UEnum*)ptr;
public string CppType => _self->cpp_type.ToString();
public UE.Toolkit.Core.Types.Unreal.UE5_4_4.TArray<UE.Toolkit.Core.Types.Unreal.UE5_4_4.TPair<FName, long>> Names => _self->entries;

public bool TryParse(string name, bool ignoreCase, [NotNullWhen(true)] out long? value)
{
value = null;
if (ignoreCase)
{
name = name.ToLower();
}
var NamesArray = new TArrayList<TPair<FName, long>>(&_self->entries, _factory.Memory);

Check warning on line 351 in UE.Toolkit.Core/Types/Unreal/Factories/UE4_27_2/UnrealFactory.cs

View workflow job for this annotation

GitHub Actions / build

Possible null reference argument for parameter '_Allocator' in 'TArrayList<TPair<FName, long>>.TArrayList(TArray<TPair<FName, long>>* _Self, IUnrealMemoryInternal _Allocator)'.
foreach (var pDiscriminant in NamesArray)
{
var Discriminant = pDiscriminant.Value;
var CheckName = Discriminant->Key.ToString();
if (ignoreCase)
CheckName = CheckName.ToLower();
if (CheckName == name)
{
value = Discriminant->Value;
return true;
}
}
return false;
}
}

public unsafe class UScriptStructUE4_27_2(nint ptr, IUnrealFactory factory)
Expand Down Expand Up @@ -383,7 +407,7 @@
public class IFFieldEnumerable(IFField? initial)
: IEnumerator<IFField>, IEnumerable<IFField>
{
private IFField? _current = initial;

Check warning on line 410 in UE.Toolkit.Core/Types/Unreal/Factories/UE4_27_2/UnrealFactory.cs

View workflow job for this annotation

GitHub Actions / build

Parameter 'IFField? initial' is captured into the state of the enclosing type and its value is also used to initialize a field, property, or event.
private bool isInitial = true;

public bool MoveNext()
Expand Down Expand Up @@ -414,7 +438,7 @@
public class IUFieldEnumerable(IUField? initial)
: IEnumerator<IUField>, IEnumerable<IUField>
{
private IUField? _current = initial;

Check warning on line 441 in UE.Toolkit.Core/Types/Unreal/Factories/UE4_27_2/UnrealFactory.cs

View workflow job for this annotation

GitHub Actions / build

Parameter 'IUField? initial' is captured into the state of the enclosing type and its value is also used to initialize a field, property, or event.
private bool isInitial = true;

public bool MoveNext()
Expand Down Expand Up @@ -480,7 +504,7 @@
}

public unsafe class UClassUE4_27_2(nint ptr, IUnrealFactory factory)
: UStructUE4_27_2(ptr, factory), IUClass

Check warning on line 507 in UE.Toolkit.Core/Types/Unreal/Factories/UE4_27_2/UnrealFactory.cs

View workflow job for this annotation

GitHub Actions / build

Parameter 'IUnrealFactory factory' is captured into the state of the enclosing type and its value is also passed to the base constructor. The value might be captured by the base class as well.
{
private readonly UClass* _self = (UClass*)ptr;

Expand All @@ -491,7 +515,7 @@
{
var FuncMapDict = new TMapDictionary<FName, Ptr<UFunction>>(
(TMap<FName, Ptr<UFunction>>*)(&_self->func_map),
_factory.Memory

Check warning on line 518 in UE.Toolkit.Core/Types/Unreal/Factories/UE4_27_2/UnrealFactory.cs

View workflow job for this annotation

GitHub Actions / build

Possible null reference argument for parameter '_Allocator' in 'TMapDictionary<FName, Ptr<UFunction>>.TMapDictionary(TMap<FName, Ptr<UFunction>>* _Self, IUnrealMemoryInternal _Allocator, Action<string>? _DebugCallback = null)'.
);
return FuncMapDict.TryGetValue(new(Name), out var Function)
? factory.CreateUFunction((nint)Function.Value->Value)
Expand Down
24 changes: 24 additions & 0 deletions UE.Toolkit.Core/Types/Unreal/Factories/UE5_4_4/UnrealFactory.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using UE.Toolkit.Core.Common;
using UE.Toolkit.Core.Types.Unreal.Factories.Interfaces;
Expand Down Expand Up @@ -276,6 +277,29 @@ public unsafe class UEnum_UE5_4_4(nint ptr, IUnrealFactory factory)
private readonly UEnum* _self = (UEnum*)ptr;
public string CppType => _self->CppType.ToString();
public TArray<TPair<FName, long>> Names => _self->Names;

public bool TryParse(string name, bool ignoreCase, [NotNullWhen(true)] out long? value)
{
value = null;
if (ignoreCase)
{
name = name.ToLower();
}
var NamesArray = new TArrayList<TPair<FName, long>>(&_self->Names, _factory.Memory);
foreach (var pDiscriminant in NamesArray)
{
var Discriminant = pDiscriminant.Value;
var CheckName = Discriminant->Key.ToString();
if (ignoreCase)
CheckName = CheckName.ToLower();
if (CheckName == name)
{
value = Discriminant->Value;
return true;
}
}
return false;
}
}

public unsafe class UScriptStruct_UE5_4_4(nint ptr, IUnrealFactory factory)
Expand Down
Loading
Loading