UObjectandUDataTablelogging.- Edit object data (numbers and text) with just XML text files.
- Simplified
UObjectandUDataTableediting at runtime. - Access to
FMemoryfunctions (supported functions). - Mod for dumping game types to C# types.
- Methods for creating
FStringandFText. - Get type information from a class or struct.
- Extend the length of a type and add custom constructor code.
- Add new properties to a class/struct's type information.
- Register a new struct into the type information system.
Object XML requires game-specific support with an extension mod. Supported games are listed here.
| Feature | UE 4.27.2 | UE 5.4.4 |
|---|---|---|
| Object Logging | โ | โ |
| Object Editing | โ | โ |
FMemory Functions |
โ | โ |
| Dumper | โ | โ |
| Property Editing (Object XML) | โ | โ |
| Add List Entry (Object XML) | โ | โ |
| Add Map Entry (Object XML) | โ | โ |
| Type Information | โ | โ |
| Custom Constructor | โ | โ |
| Add Properties | โ | โ |
| Register Struct | โ | โ |
Features marked with โ are currently untested.
I recommend the P3R guides, since they're the newest and cover GamePass. You only really need to add your game to Reloaded, you can ignore anything past that.
Beginner's Guide to Modding Persona 3 Reload
Beginner's Guide to Modding P3R (Xbox / Game Pass)
- Go to Releases and download the latest version of
UE.Toolkit.Reloaded.7z - Drag and drop the
7zfile into Reloaded to install.
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
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...
On top of that, your edits only edit what you're editing! Or put non-stupidly, mod merging is built in ๐! Mods only conflict if they edit the exact same thing, like the price of the same item for example.
- UE Toolkit (Main Mod)
- The UE Toolkit extension mod for your game.
- Enable "View File Extensions" on Windows
This will be a bit abridged since I expect most people using this mod are experienced with Reloaded and my other mods.
- Create a Reloaded mod with a Mod Dependency on the extension mod. You can add the main mod too if you like, but it's not required.
- In your mod's folder, create the following folders:
MOD_FOLDER/ue-toolkit/objects - Inside the
objectsfolder, you will place any Object XML files you make. They can be in sub-folders for organization.
Not recommended or really supported, but some people don't like change and/or hate Reloaded. Reloaded is great though ๐!
Only supported on Clair Obscur's extension mod.
- In the game's
~modsfolder, create the following folders:../~mods/ue-toolkit/objects - Inside the
objectsfolder, create a folder for your specific mod:../~mods/ue-toolkit/objects/My Super Cool Mod - Inside your mod's folder, you will place any Object XML files you make. They can be in sub-folders for organization.
Now for actually editing an object. You only need two (2) pieces of information to get started: the object's Name and its Class (and RowStruct for DataTables).
You can easily get both using FModel.
- Inside your chosen folder from the previous steps, create a new text file.
- Name the file the same as the object's Name.
- Change the file extension from
.txtto.obj.xml. Your file's name should be similar to:DT_jRPG_CharacterDefinitions.obj.xml - Open your file in a text editor.
Generally, your XML will match the "shape" of the object, starting with its Class. I highly recommend looking at the object in FModel or going to the extension mod's source if you can read code.
The best I can give is a general example, since it'll look very different depending on what you're editing.
The first element in your XML, aka the Root Element, will be the object's Class. In this example, taken from Clair Obscur's DL_Goblu_00Entry.uasset, the Class is a DataLayerAsset.
<DataLayerAsset>
</DataLayerAsset>A DataLayerAsset has two properties: DataLayerType and DebugColor. We'll first edit DataLayerType.
<DataLayerAsset>
<DataLayerType value="1"/>
OR
<DataLayerType>1</DataLayerType>
</DataLayerAsset>- Add a new element with the same name as the property, in this case
DataLayerType. - Add a
valueattribute to the element, with the value you want to set it to. - OR between the open and closing tags.
We started with DataLayerType since it's value is directly in DataLayerAsset, but what if a property has its own properties? For example, DebugColor has RGB properties.
Well, it's not too different than what we already have with the Root Element.
<DataLayerAsset>
<DataLayerType value="1"/>
<DebugColor>
<R value="255"/>
<G value="255"/>
<B value="255"/>
</DebugColor>
</DataLayerAsset>- Same as before, add a new element with the name of the property but with open and closing tags.
- Inside this element, add new elements with the names of the properties (like
DataLayerTypeearlier.)
If a sub-property has its own property, then just repeat the same process as needed.
For editing lists, arrays, or DataTables a bit later, the process a slightly different since we need to set which item we want to edit. For that, we use a special Item element.
<ArmorItemListTable>
<Data>
<Item id="2">
ย ย <EquipID value="100"/>
</Item>
</Data>
</ArmorItemListTable>In this example, from Persona 3 Reload's DatArmorItemListTable (not a DataTable), ArmorItemListTable is the Class, which has a Data property that's a list of of armor items.
First, we add an Item element with an id attribute. The ID is which item in the list we want to edit. The first item would have an ID of 1, the second item 2, third 3, and so on.
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.
Finally, DataTables! There's only two notable differences: the row-struct attribute and ids are now row names. This example is from Clair Obscur's DT_jRPG_CharacterDefinitions.uasset.
<DataTable row-struct="S_jRPG_CharacterDefinition">
<Item id="Lune">
<CharacterDisplayName value="TEST1"/>
</Item>
<Item id="Maelle">
<CharacterDisplayName value="TEST2"/>
</Item>
<Item id="Sciel">
<CharacterDisplayName value="TEST3"/>
</Item>
</DataTable>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.
That covers everything, congrats on finishing this ๐๐๐!
For enum properties (stuff like EDataLayerType::Runtime, where it starts with an E), you can use name values if you know them. You can find them in the extension mod's source or in FModel.
- Launch game and get to the main menu or later.
- In Reloaded, right-click
UE Toolkit: Dumperand selectConfigure. - In the config window, fill in any settings you want, then click
Saveto start dumping objects. - Generated files will be located in the mod folder: right-click
UE Toolkit: Dumperand selectOpen Folder.
Some Unreal types may not be generated and need to be supplied. UE.Toolkit.Core includes any types
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.
When writing code mods that interact with objects allocated by Unreal, allocation must be handled on the mod's side by the
same allocator that created it in the game's code. IUnrealMemory contains the following methods for interacting with
Unreal's memory allocator:
nint Malloc(nint count, int alignment = Default): Allocates a block of memory with the globalFMemory.void Free(nint original): Deallocates a block of memory allocated with the globalFMemory.nint Realloc(nint ptr, nint count, int alignment = Default): Reallocates a block of memory with a new size using the globalFMemorybool GetAllocSize(nint ptr, ref nint size): Queries the globalFMemoryto get the size of an allocation created using it.nint MallocZeroed(nint count, int alignment = Default): Allocates and clears a block of memory with the globalFMemorynint QuantizeSize(nint count, int alignment = Default): For some allocators this will return the actual size that should be requested to eliminate internal fragmentation.
(If alignment is not specified, Default will be 16 bytes for blocks 16 bytes or larger, and 8 bytes if it's smaller)
IUnrealClasses contains the methods AddConstructor, which passes in a custom callback that executes after the original constructor has run. AddExtension additionally allows for increasing the size of the object by ExtraSize bytes. Do note that this should only be used on types that don't have any subtypes (no type has it at the beginning as a "Super" property).
It's possible to add new properties into the property list for a given object in code using the Add[Type]Property methods in IUnrealClasses. This allows that field to be readable from Object XML and blueprints:
// UGlobalWork is P3R's game instance class
_context._toolkitClasses.AddConstructor<UGlobalWork>(obj =>
{
if (!CreatedFields)
{
_context._toolkitClasses.AddU32Property<UFldManagerSubsystem>("CurrFieldMajor", 0x54, out _);
_context._toolkitClasses.AddU32Property<UFldManagerSubsystem>("CurrFieldMinor", 0x58, out _);
_context._toolkitClasses.AddU32Property<UFldManagerSubsystem>("CurrFieldSub", 0x5c, out _);
CreatedFields = true;
}
});When using the dumper, these fields will appear in the generated file:
[StructLayout(LayoutKind.Explicit, Pack = 16, Size = 0x400)]
public unsafe struct UFldManagerSubsystem
{
[FieldOffset(0x0)] public UGameInstanceSubsystem Super; // Size: 0x30
[FieldOffset(0x30)] public FMulticastScriptDelegate mOnEventCallField_; // Size: 0x10
[FieldOffset(0xB8)] public AFldLevelManager* mLevelManager_; // Size: 0x8
[FieldOffset(0xC8)] public UAppCharacterComp* mPlayerComp_; // Size: 0x8
// Other fields ...
}[StructLayout(LayoutKind.Explicit, Pack = 16, Size = 0x400)]
public unsafe struct UFldManagerSubsystem
{
[FieldOffset(0x0)] public UGameInstanceSubsystem Super; // Size: 0x30
[FieldOffset(0x30)] public FMulticastScriptDelegate mOnEventCallField_; // Size: 0x10
[FieldOffset(0x54)] public uint CurrFieldMajor; // Size: 0x4
[FieldOffset(0x58)] public uint CurrFieldMinor; // Size: 0x4
[FieldOffset(0x5C)] public uint CurrFieldSub; // Size: 0x4
[FieldOffset(0xB8)] public AFldLevelManager* mLevelManager_; // Size: 0x8
[FieldOffset(0xC8)] public UAppCharacterComp* mPlayerComp_; // Size: 0x8
// Other fields ...
}A new struct can be registered into UE's type reflection. IUnrealClasses contains methods (Create[Type]Param) to build a list of property params to pass into CreateScriptStruct:
TryCreateScriptStruct("AgePanelSection", 0x30, new List<IFPropertyParams>
{
_context._toolkitClasses.CreateF32Param("X1", 0),
_context._toolkitClasses.CreateF32Param("X2", 4),
_context._toolkitClasses.CreateF32Param("Y1", 8),
_context._toolkitClasses.CreateF32Param("Y2", 0xc),
_context._toolkitClasses.CreateF32Param("Field28", 0x28),
});Much like adding new fields, this is viewable in Object XML and blueprints.
- UE4SS team, for object dumping reference.
- Rirurin, for object dumping reference.
