From dc96c18c1f50193e30ad11fc4562acd02904121f Mon Sep 17 00:00:00 2001 From: Eliot Date: Wed, 24 Apr 2024 19:47:38 +0200 Subject: [PATCH 01/50] Possible fix for RL https://github.com/UE-Explorer/UE-Explorer/issues/54 --- src/Core/Tables/UExportTableItem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Core/Tables/UExportTableItem.cs b/src/Core/Tables/UExportTableItem.cs index 7bc7a608..7cd80bd0 100644 --- a/src/Core/Tables/UExportTableItem.cs +++ b/src/Core/Tables/UExportTableItem.cs @@ -191,7 +191,7 @@ public void Deserialize(IUnrealStream stream) if (stream.Package.Build == UnrealPackage.GameBuild.BuildName.RocketLeague && stream.LicenseeVersion >= 22) { - SerialOffset = stream.ReadIndex(); + SerialOffset = (int)stream.ReadInt64(); goto streamExportFlags; } #endif From 84b46eed59954b70970d6bb417d5dd61135e365a Mon Sep 17 00:00:00 2001 From: Eliot Date: Wed, 24 Apr 2024 23:24:33 +0200 Subject: [PATCH 02/50] Fix T3D syntax ouput from "object end" to "end object" --- src/Core/Classes/UObjectDecompiler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Core/Classes/UObjectDecompiler.cs b/src/Core/Classes/UObjectDecompiler.cs index 31f505fd..e4416cb2 100644 --- a/src/Core/Classes/UObjectDecompiler.cs +++ b/src/Core/Classes/UObjectDecompiler.cs @@ -32,7 +32,7 @@ public virtual string Decompile() UDecompilingState.RemoveTabs(1); } - return $"{output}{UDecompilingState.Tabs}object end" + + return $"{output}{UDecompilingState.Tabs}end object" + $"\r\n{UDecompilingState.Tabs}" + $"// Reference: {Class.Name}'{GetOuterGroup()}'"; } From b6136962ae9475b2d0e519121b8274f1444f84c7 Mon Sep 17 00:00:00 2001 From: Eliot Date: Thu, 25 Apr 2024 02:54:10 +0200 Subject: [PATCH 03/50] Fix: Inline subobjects even if it is empty. (these are usually inserted by the compiler, but we must declare them for re-compiling purposes). --- src/Core/Classes/UDefaultProperty.cs | 58 +++++++++++++--------------- 1 file changed, 26 insertions(+), 32 deletions(-) diff --git a/src/Core/Classes/UDefaultProperty.cs b/src/Core/Classes/UDefaultProperty.cs index de35b040..ac460ed4 100644 --- a/src/Core/Classes/UDefaultProperty.cs +++ b/src/Core/Classes/UDefaultProperty.cs @@ -679,44 +679,38 @@ private string DeserializeDefaultPropertyValue(PropertyType type, ref Deserializ { var constantObject = _Buffer.ReadObject(); Record(nameof(constantObject), constantObject); - if (constantObject != null) + if (constantObject == null) { - var inline = false; - // If true, object is an archetype or subobject. - if (constantObject.Outer == _Container && - (deserializeFlags & DeserializeFlags.WithinStruct) == 0) + // =none + propertyValue = "none"; + break; + } + + // If the object is part of the current container, then it probably was an inlined declaration. + bool shouldInline = constantObject.Outer == _Container && + (deserializeFlags & DeserializeFlags.WithinStruct) == 0; + if (shouldInline) + { + // Unknown objects are only deserialized on demand. + constantObject.BeginDeserializing(); + propertyValue = constantObject.Decompile() + "\r\n"; + + _TempFlags |= DoNotAppendName; + if ((deserializeFlags & DeserializeFlags.WithinArray) != 0) { - // Unknown objects are only deserialized on demand. - constantObject.BeginDeserializing(); - if (constantObject.Properties != null && constantObject.Properties.Count > 0) - { - inline = true; - propertyValue = constantObject.Decompile() + "\r\n" + UDecompilingState.Tabs; - - _TempFlags |= DoNotAppendName; - if ((deserializeFlags & DeserializeFlags.WithinArray) != 0) - { - _TempFlags |= ReplaceNameMarker; - propertyValue += $"%ARRAYNAME%={constantObject.Name}"; - } - else - { - propertyValue += $"{Name}={constantObject.Name}"; - } - } + _TempFlags |= ReplaceNameMarker; + propertyValue += $"{UDecompilingState.Tabs}%ARRAYNAME%={constantObject.Name}"; } - - if (!inline) - // =CLASS'Package.Group(s)+.Name' + else { - propertyValue = PropertyDisplay.FormatLiteral(constantObject); + propertyValue += $"{UDecompilingState.Tabs}{Name}={constantObject.Name}"; } + + break; } - else - { - // =none - propertyValue = "none"; - } + + // =Class'Package.Group.Name' + propertyValue = PropertyDisplay.FormatLiteral(constantObject); break; } From 61fa3ae2334a6aa5869df64ebd2e6044b26acd0e Mon Sep 17 00:00:00 2001 From: Eliot Date: Thu, 25 Apr 2024 04:12:01 +0200 Subject: [PATCH 04/50] Fix: when inlining a subobject, don't insert the `class=classref` parameter if the object has an archetype reference, meaning the subobject is an override. --- src/Branch/PackageObjectLegacyVersion.cs | 9 ++++++-- src/Core/Classes/UObject.cs | 5 +++++ src/Core/Classes/UObjectDecompiler.cs | 27 ++++++++++++++++++------ src/Core/Tables/UExportTableItem.cs | 16 ++++++-------- 4 files changed, 39 insertions(+), 18 deletions(-) diff --git a/src/Branch/PackageObjectLegacyVersion.cs b/src/Branch/PackageObjectLegacyVersion.cs index e5fe178d..b685566f 100644 --- a/src/Branch/PackageObjectLegacyVersion.cs +++ b/src/Branch/PackageObjectLegacyVersion.cs @@ -5,7 +5,7 @@ namespace UELib.Branch public enum PackageObjectLegacyVersion { Undefined = 0, - + /// /// This is one particular update with A LOT of general package changes. /// @@ -73,9 +73,14 @@ public enum PackageObjectLegacyVersion // FIXME: Version EnumTagNameAddedToBytePropertyTag = UE3, + ObjectFlagsSizeChangedToULong = 195, + ArchetypeAddedToExports = 220, + // 227 according to the GoW client FixedVerticesToArrayFromPoly = 227, + SerialSizeConditionRemoved = 249, + // Thanks to @https://www.gildor.org/ for reverse-engineering the lazy-loader version changes. LazyLoaderFlagsAddedToLazyArray = 251, StorageSizeAddedToLazyArray = 254, @@ -86,7 +91,7 @@ public enum PackageObjectLegacyVersion // -- albeit the exact nature is not clear // -- whether if this indicates the addition of such an ObjectFlag or just the conditional test. ClassDefaultCheckAddedToTemplateName = 267, - + ComponentGuidDeprecated = 273, /// diff --git a/src/Core/Classes/UObject.cs b/src/Core/Classes/UObject.cs index cbf2ac11..8625943f 100644 --- a/src/Core/Classes/UObject.cs +++ b/src/Core/Classes/UObject.cs @@ -57,6 +57,11 @@ public partial class UObject : object, IAcceptable, IContainsTable, IBinaryData, [CanBeNull] public UObject Outer => Package.GetIndexObject(Table.OuterIndex); + [CanBeNull] + public UObject Archetype => ExportTable != null + ? Package.GetIndexObject(ExportTable.ArchetypeIndex) + : null; + /// /// The object's index represented as a table index. /// diff --git a/src/Core/Classes/UObjectDecompiler.cs b/src/Core/Classes/UObjectDecompiler.cs index e4416cb2..430dd559 100644 --- a/src/Core/Classes/UObjectDecompiler.cs +++ b/src/Core/Classes/UObjectDecompiler.cs @@ -14,14 +14,28 @@ public virtual string Decompile() BeginDeserializing(); } + string output = $"// Reference: {GetReferencePath()}\r\n"; + if (ImportTable != null) { - return $"// Cannot decompile import {Name}"; + return output + $"\r\n{UDecompilingState.Tabs}// Cannot decompile an imported object"; + } + + output += UDecompilingState.Tabs; + output += $"begin object name={Name}"; + // If null then we have a new sub-object (not an override) + if (Archetype == null) + { + Debug.Assert(Class != null); + output += $" class={Class.GetReferencePath()}"; + } + else + { + // Commented out, too noisy but useful. + //output += $" /*archetype={Archetype.GetReferencePath()}*/"; } + output += "\r\n"; - Debug.Assert(Class != null); - string output = $"begin object name={Name} class={Class.Name}" + - "\r\n"; UDecompilingState.AddTabs(1); try { @@ -32,9 +46,8 @@ public virtual string Decompile() UDecompilingState.RemoveTabs(1); } - return $"{output}{UDecompilingState.Tabs}end object" + - $"\r\n{UDecompilingState.Tabs}" + - $"// Reference: {Class.Name}'{GetOuterGroup()}'"; + return $"{output}" + + $"{UDecompilingState.Tabs}end object"; } public virtual string FormatHeader() diff --git a/src/Core/Tables/UExportTableItem.cs b/src/Core/Tables/UExportTableItem.cs index 7cd80bd0..faea694e 100644 --- a/src/Core/Tables/UExportTableItem.cs +++ b/src/Core/Tables/UExportTableItem.cs @@ -12,11 +12,9 @@ namespace UELib /// public sealed class UExportTableItem : UObjectTableItem, IUnrealSerializableClass { - private const int VArchetype = 220; + [Obsolete] public const int VObjectFlagsToULONG = 195; - private const int VSerialSizeConditionless = 249; - #region Serialized Members private int _ClassIndex; @@ -121,15 +119,15 @@ public void Serialize(IUnrealStream stream) stream.Write(_SuperIndex); stream.Write(OuterIndex); stream.Write(ObjectName); - if (stream.Version >= VArchetype) + if (stream.Version >= (uint)PackageObjectLegacyVersion.ArchetypeAddedToExports) { _ArchetypeIndex = stream.ReadInt32(); } - stream.Write(stream.Version >= VObjectFlagsToULONG + stream.Write(stream.Version >= (uint)PackageObjectLegacyVersion.ObjectFlagsSizeChangedToULong ? ObjectFlags : (uint)ObjectFlags); stream.WriteIndex(SerialSize); // Assumes SerialSize has been updated to @Object's buffer size. - if (SerialSize > 0 || stream.Version >= VSerialSizeConditionless) + if (SerialSize > 0 || stream.Version >= (uint)PackageObjectLegacyVersion.SerialSizeConditionRemoved) { // SerialOffset has to be set and written after this object has been serialized. stream.WriteIndex(SerialOffset); // Assumes the same as @SerialSize comment. @@ -155,7 +153,7 @@ public void Deserialize(IUnrealStream stream) } #endif ObjectName = stream.ReadNameReference(); - if (stream.Version >= VArchetype) + if (stream.Version >= (uint)PackageObjectLegacyVersion.ArchetypeAddedToExports) { _ArchetypeIndex = stream.ReadInt32(); } @@ -176,14 +174,14 @@ public void Deserialize(IUnrealStream stream) } #endif ObjectFlags = stream.ReadUInt32(); - if (stream.Version >= VObjectFlagsToULONG) + if (stream.Version >= (uint)PackageObjectLegacyVersion.ObjectFlagsSizeChangedToULong) { ObjectFlags = (ObjectFlags << 32) | stream.ReadUInt32(); } streamSerialSize: SerialSize = stream.ReadIndex(); - if (SerialSize > 0 || stream.Version >= VSerialSizeConditionless) + if (SerialSize > 0 || stream.Version >= (uint)PackageObjectLegacyVersion.SerialSizeConditionRemoved) { #if ROCKETLEAGUE // FIXME: Can't change SerialOffset to 64bit due UE Explorer. From 5ac20221867b168968f6995f264b784c48137afd Mon Sep 17 00:00:00 2001 From: Eliot Date: Thu, 25 Apr 2024 04:25:29 +0200 Subject: [PATCH 05/50] Fixes #36; only inline a subobject once even if it's referenced multiple times. Fix: always inline empty subobjects (such objects have usually been inserted by the compiler) Fix: try inline missing subobjects (e.g. a subobject only referenced within a struct literal). --- src/Core/Classes/UDefaultProperty.cs | 49 +++++++++++++++------- src/Core/Classes/UObjectDecompiler.cs | 59 ++++++++++++++++++++------- src/UDecompilingState.cs | 13 +++++- 3 files changed, 90 insertions(+), 31 deletions(-) diff --git a/src/Core/Classes/UDefaultProperty.cs b/src/Core/Classes/UDefaultProperty.cs index ac460ed4..70e9a1c0 100644 --- a/src/Core/Classes/UDefaultProperty.cs +++ b/src/Core/Classes/UDefaultProperty.cs @@ -686,31 +686,50 @@ private string DeserializeDefaultPropertyValue(PropertyType type, ref Deserializ break; } + Debug.Assert(UDecompilingState.s_inlinedSubObjects != null, "UDecompilingState.s_inlinedSubObjects != null"); + + bool isPendingInline = + UDecompilingState.s_inlinedSubObjects.TryGetValue(constantObject, out bool isInlined); // If the object is part of the current container, then it probably was an inlined declaration. - bool shouldInline = constantObject.Outer == _Container && - (deserializeFlags & DeserializeFlags.WithinStruct) == 0; + bool shouldInline = constantObject.Outer == _Container + && !isPendingInline + && !isInlined; if (shouldInline) { - // Unknown objects are only deserialized on demand. - constantObject.BeginDeserializing(); - propertyValue = constantObject.Decompile() + "\r\n"; - - _TempFlags |= DoNotAppendName; - if ((deserializeFlags & DeserializeFlags.WithinArray) != 0) - { - _TempFlags |= ReplaceNameMarker; - propertyValue += $"{UDecompilingState.Tabs}%ARRAYNAME%={constantObject.Name}"; - } - else + if ((deserializeFlags & DeserializeFlags.WithinStruct) == 0) { + UDecompilingState.s_inlinedSubObjects.Add(constantObject, true); + + // Unknown objects are only deserialized on demand. + constantObject.BeginDeserializing(); + + propertyValue = constantObject.Decompile() + "\r\n"; + + _TempFlags |= DoNotAppendName; + if ((deserializeFlags & DeserializeFlags.WithinArray) != 0) + { + _TempFlags |= ReplaceNameMarker; + propertyValue += $"{UDecompilingState.Tabs}%ARRAYNAME%={constantObject.Name}"; + + break; + } + propertyValue += $"{UDecompilingState.Tabs}{Name}={constantObject.Name}"; + + break; } + // Within a struct, to be inlined later on! + UDecompilingState.s_inlinedSubObjects.Add(constantObject, false); + propertyValue = $"{Name}={constantObject.Name}"; + break; } - // =Class'Package.Group.Name' - propertyValue = PropertyDisplay.FormatLiteral(constantObject); + // Use shorthand for inlined objects. + propertyValue = isInlined + ? constantObject.Name + : PropertyDisplay.FormatLiteral(constantObject); break; } diff --git a/src/Core/Classes/UObjectDecompiler.cs b/src/Core/Classes/UObjectDecompiler.cs index 430dd559..a7fc9245 100644 --- a/src/Core/Classes/UObjectDecompiler.cs +++ b/src/Core/Classes/UObjectDecompiler.cs @@ -1,4 +1,6 @@ -using System.Diagnostics; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; namespace UELib.Core { @@ -58,25 +60,52 @@ public virtual string FormatHeader() protected string DecompileProperties() { if (Properties == null || Properties.Count == 0) - return UDecompilingState.Tabs + "// This object has no properties!\r\n"; + return string.Empty; - var output = string.Empty; - for (var i = 0; i < Properties.Count; ++i) + string output = string.Empty; + + // HACK: Start with a fresh scope. + var oldState = UDecompilingState.s_inlinedSubObjects; + UDecompilingState.s_inlinedSubObjects = new Dictionary(); + + try { - //output += $"{UDecompilingState.Tabs}// {Properties[i].Type}\r\n"; - string propOutput = Properties[i].Decompile(); - - // This is the first element of a static array - if (i + 1 < Properties.Count - && Properties[i + 1].Name == Properties[i].Name - && Properties[i].ArrayIndex <= 0 - && Properties[i + 1].ArrayIndex > 0) + string propertiesText = string.Empty; + for (int i = 0; i < Properties.Count; ++i) { - propOutput = propOutput.Insert(Properties[i].Name.Length, "[0]"); + //output += $"{UDecompilingState.Tabs}// {Properties[i].Type}\r\n"; + string propertyText = Properties[i].Decompile(); + + // This is the first element of a static array + if (i + 1 < Properties.Count + && Properties[i + 1].Name == Properties[i].Name + && Properties[i].ArrayIndex <= 0 + && Properties[i + 1].ArrayIndex > 0) + { + propertyText = propertyText.Insert(Properties[i].Name.Length, "[0]"); + } + + propertiesText += $"{UDecompilingState.Tabs}{propertyText}\r\n"; } - // FORMAT: 'DEBUG[TAB /* 0xPOSITION */] TABS propertyOutput + NEWLINE - output += UDecompilingState.Tabs + propOutput + "\r\n"; + // HACK: Inline sub-objects that we could not inline directly, such as in an array or struct. + // This will still miss sub-objects that have no reference. + var missingSubObjects = UDecompilingState.s_inlinedSubObjects + .Where((k, v) => k.Value == false) + .Select(k => k.Key); + foreach (var obj in missingSubObjects) + { + obj.BeginDeserializing(); + + string objectText = obj.Decompile(); + output += $"{UDecompilingState.Tabs}{objectText}\r\n"; + } + + output += propertiesText; + } + finally + { + UDecompilingState.s_inlinedSubObjects = oldState; } return output; diff --git a/src/UDecompilingState.cs b/src/UDecompilingState.cs index e04384fa..3c391fa8 100644 --- a/src/UDecompilingState.cs +++ b/src/UDecompilingState.cs @@ -1,3 +1,7 @@ +using System.Collections.Generic; +using UELib.Annotations; +using UELib.Core; + namespace UELib { public static class UDecompilingState @@ -6,6 +10,13 @@ public static class UDecompilingState "CA2211:NonConstantFieldsShouldNotBeVisible")] public static string Tabs = string.Empty; + /// + /// Objects that have been inlined (if true) in the current decompiling state. + /// + /// Internal because this is a hack patch to fix an issue where each object is inlined for every reference. + /// + [CanBeNull] internal static Dictionary s_inlinedSubObjects = new Dictionary(); + public static void AddTabs(int count) { for (var i = 0; i < count; ++i) @@ -55,4 +66,4 @@ public static string OffsetLabelName(uint offset) return $"J0x{offset:X2}"; } } -} \ No newline at end of file +} From 88a5b61956a8732a7584f8148fba37d21488276e Mon Sep 17 00:00:00 2001 From: Eliot Date: Thu, 25 Apr 2024 08:24:49 +0200 Subject: [PATCH 06/50] Fix classes deserialization for Rocket League, see https://github.com/UE-Explorer/UE-Explorer/issues/54 --- src/Core/Classes/Props/UArrayProperty.cs | 1 + src/Core/Classes/Props/UInterfaceProperty.cs | 8 +++++ src/Core/Classes/Props/UObjectProperty.cs | 9 ++++++ src/Core/Classes/Props/UProperty.cs | 19 ++++++++++-- src/Core/Classes/UClass.cs | 30 +++++++++++++++++++ src/Core/Classes/UFunction.cs | 31 +++++++++++++++----- src/UnrealStream.cs | 5 +++- 7 files changed, 92 insertions(+), 11 deletions(-) diff --git a/src/Core/Classes/Props/UArrayProperty.cs b/src/Core/Classes/Props/UArrayProperty.cs index 04692a85..6b063161 100644 --- a/src/Core/Classes/Props/UArrayProperty.cs +++ b/src/Core/Classes/Props/UArrayProperty.cs @@ -27,6 +27,7 @@ protected override void Deserialize() base.Deserialize(); InnerProperty = _Buffer.ReadObject(); + Record(nameof(InnerProperty), InnerProperty); } /// diff --git a/src/Core/Classes/Props/UInterfaceProperty.cs b/src/Core/Classes/Props/UInterfaceProperty.cs index 53ea16c8..b87ff452 100644 --- a/src/Core/Classes/Props/UInterfaceProperty.cs +++ b/src/Core/Classes/Props/UInterfaceProperty.cs @@ -30,6 +30,14 @@ protected override void Deserialize() InterfaceClass = _Buffer.ReadObject(); Record(nameof(InterfaceClass), InterfaceClass); +#if ROCKETLEAGUE + if (_Buffer.Package.Build == UnrealPackage.GameBuild.BuildName.RocketLeague && + _Buffer.LicenseeVersion >= 32) + { + var vd0 = _Buffer.ReadNameReference(); + Record(nameof(vd0), vd0); + } +#endif } /// diff --git a/src/Core/Classes/Props/UObjectProperty.cs b/src/Core/Classes/Props/UObjectProperty.cs index 86cbeadf..030fd2f6 100644 --- a/src/Core/Classes/Props/UObjectProperty.cs +++ b/src/Core/Classes/Props/UObjectProperty.cs @@ -28,6 +28,15 @@ protected override void Deserialize() Object = _Buffer.ReadObject(); Record(nameof(Object), Object); +#if ROCKETLEAGUE + if (_Buffer.Package.Build == UnrealPackage.GameBuild.BuildName.RocketLeague && + // version >= 17 for UComponentProperty? + _Buffer.LicenseeVersion >= 32) + { + var vd0 = _Buffer.ReadNameReference(); + Record(nameof(vd0), vd0); + } +#endif } /// diff --git a/src/Core/Classes/Props/UProperty.cs b/src/Core/Classes/Props/UProperty.cs index b416e317..c654122d 100644 --- a/src/Core/Classes/Props/UProperty.cs +++ b/src/Core/Classes/Props/UProperty.cs @@ -163,8 +163,8 @@ protected override void Deserialize() #if THIEF_DS || DEUSEX_IW if (Package.Build == BuildGeneration.Flesh) { - short deusInheritedOrRuntimeInstiantiated = _Buffer.ReadInt16(); - Record(nameof(deusInheritedOrRuntimeInstiantiated), deusInheritedOrRuntimeInstiantiated); + short deusInheritedOrRuntimeInstantiated = _Buffer.ReadInt16(); + Record(nameof(deusInheritedOrRuntimeInstantiated), deusInheritedOrRuntimeInstantiated); short deusUnkInt16 = _Buffer.ReadInt16(); Record(nameof(deusUnkInt16), deusUnkInt16); } @@ -182,6 +182,21 @@ protected override void Deserialize() RepOffset = _Buffer.ReadUShort(); Record(nameof(RepOffset), RepOffset); } +#if ROCKETLEAGUE + // identical to this object's name. + if (_Buffer.Package.Build == UnrealPackage.GameBuild.BuildName.RocketLeague && + _Buffer.LicenseeVersion >= 11) + { + string vb8 = _Buffer.ReadString(); + Record(nameof(vb8), vb8); + + //if (_Buffer.LicenseeVersion == 15) + //{ + // var v68 = _Buffer.ReadNameReference(); + // Record(nameof(v68), v68); + //} + } +#endif #if VENGEANCE if (Package.Build == BuildGeneration.Vengeance) { diff --git a/src/Core/Classes/UClass.cs b/src/Core/Classes/UClass.cs index 54c44bb6..2bf598dc 100644 --- a/src/Core/Classes/UClass.cs +++ b/src/Core/Classes/UClass.cs @@ -163,6 +163,14 @@ protected override void Deserialize() #endif ClassFlags = _Buffer.ReadUInt32(); Record(nameof(ClassFlags), (ClassFlags)ClassFlags); +#if ROCKETLEAGUE + if (_Buffer.Package.Build == UnrealPackage.GameBuild.BuildName.RocketLeague && + _Buffer.LicenseeVersion >= 1) + { + uint v194 = _Buffer.ReadUInt32(); + Record(nameof(v194), v194); + } +#endif #if SPELLBORN if (Package.Build == UnrealPackage.GameBuild.BuildName.Spellborn) { @@ -403,6 +411,20 @@ protected override void Deserialize() _Buffer.ConformRecordPosition(); } +#endif +#if ROCKETLEAGUE + if (_Buffer.Package.Build == UnrealPackage.GameBuild.BuildName.RocketLeague && + _Buffer.LicenseeVersion >= 21) + { + string v298 = _Buffer.ReadString(); + Record(nameof(v298), v298); + + int v2a8 = _Buffer.ReadInt32(); + Record(nameof(v2a8), v2a8); + + _Buffer.Read(out UArray v2b0); + Record(nameof(v2b0), v2b0); + } #endif if (_Buffer.Version >= UnrealPackage.VDLLBIND && _Buffer.UE4Version < 117) { @@ -551,6 +573,14 @@ protected override void Deserialize() { DeserializeProperties(); } +#if ROCKETLEAGUE + if (_Buffer.Package.Build == UnrealPackage.GameBuild.BuildName.RocketLeague) + { + // StateMap; Seems to keep track of all declared states in the class. + _Buffer.Read(out UMap v368); + Record(nameof(v368), v368); + } +#endif } private void DeserializeInterfaces() diff --git a/src/Core/Classes/UFunction.cs b/src/Core/Classes/UFunction.cs index 75cbff07..c48b9a77 100644 --- a/src/Core/Classes/UFunction.cs +++ b/src/Core/Classes/UFunction.cs @@ -93,20 +93,35 @@ protected override void Deserialize() } #if TRANSFORMERS - // TODO: Version? - FunctionFlags = Package.Build == BuildGeneration.HMS - ? _Buffer.ReadUInt64() - : _Buffer.ReadUInt32(); -#else - FunctionFlags = _Buffer.ReadUInt32(); + // FIXME: version + if (_Buffer.Package.Build == BuildGeneration.HMS) + { + FunctionFlags = _Buffer.ReadUInt64(); + Record(nameof(FunctionFlags), (FunctionFlags)FunctionFlags); + + goto skipFunctionFlags; + } #endif + FunctionFlags = _Buffer.ReadUInt32(); Record(nameof(FunctionFlags), (FunctionFlags)FunctionFlags); +#if ROCKETLEAGUE + // Disassembled code shows two calls to ByteOrderSerialize, might be a different variable not sure. + if (_Buffer.Package.Build == UnrealPackage.GameBuild.BuildName.RocketLeague && + _Buffer.LicenseeVersion >= 24) + { + // HO:0x04 = Constructor + uint v134 = _Buffer.ReadUInt32(); + Record(nameof(v134), v134); + + FunctionFlags |= ((ulong)v134 << 32); + } +#endif + skipFunctionFlags: if (HasFunctionFlag(Flags.FunctionFlags.Net)) { RepOffset = _Buffer.ReadUShort(); Record(nameof(RepOffset), RepOffset); } - #if SPELLBORN if (_Buffer.Package.Build == UnrealPackage.GameBuild.BuildName.Spellborn && 133 < _Buffer.Version) @@ -154,7 +169,7 @@ protected override void FindChildren() } } - #endregion +#endregion #region Methods public bool HasFunctionFlag(uint flag) diff --git a/src/UnrealStream.cs b/src/UnrealStream.cs index e0657f2e..71c16871 100644 --- a/src/UnrealStream.cs +++ b/src/UnrealStream.cs @@ -1030,7 +1030,10 @@ public static void Read(this IUnrealStream stream, out UArray array) [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Read(this IUnrealStream stream, out UArray array) => ReadArray(stream, out array); - + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Read(this IUnrealStream stream, out UArray array) => ReadArray(stream, out array); + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Read(this IUnrealStream stream, out UMap map) where TKey : UName From 5b5030e00d3707f9ad701dd05f7dd2705ac8e969 Mon Sep 17 00:00:00 2001 From: Eliot Date: Thu, 25 Apr 2024 08:32:55 +0200 Subject: [PATCH 07/50] Setup a custom tokens map for Rocket League https://github.com/UE-Explorer/UE-Explorer/issues/54 --- src/Branch/UE3/RL/EngineBranch.RL.cs | 161 +++++++++++++++++++++++++++ src/UnrealPackage.cs | 3 +- 2 files changed, 163 insertions(+), 1 deletion(-) create mode 100644 src/Branch/UE3/RL/EngineBranch.RL.cs diff --git a/src/Branch/UE3/RL/EngineBranch.RL.cs b/src/Branch/UE3/RL/EngineBranch.RL.cs new file mode 100644 index 00000000..f032d3f7 --- /dev/null +++ b/src/Branch/UE3/RL/EngineBranch.RL.cs @@ -0,0 +1,161 @@ +using System; +using UELib.Core.Tokens; +using UELib.Tokens; +using static UELib.Core.UStruct.UByteCodeDecompiler; + +namespace UELib.Branch.UE3.RL +{ + public class EngineBranchRL : DefaultEngineBranch + { + [Flags] + public enum FunctionFlagsRL : ulong + { + Constructor = 0x400000000, + } + + public EngineBranchRL(BuildGeneration generation) : base(BuildGeneration.UE3) + { + } + + protected override TokenMap BuildTokenMap(UnrealPackage linker) + { + if (linker.LicenseeVersion < 32) + { + return base.BuildTokenMap(linker); + } + + var tokenMap = new TokenMap((byte)ExprToken.ExtendedNative) + { + { 0x00, typeof(UnresolvedToken) }, + { 0x01, typeof(NothingToken) }, + { 0x02, typeof(BadToken) }, + { 0x03, typeof(BadToken) }, + { 0x04, typeof(UnresolvedToken) }, + { 0x05, typeof(UnresolvedToken) }, + { 0x06, typeof(UnresolvedToken) }, + { 0x07, typeof(BadToken) }, + { 0x08, typeof(UnresolvedToken) }, + { 0x09, typeof(UnresolvedToken) }, + { 0x0A, typeof(FloatConstToken) }, + { 0x0B, typeof(OutVariableToken) }, + { 0x0C, typeof(UnresolvedToken) }, + { 0x0D, typeof(BadToken) }, + { 0x0E, typeof(UnresolvedToken) }, + { 0x0F, typeof(UnresolvedToken) }, + { 0x10, typeof(UnresolvedToken) }, + { 0x11, typeof(UnresolvedToken) }, + { 0x12, typeof(UnresolvedToken) }, + { 0x13, typeof(BadToken) }, + { 0x14, typeof(BadToken) }, + { 0x15, typeof(UnresolvedToken) }, + { 0x16, typeof(UnresolvedToken) }, + { 0x17, typeof(UnresolvedToken) }, + { 0x18, typeof(UnresolvedToken) }, + { 0x19, typeof(UnresolvedToken) }, + { 0x1A, typeof(UnresolvedToken) }, + { 0x1B, typeof(UnresolvedToken) }, + { 0x1C, typeof(UnresolvedToken) }, + { 0x1D, typeof(UnresolvedToken) }, + { 0x1E, typeof(UnresolvedToken) }, + { 0x1F, typeof(UnresolvedToken) }, + { 0x20, typeof(UnresolvedToken) }, + { 0x21, typeof(UnresolvedToken) }, + { 0x22, typeof(UnresolvedToken) }, + { 0x23, typeof(UnresolvedToken) }, + { 0x24, typeof(UnresolvedToken) }, + { 0x25, typeof(UnresolvedToken) }, + { 0x26, typeof(InstanceVariableToken) }, + { 0x27, typeof(UnresolvedToken) }, + { 0x28, typeof(BadToken) }, + { 0x29, typeof(ReturnToken) }, + { 0x2A, typeof(BadToken) }, + { 0x2B, typeof(EndOfScriptToken) }, + { 0x2C, typeof(BadToken) }, + { 0x2D, typeof(UnresolvedToken) }, + { 0x2E, typeof(UnresolvedToken) }, + { 0x2F, typeof(BadToken) }, + { 0x30, typeof(UnresolvedToken) }, + { 0x31, typeof(BadToken) }, + { 0x32, typeof(UnresolvedToken) }, + { 0x33, typeof(BadToken) }, + { 0x34, typeof(UnresolvedToken) }, + { 0x35, typeof(UnresolvedToken) }, + { 0x36, typeof(UnresolvedToken) }, + { 0x37, typeof(StringConstToken) }, + { 0x38, typeof(BadToken) }, + { 0x39, typeof(BadToken) }, + { 0x3A, typeof(UnresolvedToken) }, + { 0x3B, typeof(FinalFunctionToken) }, + { 0x3C, typeof(UnresolvedToken) }, + { 0x3D, typeof(UnresolvedToken) }, + { 0x3E, typeof(StructMemberToken) }, + { 0x3F, typeof(UnresolvedToken) }, + { 0x40, typeof(UnresolvedToken) }, + { 0x41, typeof(PrimitiveCastToken) }, + { 0x42, typeof(UnresolvedToken) }, + { 0x43, typeof(UnresolvedToken) }, + { 0x44, typeof(UnresolvedToken) }, + { 0x45, typeof(UnresolvedToken) }, + { 0x46, typeof(BadToken) }, + { 0x47, typeof(UnresolvedToken) }, + { 0x48, typeof(BadToken) }, + { 0x49, typeof(BadToken) }, + { 0x4A, typeof(UnresolvedToken) }, + { 0x4B, typeof(UnresolvedToken) }, + { 0x4C, typeof(LetBoolToken) }, + { 0x4D, typeof(BadToken) }, + { 0x4E, typeof(BadToken) }, + { 0x4F, typeof(BadToken) }, + { 0x50, typeof(UnresolvedToken) }, + { 0x51, typeof(UnresolvedToken) }, + { 0x52, typeof(BadToken) }, + { 0x53, typeof(BadToken) }, + { 0x54, typeof(NativeParameterToken) }, + { 0x55, typeof(UnicodeStringConstToken) }, + { 0x56, typeof(UnresolvedToken) }, + { 0x57, typeof(BadToken) }, + { 0x58, typeof(UnresolvedToken) }, + { 0x59, typeof(UnresolvedToken) }, + { 0x5A, typeof(BoolVariableToken) }, + { 0x5B, typeof(UnresolvedToken) }, + { 0x5C, typeof(UnresolvedToken) }, + { 0x5D, typeof(UnresolvedToken) }, + { 0x5E, typeof(UnresolvedToken) }, + { 0x5F, typeof(BadToken) }, + { 0x60, typeof(UnresolvedToken) }, + { 0x61, typeof(UnresolvedToken) }, + { 0x62, typeof(UnresolvedToken) }, + { 0x63, typeof(UnresolvedToken) }, + { 0x64, typeof(UnresolvedToken) }, + { 0x65, typeof(UnresolvedToken) }, + { 0x66, typeof(VirtualFunctionToken) }, + { 0x67, typeof(UnresolvedToken) }, + { 0x68, typeof(UnresolvedToken) }, + { 0x69, typeof(TrueToken) }, + { 0x6A, typeof(UnresolvedToken) }, + { 0x6B, typeof(UnresolvedToken) }, + { 0x6C, typeof(LocalVariableToken) }, + { 0x6D, typeof(LetToken) }, + // Prob LetDelegate + { 0x6E, typeof(UnresolvedToken) }, + { 0x6F, typeof(EndFunctionParmsToken) }, + }; + + return tokenMap; + } + + protected override void SetupTokenFactory(UnrealPackage linker) + { + var tokenMap = BuildTokenMap(linker); + SetupTokenFactory( + tokenMap, + TokenFactory.FromPackage(linker.NTLPackage), + 0x70, + 0x80); + } + } + + public class RLIdkToken : Token + { + } +} diff --git a/src/UnrealPackage.cs b/src/UnrealPackage.cs index 4ae516a4..050980a0 100644 --- a/src/UnrealPackage.cs +++ b/src/UnrealPackage.cs @@ -22,6 +22,7 @@ namespace UELib using Core; using Decoding; using Branch.UE2.DVS; + using UELib.Branch.UE3.RL; /// /// Represents the method that will handle the UELib.UnrealPackage.NotifyObjectAdded @@ -588,7 +589,7 @@ public enum BuildName /// 867/009:032 /// Requires third-party decompression and decryption /// - [Build(867, 868, 9u, 32u)] RocketLeague, + [Build(867, 868, 9u, 32u)] [BuildEngineBranch(typeof(EngineBranchRL))] RocketLeague, /// /// Battleborn From fc4eb1e2e6a5c00da1d1518de3c0a8dceec3bede Mon Sep 17 00:00:00 2001 From: Eliot Date: Sat, 27 Apr 2024 23:55:46 +0200 Subject: [PATCH 08/50] Fix package loading for Borderlands https://github.com/UE-Explorer/UE-Explorer/issues/55 --- src/Eliot.UELib.csproj | 2 +- src/UnrealPackage.cs | 7 ++----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/Eliot.UELib.csproj b/src/Eliot.UELib.csproj index 98f084a2..4c5b41ec 100644 --- a/src/Eliot.UELib.csproj +++ b/src/Eliot.UELib.csproj @@ -1,6 +1,6 @@ - DECOMPILE;BINARYMETADATA;Forms;UE1;UE2;UE3;UE4;VENGEANCE;SWAT4;UNREAL2;INFINITYBLADE;BORDERLANDS2;GOW2;APB;SPECIALFORCE2;XIII;SINGULARITY;THIEF_DS;DEUSEX_IW;BORDERLANDS;MIRRORSEDGE;BIOSHOCK;HAWKEN;UT;DISHONORED;REMEMBERME;ALPHAPROTOCOL;VANGUARD;TERA;MKKE;TRANSFORMERS;XCOM2;DD2;DCUO;AA2;SPELLBORN;BATMAN;MOH;ROCKETLEAGUE;DNF;LSGAME;UNDYING;HP;DEVASTATION;SPLINTERCELL;AHIT + DECOMPILE;BINARYMETADATA;Forms;UE1;UE2;UE3;UE4;VENGEANCE;SWAT4;UNREAL2;INFINITYBLADE;BORDERLANDS2;GOW2;APB;SPECIALFORCE2;XIII;SINGULARITY;THIEF_DS;DEUSEX_IW;BORDERLANDS;MIRRORSEDGE;BIOSHOCK;HAWKEN;UT;DISHONORED;REMEMBERME;ALPHAPROTOCOL;VANGUARD;TERA;MKKE;TRANSFORMERS;XCOM2;DD2;DCUO;AA2;SPELLBORN;BATMAN;MOH;ROCKETLEAGUE;DNF;LSGAME;UNDYING;HP;DEVASTATION;BATTLEBORN;SPLINTERCELL;AHIT net48 Library UELib diff --git a/src/UnrealPackage.cs b/src/UnrealPackage.cs index 050980a0..aafafcfb 100644 --- a/src/UnrealPackage.cs +++ b/src/UnrealPackage.cs @@ -1111,9 +1111,6 @@ public void Deserialize(IUnrealStream stream) // An FString converted to an FArray? Concatenating appUserName, appComputerName, appBaseDir, and appTimestamp. stream.ReadArray(out UArray iStack_fc); } -#endif -#if BORDERLANDS - if (stream.Package.Build == GameBuild.BuildName.Borderlands) stream.Skip(4); #endif if (stream.UE4Version >= 384) { @@ -1297,10 +1294,10 @@ public void Deserialize(IUnrealStream stream) } #endif } -#if BORDERLANDS +#if BATTLEBORN if (stream.Package.Build == GameBuild.BuildName.Battleborn) { - // FIXME: Package format is being deserialzied incorrectly and fails here. + // FIXME: Package format is being deserialized incorrectly and fails here. stream.ReadUInt32(); return; } From fa4cff685895b66afb076b3156c69d770c9d4747 Mon Sep 17 00:00:00 2001 From: Eliot Date: Sat, 27 Apr 2024 23:56:06 +0200 Subject: [PATCH 09/50] Fix missing children (functions etc) for Borderlands https://github.com/UE-Explorer/UE-Explorer/issues/55 --- src/Core/Classes/UStruct.cs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/Core/Classes/UStruct.cs b/src/Core/Classes/UStruct.cs index 9cebe08e..9e50d59b 100644 --- a/src/Core/Classes/UStruct.cs +++ b/src/Core/Classes/UStruct.cs @@ -91,15 +91,31 @@ protected override void Deserialize() { goto skipScriptText; } +#endif +#if BORDERLANDS + // Swapped order... + if (Package.Build == UnrealPackage.GameBuild.BuildName.Borderlands) + { + Children = _Buffer.ReadObject(); + Record(nameof(Children), Children); + + ScriptText = _Buffer.ReadObject(); + Record(nameof(ScriptText), ScriptText); + + // FIXME: another 2x32 uints here (IsConsoleCooked) + + goto skipChildren; + } #endif if (!Package.IsConsoleCooked() && _Buffer.UE4Version < 117) { ScriptText = _Buffer.ReadObject(); Record(nameof(ScriptText), ScriptText); } - skipScriptText: + skipScriptText: Children = _Buffer.ReadObject(); Record(nameof(Children), Children); + skipChildren: #if BATMAN if (Package.Build == UnrealPackage.GameBuild.BuildName.Batman4) { From c84ecdf34f3ca06845b0cdedb4537ad56347ea97 Mon Sep 17 00:00:00 2001 From: Eliot Date: Sun, 28 Apr 2024 00:46:18 +0200 Subject: [PATCH 10/50] Fix UClass and UProperty for Borderlands https://github.com/UE-Explorer/UE-Explorer/issues/55 --- src/Core/Classes/Props/UProperty.cs | 12 ++++++++++++ src/Core/Classes/UClass.cs | 11 ++++++++++- src/UnrealPackage.cs | 9 +++++++-- 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/src/Core/Classes/Props/UProperty.cs b/src/Core/Classes/Props/UProperty.cs index c654122d..296e2236 100644 --- a/src/Core/Classes/Props/UProperty.cs +++ b/src/Core/Classes/Props/UProperty.cs @@ -129,6 +129,18 @@ protected override void Deserialize() Record(nameof(PropertyFlags), (PropertyFlagsLO)PropertyFlags); } #endif +#if BORDERLANDS + if (Package.Build == UnrealPackage.GameBuild.BuildName.Borderlands && + _Buffer.LicenseeVersion >= 2) + { + // Disassembled as two ReadObjects, but upon further inspection this HAS to be a FName read instead. + // Always the struct's name for struct properties. + // Always "Messaging" for message like properties. + // Also seen as "Action_SamePropertyName" + var v84 = _Buffer.ReadNameReference(); + Record(nameof(v84), v84); + } +#endif #if XCOM2 if (Package.Build == UnrealPackage.GameBuild.BuildName.XCOM2WotC) { diff --git a/src/Core/Classes/UClass.cs b/src/Core/Classes/UClass.cs index 2bf598dc..a980c031 100644 --- a/src/Core/Classes/UClass.cs +++ b/src/Core/Classes/UClass.cs @@ -330,7 +330,13 @@ protected override void Deserialize() } #endif } - +#if BORDERLANDS + // Back port of version 670? (No version specified). See also above + if (Package.Build == UnrealPackage.GameBuild.BuildName.Borderlands) + { + AutoCollapseCategories = DeserializeGroup("AutoCollapseCategories"); + } +#endif if (_Buffer.Version >= (uint)PackageObjectLegacyVersion.ForceScriptOrderAddedToUClass #if BIOSHOCK // Partially upgraded @@ -386,6 +392,9 @@ protected override void Deserialize() #endif #if TRANSFORMERS && Package.Build != BuildGeneration.HMS +#endif +#if BORDERLANDS + && Package.Build != UnrealPackage.GameBuild.BuildName.Borderlands #endif ) { diff --git a/src/UnrealPackage.cs b/src/UnrealPackage.cs index aafafcfb..c3c407d6 100644 --- a/src/UnrealPackage.cs +++ b/src/UnrealPackage.cs @@ -430,9 +430,14 @@ public enum BuildName MOH, /// - /// 584/058 + /// Borderlands + /// + /// 584/057-058 + /// 594/058 /// - [Build(584, 58)] Borderlands, + [Build(584, 584, 57, 58)] + [Build(594, 58)] + Borderlands, /// /// 584/126 From b9441d741476265a1dddbc44b3a3831ad814dc4c Mon Sep 17 00:00:00 2001 From: Eliot Date: Mon, 29 Apr 2024 10:34:11 +0200 Subject: [PATCH 11/50] Fix extra space before "[reliable] if" when "reliable" is not available. --- src/Core/Classes/UClassDecompiler.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Core/Classes/UClassDecompiler.cs b/src/Core/Classes/UClassDecompiler.cs index 754e770c..b4f6949a 100644 --- a/src/Core/Classes/UClassDecompiler.cs +++ b/src/Core/Classes/UClassDecompiler.cs @@ -487,7 +487,9 @@ public string FormatReplication() string statementType = Package.Version < VReliableDeprecation ? rel ? "reliable" : "unreliable" : string.Empty; - var statementFormat = $"{statementType} if({statementCode})"; + string statementFormat = statementType != string.Empty + ? $"{statementType} if({statementCode})" + : $"if({statementCode}"; output.Append(statementFormat); UDecompilingState.AddTab(); From 139254a976372fc34363a2c51584d1ba9ce1447e Mon Sep 17 00:00:00 2001 From: Eliot Date: Mon, 29 Apr 2024 10:37:14 +0200 Subject: [PATCH 12/50] Fix Borderlands and add support for Borderlands: Game of the Year Enhanced. Fixes https://github.com/UE-Explorer/UE-Explorer/issues/55 --- src/Core/Classes/Props/UProperty.cs | 24 +++++++++++------------- src/Core/Classes/UClass.cs | 26 ++++++++++++-------------- src/Core/Classes/UDefaultProperty.cs | 17 ++++++++++++++--- src/UnrealBuild.cs | 7 +++++++ src/UnrealPackage.cs | 28 +++++++++++++++++++++++++--- 5 files changed, 69 insertions(+), 33 deletions(-) diff --git a/src/Core/Classes/Props/UProperty.cs b/src/Core/Classes/Props/UProperty.cs index 296e2236..2984d185 100644 --- a/src/Core/Classes/Props/UProperty.cs +++ b/src/Core/Classes/Props/UProperty.cs @@ -111,7 +111,7 @@ protected override void Deserialize() ElementSize = (ushort)(ArrayDim >> 16); skipArrayDim: // Just to verify if this is in use at all. - Debug.Assert(ElementSize == 0, $"ElementSize: {ElementSize}"); + //Debug.Assert(ElementSize == 0, $"ElementSize: {ElementSize}"); // 2048 is the max allowed dimension in the UnrealScript compiler, however some licensees have extended this to a much higher size. //Debug.Assert( // (ArrayDim & 0x0000FFFFU) > 0 && (ArrayDim & 0x0000FFFFU) <= 2048, @@ -129,18 +129,6 @@ protected override void Deserialize() Record(nameof(PropertyFlags), (PropertyFlagsLO)PropertyFlags); } #endif -#if BORDERLANDS - if (Package.Build == UnrealPackage.GameBuild.BuildName.Borderlands && - _Buffer.LicenseeVersion >= 2) - { - // Disassembled as two ReadObjects, but upon further inspection this HAS to be a FName read instead. - // Always the struct's name for struct properties. - // Always "Messaging" for message like properties. - // Also seen as "Action_SamePropertyName" - var v84 = _Buffer.ReadNameReference(); - Record(nameof(v84), v84); - } -#endif #if XCOM2 if (Package.Build == UnrealPackage.GameBuild.BuildName.XCOM2WotC) { @@ -181,6 +169,16 @@ protected override void Deserialize() Record(nameof(deusUnkInt16), deusUnkInt16); } #endif +#if BORDERLANDS + if (Package.Build == BuildGeneration.GB && + _Buffer.LicenseeVersion >= 2) + { + var va8 = _Buffer.ReadObject(); + Record(nameof(va8), va8); + var vb0 = _Buffer.ReadObject(); + Record(nameof(vb0), vb0); + } +#endif #if UE4 if (_Buffer.UE4Version > 0) { diff --git a/src/Core/Classes/UClass.cs b/src/Core/Classes/UClass.cs index a980c031..19bcc624 100644 --- a/src/Core/Classes/UClass.cs +++ b/src/Core/Classes/UClass.cs @@ -317,24 +317,22 @@ protected override void Deserialize() #endif } - if (_Buffer.Version > 670) + // FIXME: Wrong version, no version checks found in games that DO have checks for version 600+ + if (_Buffer.Version > 670 +#if BORDERLANDS + || Package.Build == UnrealPackage.GameBuild.BuildName.Borderlands +#endif + ) { AutoCollapseCategories = DeserializeGroup("AutoCollapseCategories"); -#if BATMAN - // Only attested in bm4 with no version check. - if (_Buffer.Package.Build == BuildGeneration.RSS && - _Buffer.Package.Build == UnrealPackage.GameBuild.BuildName.Batman4) - { - IList bm4_v198; - bm4_v198 = DeserializeGroup(nameof(bm4_v198)); - } -#endif } -#if BORDERLANDS - // Back port of version 670? (No version specified). See also above - if (Package.Build == UnrealPackage.GameBuild.BuildName.Borderlands) +#if BATMAN + // Only attested in bm4 with no version check. + if (_Buffer.Package.Build == BuildGeneration.RSS && + _Buffer.Package.Build == UnrealPackage.GameBuild.BuildName.Batman4) { - AutoCollapseCategories = DeserializeGroup("AutoCollapseCategories"); + IList bm4_v198; + bm4_v198 = DeserializeGroup(nameof(bm4_v198)); } #endif if (_Buffer.Version >= (uint)PackageObjectLegacyVersion.ForceScriptOrderAddedToUClass diff --git a/src/Core/Classes/UDefaultProperty.cs b/src/Core/Classes/UDefaultProperty.cs index 70e9a1c0..551d465b 100644 --- a/src/Core/Classes/UDefaultProperty.cs +++ b/src/Core/Classes/UDefaultProperty.cs @@ -502,9 +502,20 @@ private void DeserializeTypeDataUE3() break; case PropertyType.BoolProperty: - BoolValue = _Buffer.Version >= (uint)PackageObjectLegacyVersion.BoolValueToByteForBoolPropertyTag - ? _Buffer.ReadByte() > 0 - : _Buffer.ReadInt32() > 0; + if (_Buffer.Version >= (uint)PackageObjectLegacyVersion.BoolValueToByteForBoolPropertyTag +#if BORDERLANDS + // GOTYE didn't apply this upgrade, but did the EnumName update? ... + && _Buffer.Package.Build != UnrealPackage.GameBuild.BuildName.Borderlands_GOTYE +#endif + ) + { + BoolValue = _Buffer.ReadByte() > 0; + } + else + { + BoolValue = _Buffer.ReadInt32() > 0; + } + Record(nameof(BoolValue), BoolValue); break; diff --git a/src/UnrealBuild.cs b/src/UnrealBuild.cs index cd016367..387b32a1 100644 --- a/src/UnrealBuild.cs +++ b/src/UnrealBuild.cs @@ -82,6 +82,13 @@ public enum BuildGeneration /// HMS, + /// + /// Gearbox Software + /// + /// Modified Unreal Engine 3 for Borderlands + /// + GB, + /// /// Unreal Engine 4 /// diff --git a/src/UnrealPackage.cs b/src/UnrealPackage.cs index c3c407d6..91448c58 100644 --- a/src/UnrealPackage.cs +++ b/src/UnrealPackage.cs @@ -433,12 +433,25 @@ public enum BuildName /// Borderlands /// /// 584/057-058 - /// 594/058 + /// + /// Includes back-ported features from UDK /// - [Build(584, 584, 57, 58)] - [Build(594, 58)] + [Build(584, 584, 57, 58, BuildGeneration.GB)] Borderlands, + /// + /// Borderlands Game of the Year Enhanced + /// + /// 594/058 + /// + /// Includes back-ported features from UDK of at least v813 (NativeClassGroup) + /// Appears to be missing (v623:ExportGuids, v767:TextureAllocations, and v673:FPropertyTag's BoolValue change). + /// Presume at least v832 from Borderlands 2 + /// + [Build(594, 58, BuildGeneration.GB)] + [OverridePackageVersion(832)] + Borderlands_GOTYE, + /// /// 584/126 /// @@ -1133,6 +1146,9 @@ public void Deserialize(IUnrealStream stream) // FIXME: Correct the output version of these games instead. #if BIOSHOCK && stream.Package.Build != GameBuild.BuildName.Bioshock_Infinite +#endif +#if BORDERLANDS + && stream.Package.Build != GameBuild.BuildName.Borderlands_GOTYE #endif ) { @@ -1299,6 +1315,12 @@ public void Deserialize(IUnrealStream stream) } #endif } +#if BORDERLANDS + if (stream.Package.Build == GameBuild.BuildName.Borderlands_GOTYE) + { + return; + } +#endif #if BATTLEBORN if (stream.Package.Build == GameBuild.BuildName.Battleborn) { From 20a56cef2bf9ad342d354c32469164dbf9076bad Mon Sep 17 00:00:00 2001 From: Eliot Date: Mon, 29 Apr 2024 10:39:15 +0200 Subject: [PATCH 13/50] Fix decompilation of option parameter default value expressions for older packages, also fix an issue where it to "ate" one token too many, causing some expressions to be missing in the decompiled output. --- src/ByteCodeDecompiler.cs | 17 ++++++++++------- src/Core/Classes/UFunction.cs | 7 +++++-- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/ByteCodeDecompiler.cs b/src/ByteCodeDecompiler.cs index 0d9c0bd8..a511d553 100644 --- a/src/ByteCodeDecompiler.cs +++ b/src/ByteCodeDecompiler.cs @@ -474,15 +474,15 @@ public string Decompile() break; case DefaultParameterToken _: - { - do { - ++CurrentTokenIndex; - } while (!(CurrentToken is EndParmValueToken)); + do + { + ++CurrentTokenIndex; + } while (!(CurrentToken is EndParmValueToken)); - ++CurrentTokenIndex; // EndParmValueToken - break; - } + ++CurrentTokenIndex; // EndParmValueToken + break; + } default: // Can be true e.g a function with optionals but no default values @@ -490,6 +490,9 @@ public string Decompile() break; } } + + // The decompiler expects to start with a -1 index + --CurrentTokenIndex; } #endif while (CurrentTokenIndex + 1 < DeserializedTokens.Count) diff --git a/src/Core/Classes/UFunction.cs b/src/Core/Classes/UFunction.cs index c48b9a77..01bccf93 100644 --- a/src/Core/Classes/UFunction.cs +++ b/src/Core/Classes/UFunction.cs @@ -201,8 +201,11 @@ public bool HasOptionalParamData() { // FIXME: Deprecate version check, and re-map the function flags using the EngineBranch class approach. return Package.Version > 300 - && ByteCodeManager != null - && HasFunctionFlag(Flags.FunctionFlags.OptionalParameters); + && ByteCodeManager != null + && Params?.Any() == true + // Not available for older packages. + // && HasFunctionFlag(Flags.FunctionFlags.OptionalParameters); + ; } #endregion From 21e38ae38e1ef9748b177bc4b0d826bb789a7137 Mon Sep 17 00:00:00 2001 From: UnDrew Date: Mon, 29 Apr 2024 22:05:51 +0300 Subject: [PATCH 14/50] Fix for UObject recreating the object buffer while one still exists --- src/Core/Classes/UObject.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Core/Classes/UObject.cs b/src/Core/Classes/UObject.cs index cbf2ac11..4fc69f53 100644 --- a/src/Core/Classes/UObject.cs +++ b/src/Core/Classes/UObject.cs @@ -197,7 +197,8 @@ private void InitBuffer() internal void EnsureBuffer() { //Console.WriteLine( "Ensure buffer for {0}", (string)this ); - InitBuffer(); + if (_Buffer == null) + InitBuffer(); } internal void MaybeDisposeBuffer() From ea606244659f8d79a25cfee7f32e46609a61a583 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 May 2024 00:14:02 +0000 Subject: [PATCH 15/50] Bump microsoft/setup-msbuild from 1.3 to 2 Bumps [microsoft/setup-msbuild](https://github.com/microsoft/setup-msbuild) from 1.3 to 2. - [Release notes](https://github.com/microsoft/setup-msbuild/releases) - [Changelog](https://github.com/microsoft/setup-msbuild/blob/main/building-release.md) - [Commits](https://github.com/microsoft/setup-msbuild/compare/v1.3...v2) --- updated-dependencies: - dependency-name: microsoft/setup-msbuild dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index c03412dc..ff428b0a 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -14,7 +14,7 @@ jobs: - uses: actions/checkout@v4 - name: Setup MSBuild - uses: microsoft/setup-msbuild@v1.3 + uses: microsoft/setup-msbuild@v2 - name: Setup NuGet uses: NuGet/setup-nuget@v2 From e98c9c46ea7828dcad83da31f62d90a03e5bf3be Mon Sep 17 00:00:00 2001 From: Eliot Date: Wed, 1 May 2024 14:08:37 +0200 Subject: [PATCH 16/50] Update publish.yml --- .github/workflows/publish.yml | 35 ++++++++++++++++++++++++++++------- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index ff428b0a..ed8d415b 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -9,7 +9,9 @@ on: jobs: build: runs-on: windows-2019 - + permissions: + packages: write + contents: read steps: - uses: actions/checkout@v4 @@ -23,12 +25,31 @@ jobs: run: nuget restore src/Eliot.UELib.csproj - name: Build - run: msbuild src/Eliot.UELib.csproj -t:rebuild -property:Configuration=Remease + run: msbuild src/Eliot.UELib.csproj -t:rebuild -property:Configuration=Release - - name: Publish Eliot.UELib + - name: Publish Eliot.UELib to NuGet + id: nuget uses: alirezanet/publish-nuget@v3.1.0 with: - PROJECT_FILE_PATH: src/Eliot.UELib.csproj - VERSION_FILE_PATH: src/Eliot.UELib.csproj - VERSION_REGEX: \>\$\(VersionPrefix\)[0-9]+\.[0-9]+\.[0-9]+\< - NUGET_KEY: ${{ secrets.NUGET_API_KEY }} + PROJECT_FILE_PATH: src/Eliot.UELib.csproj + VERSION_STATIC: ${{ github.ref_name }} + NUGET_KEY: ${{ secrets.NUGET_API_KEY }} + + - name: Publish Eliot.UELib to GitHub + uses: actions/upload-artifact@v4 + with: + name: Eliot.UELib + path: ${{ steps.nuget.outputs.PACKAGE_PATH }}/${{ steps.nuget.outputs.PACKAGE_NAME }} + + release: + runs-on: ubuntu + permissions: + packages: write + contents: read + steps: + - name: Create a Release + uses: elgohr/Github-Release-Action@v5 + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + title: ${{ github.ref_name }} From 4c358f47054fa20089e0b0a074fddf809a737a15 Mon Sep 17 00:00:00 2001 From: Eliot Date: Wed, 1 May 2024 14:13:15 +0200 Subject: [PATCH 17/50] Update test.yml --- .github/workflows/test.yml | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e535d8e4..bd98916b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -8,22 +8,21 @@ on: jobs: build: - runs-on: ubuntu-latest - env: - working-directory: ./src steps: - uses: actions/checkout@v4 + - name: Setup .NET uses: actions/setup-dotnet@v4 with: - # Legacy versions not supported? :( - # 5.0 will likely not work yet due legacy dependencies... - dotnet-version: 5.0.x + dotnet-version: 8.0.x + - name: Restore dependencies - run: dotnet restore + run: dotnet restore Test/Eliot.UELib.Test.csproj + - name: Build - run: dotnet build --no-restore + run: dotnet build Test/Eliot.UELib.Test.csproj --no-restore + - name: Test - run: dotnet test --no-build --verbosity normal + run: dotnet test Test/Eliot.UELib.Test.csproj --no-build --verbosity normal From 43c2ac1b13532f8c560aa4b073ec0aa16e0f9aaa Mon Sep 17 00:00:00 2001 From: HyenaCoding <62553452+HyenaCoding@users.noreply.github.com> Date: Fri, 3 May 2024 03:32:01 +0100 Subject: [PATCH 18/50] Added Basic Gigantic Support --- src/Eliot.UELib.csproj | 4 ++-- src/UnrealPackage.cs | 40 +++++++++++++++++++++++++++++++--------- src/UnrealTypes.cs | 4 ++++ 3 files changed, 37 insertions(+), 11 deletions(-) diff --git a/src/Eliot.UELib.csproj b/src/Eliot.UELib.csproj index 98f084a2..732365e9 100644 --- a/src/Eliot.UELib.csproj +++ b/src/Eliot.UELib.csproj @@ -1,6 +1,6 @@ - DECOMPILE;BINARYMETADATA;Forms;UE1;UE2;UE3;UE4;VENGEANCE;SWAT4;UNREAL2;INFINITYBLADE;BORDERLANDS2;GOW2;APB;SPECIALFORCE2;XIII;SINGULARITY;THIEF_DS;DEUSEX_IW;BORDERLANDS;MIRRORSEDGE;BIOSHOCK;HAWKEN;UT;DISHONORED;REMEMBERME;ALPHAPROTOCOL;VANGUARD;TERA;MKKE;TRANSFORMERS;XCOM2;DD2;DCUO;AA2;SPELLBORN;BATMAN;MOH;ROCKETLEAGUE;DNF;LSGAME;UNDYING;HP;DEVASTATION;SPLINTERCELL;AHIT + DECOMPILE;BINARYMETADATA;Forms;UE1;UE2;UE3;UE4;VENGEANCE;SWAT4;UNREAL2;INFINITYBLADE;BORDERLANDS2;GOW2;APB;SPECIALFORCE2;XIII;SINGULARITY;THIEF_DS;DEUSEX_IW;BORDERLANDS;MIRRORSEDGE;BIOSHOCK;HAWKEN;UT;DISHONORED;REMEMBERME;ALPHAPROTOCOL;VANGUARD;TERA;MKKE;TRANSFORMERS;XCOM2;DD2;DCUO;AA2;SPELLBORN;BATMAN;MOH;ROCKETLEAGUE;DNF;LSGAME;UNDYING;HP;DEVASTATION;SPLINTERCELL;AHIT;GIGANTIC net48 Library UELib @@ -88,4 +88,4 @@ latest-recommended - \ No newline at end of file + diff --git a/src/UnrealPackage.cs b/src/UnrealPackage.cs index 4ae516a4..e66f5656 100644 --- a/src/UnrealPackage.cs +++ b/src/UnrealPackage.cs @@ -582,6 +582,13 @@ public enum BuildName [Build(863, 32995)] [OverridePackageVersion(863, 227)] [BuildEngineBranch(typeof(EngineBranchRSS))] Batman4, + /// + /// Gigantic: Rampage Edition + /// + /// 867/008:010 + /// + [Build(867, 867, 8u, 10u, BuildGeneration.UE3)] Gigantic, + /// /// Rocket League /// @@ -1034,14 +1041,16 @@ public void Deserialize(IUnrealStream stream) if (stream.Version >= VFolderName) { FolderName = stream.ReadText(); + // Console.WriteLine("Folder Name:" + FolderName); } PackageFlags = stream.ReadFlags32(); Console.WriteLine("Package Flags:" + PackageFlags); -#if HAWKEN - if (stream.Package.Build == GameBuild.BuildName.Hawken && +#if HAWKEN || GIGANTIC + if ((stream.Package.Build == GameBuild.BuildName.Hawken || + stream.Package.Build == GameBuild.BuildName.Gigantic) && stream.LicenseeVersion >= 2) - stream.Skip(4); + stream.Skip(4); #endif NameCount = stream.ReadInt32(); NameOffset = stream.ReadInt32(); @@ -1090,10 +1099,12 @@ public void Deserialize(IUnrealStream stream) if (stream.Version >= VDependsOffset) { DependsOffset = stream.ReadInt32(); + //Console.WriteLine("Depends Offset:" + DependsOffset); } -#if THIEF_DS || DEUSEX_IW +#if THIEF_DS || DEUSEX_IW || GIGANTIC if (stream.Package.Build == GameBuild.BuildName.Thief_DS || - stream.Package.Build == GameBuild.BuildName.DeusEx_IW) + stream.Package.Build == GameBuild.BuildName.DeusEx_IW || + stream.Package.Build == GameBuild.BuildName.Gigantic) { //stream.Skip( 4 ); int unknown = stream.ReadInt32(); @@ -1136,6 +1147,8 @@ public void Deserialize(IUnrealStream stream) ImportExportGuidsOffset = stream.ReadInt32(); ImportGuidsCount = stream.ReadInt32(); ExportGuidsCount = stream.ReadInt32(); + // Console.WriteLine("ImportExportGuidsOffset:" + ImportExportGuidsOffset + " ImportsGuidCount:" + ImportGuidsCount + // + " ExportsGuid Count:" + ExportGuidsCount); } #if TRANSFORMERS if (stream.Package.Build == BuildGeneration.HMS && @@ -1151,9 +1164,13 @@ public void Deserialize(IUnrealStream stream) if (stream.Package.Build == GameBuild.BuildName.DD2 && PackageFlags.HasFlag(PackageFlag.Cooked)) stream.Skip(4); #endif - if (stream.Version >= VThumbnailTableOffset) + if (stream.Version >= VThumbnailTableOffset +#if GIGANTIC + && stream.Package.Build !=GameBuild.BuildName.Gigantic) +#endif { ThumbnailTableOffset = stream.ReadInt32(); + // Console.WriteLine("ThumbnailTableOffset:" + ThumbnailTableOffset); } #if MKKE if (stream.Package.Build == GameBuild.BuildName.MKKE) stream.Skip(4); @@ -1161,7 +1178,7 @@ public void Deserialize(IUnrealStream stream) #if SPELLBORN if (stream.Package.Build == GameBuild.BuildName.Spellborn && stream.Version >= 148) - goto skipGuid; + goto skipGuid; #endif stream.ReadStruct(out Guid); Console.WriteLine("GUID:" + Guid); @@ -1185,6 +1202,11 @@ public void Deserialize(IUnrealStream stream) } #endif stream.ReadArray(out Generations, generationCount); + + foreach (UGenerationTableItem i in Generations) { + int index = Generations.IndexOf(i) + 1; + // Console.WriteLine($"Generation {index}: \n\tExport Count:" + i.ExportCount + " \n\tName Count:" + i.NameCount + " \n\tNetObjectCount:" + i.NetObjectCount); + } #if MKKE } #endif @@ -1214,7 +1236,7 @@ public void Deserialize(IUnrealStream stream) { // The Engine Version this package was created with EngineVersion = stream.ReadInt32(); - Console.WriteLine("\tEngineVersion:" + EngineVersion); + Console.WriteLine("EngineVersion:" + EngineVersion); } #if UE4 if (stream.Package.ContainsEditorData()) @@ -1892,7 +1914,7 @@ private void DeserializeObjects() foreach (var exp in Exports) { if (!(exp.Object is UnknownObject || exp.Object.ShouldDeserializeOnDemand)) - //Console.WriteLine( "Deserializing object:" + exp.ObjectName ); + // Console.WriteLine( "Deserializing object:" + exp.ObjectName ); exp.Object.BeginDeserializing(); OnNotifyPackageEvent(new PackageEventArgs(PackageEventArgs.Id.Object)); diff --git a/src/UnrealTypes.cs b/src/UnrealTypes.cs index a3010704..003b5797 100644 --- a/src/UnrealTypes.cs +++ b/src/UnrealTypes.cs @@ -28,6 +28,10 @@ public enum PropertyType : byte XWeakReferenceProperty, #endif +#if GIGANTIC + JsonRefProperty, +#endif + InterfaceProperty, // >= UE3, displaced FixedArrayProperty, actual value 15, but we don't need the value for UE3 types. ComponentProperty, // >= UE3 From 6cb068e2243b8fd3e93b1598757da05d8e75693f Mon Sep 17 00:00:00 2001 From: HyenaCoding <62553452+HyenaCoding@users.noreply.github.com> Date: Fri, 3 May 2024 03:32:40 +0100 Subject: [PATCH 19/50] Added Basic Gigantic Support --- src/Core/Classes/UDefaultProperty.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/Core/Classes/UDefaultProperty.cs b/src/Core/Classes/UDefaultProperty.cs index de35b040..6f7f2e0b 100644 --- a/src/Core/Classes/UDefaultProperty.cs +++ b/src/Core/Classes/UDefaultProperty.cs @@ -613,7 +613,12 @@ private string DeserializeDefaultPropertyValue(PropertyType type, ref Deserializ Record(nameof(value), value); propertyValue = $"\"{value}\""; break; - } + } +#if GIGANTIC + case PropertyType.JsonRefProperty: + propertyValue = + _Buffer.ReadNameReference() + "@JSONREF@" + _Buffer.ReadObject(); + break; +#endif case PropertyType.IntProperty: { @@ -634,8 +639,9 @@ private string DeserializeDefaultPropertyValue(PropertyType type, ref Deserializ case PropertyType.XWeakReferenceProperty: propertyValue = "/* XWeakReference: (?=" + _Buffer.ReadName() + ",?=" + _Buffer.ReadName() + ",?=" + _Buffer.ReadByte() + ",?=" + _Buffer.ReadName() + ") */"; - break; -#endif + break; +#endif + case PropertyType.FloatProperty: { float value = _Buffer.ReadFloat(); From ef64799aa33fdfe53e2f49bee2400b74fa767ecd Mon Sep 17 00:00:00 2001 From: HyenaCoding <62553452+HyenaCoding@users.noreply.github.com> Date: Fri, 3 May 2024 03:33:12 +0100 Subject: [PATCH 20/50] Added Basic Gigantic Support --- .../Classes/Props/Custom/UJsonRefProperty.cs | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 src/Core/Classes/Props/Custom/UJsonRefProperty.cs diff --git a/src/Core/Classes/Props/Custom/UJsonRefProperty.cs b/src/Core/Classes/Props/Custom/UJsonRefProperty.cs new file mode 100644 index 00000000..63f1a71f --- /dev/null +++ b/src/Core/Classes/Props/Custom/UJsonRefProperty.cs @@ -0,0 +1,24 @@ +#if GIGANTIC +using System; +using UELib.Types; + +namespace UELib.Core +{ + /// + /// JSON Reference Property + /// + [UnrealRegisterClass] + public class UJsonRefProperty : UProperty + { + public UJsonRefProperty() + { + Type = PropertyType.JsonRefProperty; + } + + public override string GetFriendlyType() + { + return "JsonRef"; + } + } +} +#endif From 75bbbbda5db157faece1e97715bccd926ccc2a91 Mon Sep 17 00:00:00 2001 From: HyenaCoding <62553452+HyenaCoding@users.noreply.github.com> Date: Fri, 3 May 2024 03:44:04 +0100 Subject: [PATCH 21/50] Added Gigantic Support --- src/Core/Classes/UDefaultProperty.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Core/Classes/UDefaultProperty.cs b/src/Core/Classes/UDefaultProperty.cs index 6f7f2e0b..3b73c0cf 100644 --- a/src/Core/Classes/UDefaultProperty.cs +++ b/src/Core/Classes/UDefaultProperty.cs @@ -616,7 +616,7 @@ private string DeserializeDefaultPropertyValue(PropertyType type, ref Deserializ } #if GIGANTIC case PropertyType.JsonRefProperty: - propertyValue = + _Buffer.ReadNameReference() + "@JSONREF@" + _Buffer.ReadObject(); + propertyValue = _Buffer.ReadNameReference() + "@JSONREF@" + _Buffer.ReadObject(); break; #endif From 96afc48b55f76d4e8d83dd116c8c1b3573c1d262 Mon Sep 17 00:00:00 2001 From: Eliot Date: Sat, 4 May 2024 07:20:39 +0200 Subject: [PATCH 22/50] Fix for JsonRef formatting. --- .../Classes/Props/Custom/UJsonRefProperty.cs | 26 +- src/Core/Classes/UDefaultProperty.cs | 2444 +++++++++-------- 2 files changed, 1251 insertions(+), 1219 deletions(-) diff --git a/src/Core/Classes/Props/Custom/UJsonRefProperty.cs b/src/Core/Classes/Props/Custom/UJsonRefProperty.cs index 63f1a71f..b9aee5f9 100644 --- a/src/Core/Classes/Props/Custom/UJsonRefProperty.cs +++ b/src/Core/Classes/Props/Custom/UJsonRefProperty.cs @@ -1,23 +1,43 @@ #if GIGANTIC -using System; using UELib.Types; namespace UELib.Core { /// /// JSON Reference Property + /// + /// + /// PropertyType Format: + /// JsonRef<MetaClass> JsonProperty + /// PropertyValue Format: + /// JsonRef<MetaClass>'ObjectReference' + /// /// [UnrealRegisterClass] public class UJsonRefProperty : UProperty - { + { + #region Serialized Members + + public UClass MetaClass; + + #endregion + public UJsonRefProperty() { Type = PropertyType.JsonRefProperty; } + protected override void Deserialize() + { + base.Deserialize(); + + MetaClass = _Buffer.ReadObject(); + Record(nameof(MetaClass), MetaClass); + } + public override string GetFriendlyType() { - return "JsonRef"; + return $"JsonRef<{MetaClass.GetFriendlyType()}>"; } } } diff --git a/src/Core/Classes/UDefaultProperty.cs b/src/Core/Classes/UDefaultProperty.cs index 3b73c0cf..68a77d13 100644 --- a/src/Core/Classes/UDefaultProperty.cs +++ b/src/Core/Classes/UDefaultProperty.cs @@ -1,1219 +1,1231 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Runtime.InteropServices; -using UELib.Annotations; -using UELib.Branch; -using UELib.Types; -using UELib.UnrealScript; - -namespace UELib.Core -{ - /// - /// [Default]Properties values deserializer. - /// - public sealed class UDefaultProperty : IUnrealDecompilable - { - [Flags] - public enum DeserializeFlags : byte - { - None = 0x00, - WithinStruct = 0x01, - WithinArray = 0x02 - } - - private const byte InfoTypeMask = 0x0F; - private const byte InfoSizeMask = 0x70; - private const byte InfoArrayIndexMask = 0x80; - - private const byte DoNotAppendName = 0x01; - private const byte ReplaceNameMarker = 0x02; - - private readonly UObject _Container; - private UStruct _Outer; - private bool _RecordingEnabled = true; - - private UObjectRecordStream _Buffer => (UObjectRecordStream)_Container.Buffer; - - internal long _TagPosition { get; set; } - internal long _PropertyValuePosition { get; set; } - - private byte _TempFlags { get; set; } - - /// - /// The deserialized and decompiled output. - /// Serves as a temporary workaround, don't rely on it. - /// - [PublicAPI] - public string Value { get; private set; } - - public string Decompile() - { - _TempFlags = 0x00; - string value; - _Container.EnsureBuffer(); - try - { - value = DeserializeValue(); - } - catch (Exception ex) - { - Console.Error.WriteLine($"Exception thrown: {ex} in {nameof(Decompile)}"); - value = $"/* ERROR: {ex.GetType()} */"; - } - finally - { - _Container.MaybeDisposeBuffer(); - } - - // Array or Inlined object - if ((_TempFlags & DoNotAppendName) != 0) - // The tag handles the name etc on its own. - { - return value; - } - - string name = Name; - if (Type == PropertyType.DelegateProperty && _Container.Package.Version >= 100) - { - // Re-point the compiler-generated delegate property to the actual delegate function's name - // e.g. __%delegateName%__Delegate -> %delegateName% - if (name.EndsWith("__Delegate")) - { - string normalizedDelegateName = name.Substring(2, name.Length - 12); - name = $"{normalizedDelegateName}"; - } - } - - string expr = name; - return ArrayIndex > 0 - ? $"{expr}[{ArrayIndex}]={value}" - : $"{expr}={value}"; - } - - [CanBeNull] - private T FindProperty(out UStruct outer) - where T : UProperty - { - UProperty property = null; - outer = _Outer ?? (UStruct)_Container.Class; - Debug.Assert(outer != null, nameof(outer) + " != null"); - foreach (var super in outer.EnumerateSuper(outer)) - { - foreach (var field in super - .EnumerateFields() - .OfType()) - { - // FIXME: UName - if (field.Table.ObjectName != Name) - { - continue; - } - - property = field; - outer = super; - break; - } - - if (property == null) - { - continue; - } - - switch (property.Type) - { - case PropertyType.StructProperty: - outer = ((UStructProperty)property).Struct; - break; - - case PropertyType.ArrayProperty: - var arrayField = (UArrayProperty)property; - Debug.Assert(arrayField != null, "arrayField != null"); - var arrayInnerField = arrayField.InnerProperty; - if (arrayInnerField.Type == PropertyType.StructProperty) - { - outer = ((UStructProperty)arrayInnerField).Struct; - } - - break; - } - break; - } - - return (T)property; - } - - [Conditional("BINARYMETADATA")] - private void Record(string varName, object varObject = null) - { - if (_RecordingEnabled) - { - _Container.Record(varName, varObject); - } - } - - #region Serialized Members - - /// - /// Name of the UProperty. - /// - public UName Name; - - /// - /// See PropertyType enum in UnrealFlags.cs - /// - public PropertyType Type; - - [StructLayout(LayoutKind.Explicit, Size = 8)] - private struct PropertyTypeData - { - /// - /// Name of the UStruct. If is StructProperty. - /// - [FieldOffset(0)] public UName StructName; // Formerly known as "ItemName" - - /// - /// Name of the UEnum. If is ByteProperty. - /// - [FieldOffset(0)] public UName EnumName; - - /// - /// Name of the UArray's inner type. If is ArrayProperty. - /// - [FieldOffset(0)] public UName InnerTypeName; - } - - private PropertyTypeData _TypeData; - - [CanBeNull] public UName StructName => _TypeData.StructName; - [CanBeNull] public UName EnumName => _TypeData.EnumName; - [CanBeNull] public UName InnerTypeName => _TypeData.InnerTypeName; - - /// - /// The size in bytes of this tag's value. - /// - public int Size; - - /// - /// The element index of this tag e.g. consider this static array: "var Object Elements[2];" - /// This defines a static array of 2 elements which would have two tags with this field being the index into that - /// array. - /// - public int ArrayIndex = -1; - - /// - /// Value of the UBoolProperty. If Type equals BoolProperty. - /// - public bool? BoolValue; - - #endregion - - #region Constructors - - public UDefaultProperty(UObject owner, UStruct outer = null) - { - _Container = owner; - _Outer = (outer ?? _Container as UStruct) ?? _Container.Outer as UStruct; - } - - private int DeserializePackedSize(byte sizePack) - { - switch (sizePack) - { - case 0x00: - return 1; - - case 0x10: - return 2; - - case 0x20: - return 4; - - case 0x30: - return 12; - - case 0x40: - return 16; - - case 0x50: - return _Buffer.ReadByte(); - - case 0x60: - return _Buffer.ReadInt16(); - - case 0x70: - return _Buffer.ReadInt32(); - - default: - throw new NotImplementedException($"Unknown sizePack {sizePack}"); - } - } - - private int DeserializeTagArrayIndexUE1() - { - int arrayIndex; - byte b = _Buffer.ReadByte(); - if ((b & InfoArrayIndexMask) == 0) - { - arrayIndex = b; - } - else if ((b & 0xC0) == InfoArrayIndexMask) - { - byte c = _Buffer.ReadByte(); - arrayIndex = ((b & 0x7F) << 8) + c; - } - else - { - byte c = _Buffer.ReadByte(); - byte d = _Buffer.ReadByte(); - byte e = _Buffer.ReadByte(); - arrayIndex = ((b & 0x3F) << 24) + (c << 16) + (d << 8) + e; - } - return arrayIndex; - } - - /// True if there are more property tags. - public bool Deserialize() - { - _TagPosition = _Buffer.Position; - if (DeserializeNextTag()) - { - return false; - } - - _PropertyValuePosition = _Buffer.Position; - try - { - Value = DeserializeValue(); - _RecordingEnabled = false; - } - finally - { - // Even if something goes wrong, we can still skip everything and safely deserialize the next property if any! - // Note: In some builds @Size is not serialized - _Buffer.Position = _PropertyValuePosition + Size; - _Buffer.ConformRecordPosition(); - } - - return true; - } - - /// True if this is the last tag. - private bool DeserializeNextTag() - { - if (_Buffer.Version < (uint)PackageObjectLegacyVersion.UE3) - { - return DeserializeTagUE1(); - } -#if BATMAN - if (_Buffer.Package.Build == BuildGeneration.RSS) - { - return DeserializeTagByOffset(); - } -#endif - return DeserializeTagUE3(); - } - - /// True if this is the last tag. - private bool DeserializeTagUE1() - { - Name = _Buffer.ReadNameReference(); - Record(nameof(Name), Name); - if (Name.IsNone()) - { - return true; - } - - byte info = _Buffer.ReadByte(); - Record(nameof(info), info); - - Type = (PropertyType)(byte)(info & InfoTypeMask); - switch (Type) - { - case PropertyType.StructProperty: - _Buffer.Read(out _TypeData.StructName); - Record(nameof(_TypeData.StructName), _TypeData.StructName); - break; - - case PropertyType.ArrayProperty: - { -#if DNF - if (_Buffer.Package.Build == UnrealPackage.GameBuild.BuildName.DNF && - _Buffer.Version >= 124) - { - _Buffer.Read(out _TypeData.InnerTypeName); - Record(nameof(_TypeData.InnerTypeName), _TypeData.InnerTypeName); - } -#endif - break; - } - } - - Size = DeserializePackedSize((byte)(info & InfoSizeMask)); - Record(nameof(Size), Size); - - // TypeData - switch (Type) - { - case PropertyType.BoolProperty: - BoolValue = (info & InfoArrayIndexMask) != 0; - break; - - default: - if ((info & InfoArrayIndexMask) != 0) - { - ArrayIndex = DeserializeTagArrayIndexUE1(); - Record(nameof(ArrayIndex), ArrayIndex); - } - - break; - } - - return false; - } - - /// True if this is the last tag. - private bool DeserializeTagUE3() - { - Name = _Buffer.ReadNameReference(); - Record(nameof(Name), Name); - if (Name.IsNone()) - { - return true; - } - - string typeName = _Buffer.ReadName(); - Record(nameof(typeName), typeName); - Type = (PropertyType)Enum.Parse(typeof(PropertyType), typeName); - - Size = _Buffer.ReadInt32(); - Record(nameof(Size), Size); - - ArrayIndex = _Buffer.ReadInt32(); - Record(nameof(ArrayIndex), ArrayIndex); - - DeserializeTypeDataUE3(); - return false; - } -#if BATMAN - /// True if this is the last tag. - private bool DeserializeTagByOffset() - { - Type = (PropertyType)_Buffer.ReadInt16(); - Record(nameof(Type), Type.ToString()); - if (Type == PropertyType.None) - { - return true; - } - - if (_Buffer.Package.Build != UnrealPackage.GameBuild.BuildName.Batman3MP) - { - ushort offset = _Buffer.ReadUInt16(); - Record(nameof(offset), offset); - - // TODO: Incomplete, PropertyTypes' have shifted. - if ((int)Type == 11) - { - Type = PropertyType.Vector; - } - - // This may actually be determined by the property's flags, but we don't calculate the offset of properties :/ - // TODO: Incomplete - if (Type == PropertyType.StrProperty || - Type == PropertyType.NameProperty || - Type == PropertyType.IntProperty || - Type == PropertyType.FloatProperty || - Type == PropertyType.StructProperty || - Type == PropertyType.Vector || - Type == PropertyType.Rotator || - (Type == PropertyType.BoolProperty && - _Buffer.Package.Build == UnrealPackage.GameBuild.BuildName.Batman4)) - { - switch (Type) - { - case PropertyType.Vector: - case PropertyType.Rotator: - Size = 12; - break; - - case PropertyType.IntProperty: - case PropertyType.FloatProperty: - case PropertyType.StructProperty: - //case PropertyType.ObjectProperty: - //case PropertyType.InterfaceProperty: - //case PropertyType.ComponentProperty: - //case PropertyType.ClassProperty: - Size = 4; - break; - - case PropertyType.NameProperty: - Size = 8; - break; - - case PropertyType.BoolProperty: - Size = sizeof(byte); - break; - } - - Name = new UName($"self[0x{offset:X3}]"); - DeserializeTypeDataUE3(); - return false; - } - } - - Name = _Buffer.ReadNameReference(); - Record(nameof(Name), Name); - - Size = _Buffer.ReadInt32(); - Record(nameof(Size), Size); - - ArrayIndex = _Buffer.ReadInt32(); - Record(nameof(ArrayIndex), ArrayIndex); - - DeserializeTypeDataUE3(); - return false; - } -#endif - private void DeserializeTypeDataUE3() - { - switch (Type) - { - case PropertyType.StructProperty: - _Buffer.Read(out _TypeData.StructName); - Record(nameof(_TypeData.StructName), _TypeData.StructName); -#if UE4 - if (_Buffer.UE4Version >= 441) - { - _Buffer.Skip(16); - _Buffer.ConformRecordPosition(); - } -#endif - break; - - case PropertyType.ByteProperty: - if (_Buffer.Version >= (uint)PackageObjectLegacyVersion.EnumNameAddedToBytePropertyTag) - { - _Buffer.Read(out _TypeData.EnumName); - Record(nameof(_TypeData.EnumName), _TypeData.EnumName); - } - - break; - - case PropertyType.BoolProperty: - BoolValue = _Buffer.Version >= (uint)PackageObjectLegacyVersion.BoolValueToByteForBoolPropertyTag - ? _Buffer.ReadByte() > 0 - : _Buffer.ReadInt32() > 0; - Record(nameof(BoolValue), BoolValue); - break; - - case PropertyType.ArrayProperty: -#if UE4 - // FIXME: UE4 version - if (_Buffer.UE4Version > 220) - { - _Buffer.Read(out _TypeData.InnerTypeName); - Record(nameof(_TypeData.InnerTypeName), _TypeData.InnerTypeName); - } -#endif - break; - } - } - - /// - /// Deserialize the value of this UPropertyTag instance. - /// Note: - /// Only call after the whole package has been deserialized! - /// - /// The deserialized value if any. - [PublicAPI] - public string DeserializeValue(DeserializeFlags deserializeFlags = DeserializeFlags.None) - { - if (_Buffer == null) - { - _Container.EnsureBuffer(); - if (_Buffer == null) - { - throw new DeserializationException("_Buffer is not initialized!"); - } - } - - _Buffer.Seek(_PropertyValuePosition, SeekOrigin.Begin); - return TryDeserializeDefaultPropertyValue(Type, ref deserializeFlags); - } - - private string TryDeserializeDefaultPropertyValue(PropertyType type, ref DeserializeFlags deserializeFlags) - { - try - { - return DeserializeDefaultPropertyValue(type, ref deserializeFlags); - } - catch (EndOfStreamException e) - { - // Abort decompilation - throw; - } - catch (Exception ex) - { - Console.Error.WriteLine($"\r\n> PropertyTag value deserialization error for {_Container.GetPath()}.{Name}" + - $"\r\n Exception: {ex}"); - - return $"/* ERROR: {ex.GetType()} */"; - } - } - - private void AssertFastSerialize(IUnrealArchive archive) - { - Debug.Assert(archive.Version >= (uint)PackageObjectLegacyVersion.FastSerializeStructs); - } - - /// - /// Deserialize a default property value of a specified type. - /// - /// Kind of type to try deserialize. - /// The deserialized value if any. - private string DeserializeDefaultPropertyValue(PropertyType type, ref DeserializeFlags deserializeFlags) - { - var orgOuter = _Outer; - var propertyValue = string.Empty; - - // Deserialize Value - switch (type) - { - case PropertyType.BoolProperty: - { - bool value; - if (Size == 0) - { - Debug.Assert(BoolValue != null, nameof(BoolValue) + " != null"); - value = BoolValue.Value; - } - else - { - value = _Buffer.ReadByte() > 0; - } - - Record(nameof(value), value); - propertyValue = value ? "true" : "false"; - break; - } - - case PropertyType.StrProperty: - { - string value = _Buffer.ReadText(); - Record(nameof(value), value); - propertyValue = PropertyDisplay.FormatLiteral(value); - break; - } - - case PropertyType.NameProperty: - { - var value = _Buffer.ReadNameReference(); - Record(nameof(value), value); - propertyValue = $"\"{value}\""; - break; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.Contracts; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using UELib.Annotations; +using UELib.Branch; +using UELib.Types; +using UELib.UnrealScript; + +namespace UELib.Core +{ + /// + /// [Default]Properties values deserializer. + /// + public sealed class UDefaultProperty : IUnrealDecompilable + { + [Flags] + public enum DeserializeFlags : byte + { + None = 0x00, + WithinStruct = 0x01, + WithinArray = 0x02 + } + + private const byte InfoTypeMask = 0x0F; + private const byte InfoSizeMask = 0x70; + private const byte InfoArrayIndexMask = 0x80; + + private const byte DoNotAppendName = 0x01; + private const byte ReplaceNameMarker = 0x02; + + private readonly UObject _Container; + private UStruct _Outer; + private bool _RecordingEnabled = true; + + private UObjectRecordStream _Buffer => (UObjectRecordStream)_Container.Buffer; + + internal long _TagPosition { get; set; } + internal long _PropertyValuePosition { get; set; } + + private byte _TempFlags { get; set; } + + /// + /// The deserialized and decompiled output. + /// Serves as a temporary workaround, don't rely on it. + /// + [PublicAPI] + public string Value { get; private set; } + + public string Decompile() + { + _TempFlags = 0x00; + string value; + _Container.EnsureBuffer(); + try + { + value = DeserializeValue(); + } + catch (Exception ex) + { + Console.Error.WriteLine($"Exception thrown: {ex} in {nameof(Decompile)}"); + value = $"/* ERROR: {ex.GetType()} */"; + } + finally + { + _Container.MaybeDisposeBuffer(); + } + + // Array or Inlined object + if ((_TempFlags & DoNotAppendName) != 0) + // The tag handles the name etc on its own. + { + return value; + } + + string name = Name; + if (Type == PropertyType.DelegateProperty && _Container.Package.Version >= 100) + { + // Re-point the compiler-generated delegate property to the actual delegate function's name + // e.g. __%delegateName%__Delegate -> %delegateName% + if (name.EndsWith("__Delegate")) + { + string normalizedDelegateName = name.Substring(2, name.Length - 12); + name = $"{normalizedDelegateName}"; } -#if GIGANTIC - case PropertyType.JsonRefProperty: - propertyValue = _Buffer.ReadNameReference() + "@JSONREF@" + _Buffer.ReadObject(); - break; -#endif - - case PropertyType.IntProperty: - { - int value = _Buffer.ReadInt32(); - Record(nameof(value), value); - propertyValue = PropertyDisplay.FormatLiteral(value); - break; - } -#if BIOSHOCK - case PropertyType.QwordProperty: - { - long value = _Buffer.ReadInt64(); - Record(nameof(value), value); - propertyValue = PropertyDisplay.FormatLiteral(value); - break; - } - - case PropertyType.XWeakReferenceProperty: - propertyValue = "/* XWeakReference: (?=" + _Buffer.ReadName() + ",?=" + _Buffer.ReadName() + - ",?=" + _Buffer.ReadByte() + ",?=" + _Buffer.ReadName() + ") */"; + } + + string expr = name; + return ArrayIndex > 0 + ? $"{expr}[{ArrayIndex}]={value}" + : $"{expr}={value}"; + } + + [CanBeNull] + private T FindProperty(out UStruct outer) + where T : UProperty + { + UProperty property = null; + outer = _Outer ?? (UStruct)_Container.Class; + Debug.Assert(outer != null, nameof(outer) + " != null"); + foreach (var super in outer.EnumerateSuper(outer)) + { + foreach (var field in super + .EnumerateFields() + .OfType()) + { + // FIXME: UName + if (field.Table.ObjectName != Name) + { + continue; + } + + property = field; + outer = super; + break; + } + + if (property == null) + { + continue; + } + + switch (property.Type) + { + case PropertyType.StructProperty: + outer = ((UStructProperty)property).Struct; + break; + + case PropertyType.ArrayProperty: + var arrayField = (UArrayProperty)property; + Debug.Assert(arrayField != null, "arrayField != null"); + var arrayInnerField = arrayField.InnerProperty; + if (arrayInnerField.Type == PropertyType.StructProperty) + { + outer = ((UStructProperty)arrayInnerField).Struct; + } + + break; + } + break; + } + + return (T)property; + } + + [Conditional("BINARYMETADATA")] + private void Record(string varName, object varObject = null) + { + if (_RecordingEnabled) + { + _Container.Record(varName, varObject); + } + } + + #region Serialized Members + + /// + /// Name of the UProperty. + /// + public UName Name; + + /// + /// See PropertyType enum in UnrealFlags.cs + /// + public PropertyType Type; + + [StructLayout(LayoutKind.Explicit, Size = 8)] + private struct PropertyTypeData + { + /// + /// Name of the UStruct. If is StructProperty. + /// + [FieldOffset(0)] public UName StructName; // Formerly known as "ItemName" + + /// + /// Name of the UEnum. If is ByteProperty. + /// + [FieldOffset(0)] public UName EnumName; + + /// + /// Name of the UArray's inner type. If is ArrayProperty. + /// + [FieldOffset(0)] public UName InnerTypeName; + } + + private PropertyTypeData _TypeData; + + [CanBeNull] public UName StructName => _TypeData.StructName; + [CanBeNull] public UName EnumName => _TypeData.EnumName; + [CanBeNull] public UName InnerTypeName => _TypeData.InnerTypeName; + + /// + /// The size in bytes of this tag's value. + /// + public int Size; + + /// + /// The element index of this tag e.g. consider this static array: "var Object Elements[2];" + /// This defines a static array of 2 elements which would have two tags with this field being the index into that + /// array. + /// + public int ArrayIndex = -1; + + /// + /// Value of the UBoolProperty. If Type equals BoolProperty. + /// + public bool? BoolValue; + + #endregion + + #region Constructors + + public UDefaultProperty(UObject owner, UStruct outer = null) + { + _Container = owner; + _Outer = (outer ?? _Container as UStruct) ?? _Container.Outer as UStruct; + } + + private int DeserializePackedSize(byte sizePack) + { + switch (sizePack) + { + case 0x00: + return 1; + + case 0x10: + return 2; + + case 0x20: + return 4; + + case 0x30: + return 12; + + case 0x40: + return 16; + + case 0x50: + return _Buffer.ReadByte(); + + case 0x60: + return _Buffer.ReadInt16(); + + case 0x70: + return _Buffer.ReadInt32(); + + default: + throw new NotImplementedException($"Unknown sizePack {sizePack}"); + } + } + + private int DeserializeTagArrayIndexUE1() + { + int arrayIndex; + byte b = _Buffer.ReadByte(); + if ((b & InfoArrayIndexMask) == 0) + { + arrayIndex = b; + } + else if ((b & 0xC0) == InfoArrayIndexMask) + { + byte c = _Buffer.ReadByte(); + arrayIndex = ((b & 0x7F) << 8) + c; + } + else + { + byte c = _Buffer.ReadByte(); + byte d = _Buffer.ReadByte(); + byte e = _Buffer.ReadByte(); + arrayIndex = ((b & 0x3F) << 24) + (c << 16) + (d << 8) + e; + } + return arrayIndex; + } + + /// True if there are more property tags. + public bool Deserialize() + { + _TagPosition = _Buffer.Position; + if (DeserializeNextTag()) + { + return false; + } + + _PropertyValuePosition = _Buffer.Position; + try + { + Value = DeserializeValue(); + _RecordingEnabled = false; + } + finally + { + // Even if something goes wrong, we can still skip everything and safely deserialize the next property if any! + // Note: In some builds @Size is not serialized + _Buffer.Position = _PropertyValuePosition + Size; + _Buffer.ConformRecordPosition(); + } + + return true; + } + + /// True if this is the last tag. + private bool DeserializeNextTag() + { + if (_Buffer.Version < (uint)PackageObjectLegacyVersion.UE3) + { + return DeserializeTagUE1(); + } +#if BATMAN + if (_Buffer.Package.Build == BuildGeneration.RSS) + { + return DeserializeTagByOffset(); + } +#endif + return DeserializeTagUE3(); + } + + /// True if this is the last tag. + private bool DeserializeTagUE1() + { + Name = _Buffer.ReadNameReference(); + Record(nameof(Name), Name); + if (Name.IsNone()) + { + return true; + } + + byte info = _Buffer.ReadByte(); + Record(nameof(info), info); + + Type = (PropertyType)(byte)(info & InfoTypeMask); + switch (Type) + { + case PropertyType.StructProperty: + _Buffer.Read(out _TypeData.StructName); + Record(nameof(_TypeData.StructName), _TypeData.StructName); + break; + + case PropertyType.ArrayProperty: + { +#if DNF + if (_Buffer.Package.Build == UnrealPackage.GameBuild.BuildName.DNF && + _Buffer.Version >= 124) + { + _Buffer.Read(out _TypeData.InnerTypeName); + Record(nameof(_TypeData.InnerTypeName), _TypeData.InnerTypeName); + } +#endif + break; + } + } + + Size = DeserializePackedSize((byte)(info & InfoSizeMask)); + Record(nameof(Size), Size); + + // TypeData + switch (Type) + { + case PropertyType.BoolProperty: + BoolValue = (info & InfoArrayIndexMask) != 0; + break; + + default: + if ((info & InfoArrayIndexMask) != 0) + { + ArrayIndex = DeserializeTagArrayIndexUE1(); + Record(nameof(ArrayIndex), ArrayIndex); + } + + break; + } + + return false; + } + + /// True if this is the last tag. + private bool DeserializeTagUE3() + { + Name = _Buffer.ReadNameReference(); + Record(nameof(Name), Name); + if (Name.IsNone()) + { + return true; + } + + string typeName = _Buffer.ReadName(); + Record(nameof(typeName), typeName); + Type = (PropertyType)Enum.Parse(typeof(PropertyType), typeName); + + Size = _Buffer.ReadInt32(); + Record(nameof(Size), Size); + + ArrayIndex = _Buffer.ReadInt32(); + Record(nameof(ArrayIndex), ArrayIndex); + + DeserializeTypeDataUE3(); + return false; + } +#if BATMAN + /// True if this is the last tag. + private bool DeserializeTagByOffset() + { + Type = (PropertyType)_Buffer.ReadInt16(); + Record(nameof(Type), Type.ToString()); + if (Type == PropertyType.None) + { + return true; + } + + if (_Buffer.Package.Build != UnrealPackage.GameBuild.BuildName.Batman3MP) + { + ushort offset = _Buffer.ReadUInt16(); + Record(nameof(offset), offset); + + // TODO: Incomplete, PropertyTypes' have shifted. + if ((int)Type == 11) + { + Type = PropertyType.Vector; + } + + // This may actually be determined by the property's flags, but we don't calculate the offset of properties :/ + // TODO: Incomplete + if (Type == PropertyType.StrProperty || + Type == PropertyType.NameProperty || + Type == PropertyType.IntProperty || + Type == PropertyType.FloatProperty || + Type == PropertyType.StructProperty || + Type == PropertyType.Vector || + Type == PropertyType.Rotator || + (Type == PropertyType.BoolProperty && + _Buffer.Package.Build == UnrealPackage.GameBuild.BuildName.Batman4)) + { + switch (Type) + { + case PropertyType.Vector: + case PropertyType.Rotator: + Size = 12; + break; + + case PropertyType.IntProperty: + case PropertyType.FloatProperty: + case PropertyType.StructProperty: + //case PropertyType.ObjectProperty: + //case PropertyType.InterfaceProperty: + //case PropertyType.ComponentProperty: + //case PropertyType.ClassProperty: + Size = 4; + break; + + case PropertyType.NameProperty: + Size = 8; + break; + + case PropertyType.BoolProperty: + Size = sizeof(byte); + break; + } + + Name = new UName($"self[0x{offset:X3}]"); + DeserializeTypeDataUE3(); + return false; + } + } + + Name = _Buffer.ReadNameReference(); + Record(nameof(Name), Name); + + Size = _Buffer.ReadInt32(); + Record(nameof(Size), Size); + + ArrayIndex = _Buffer.ReadInt32(); + Record(nameof(ArrayIndex), ArrayIndex); + + DeserializeTypeDataUE3(); + return false; + } +#endif + private void DeserializeTypeDataUE3() + { + switch (Type) + { + case PropertyType.StructProperty: + _Buffer.Read(out _TypeData.StructName); + Record(nameof(_TypeData.StructName), _TypeData.StructName); +#if UE4 + if (_Buffer.UE4Version >= 441) + { + _Buffer.Skip(16); + _Buffer.ConformRecordPosition(); + } +#endif + break; + + case PropertyType.ByteProperty: + if (_Buffer.Version >= (uint)PackageObjectLegacyVersion.EnumNameAddedToBytePropertyTag) + { + _Buffer.Read(out _TypeData.EnumName); + Record(nameof(_TypeData.EnumName), _TypeData.EnumName); + } + + break; + + case PropertyType.BoolProperty: + BoolValue = _Buffer.Version >= (uint)PackageObjectLegacyVersion.BoolValueToByteForBoolPropertyTag + ? _Buffer.ReadByte() > 0 + : _Buffer.ReadInt32() > 0; + Record(nameof(BoolValue), BoolValue); + break; + + case PropertyType.ArrayProperty: +#if UE4 + // FIXME: UE4 version + if (_Buffer.UE4Version > 220) + { + _Buffer.Read(out _TypeData.InnerTypeName); + Record(nameof(_TypeData.InnerTypeName), _TypeData.InnerTypeName); + } +#endif + break; + } + } + + /// + /// Deserialize the value of this UPropertyTag instance. + /// Note: + /// Only call after the whole package has been deserialized! + /// + /// The deserialized value if any. + [PublicAPI] + public string DeserializeValue(DeserializeFlags deserializeFlags = DeserializeFlags.None) + { + if (_Buffer == null) + { + _Container.EnsureBuffer(); + if (_Buffer == null) + { + throw new DeserializationException("_Buffer is not initialized!"); + } + } + + _Buffer.Seek(_PropertyValuePosition, SeekOrigin.Begin); + return TryDeserializeDefaultPropertyValue(Type, ref deserializeFlags); + } + + private string TryDeserializeDefaultPropertyValue(PropertyType type, ref DeserializeFlags deserializeFlags) + { + try + { + return DeserializeDefaultPropertyValue(type, ref deserializeFlags); + } + catch (EndOfStreamException e) + { + // Abort decompilation + throw; + } + catch (Exception ex) + { + Console.Error.WriteLine($"\r\n> PropertyTag value deserialization error for {_Container.GetPath()}.{Name}" + + $"\r\n Exception: {ex}"); + + return $"/* ERROR: {ex.GetType()} */"; + } + } + + private void AssertFastSerialize(IUnrealArchive archive) + { + Debug.Assert(archive.Version >= (uint)PackageObjectLegacyVersion.FastSerializeStructs); + } + + /// + /// Deserialize a default property value of a specified type. + /// + /// Kind of type to try deserialize. + /// The deserialized value if any. + private string DeserializeDefaultPropertyValue(PropertyType type, ref DeserializeFlags deserializeFlags) + { + var orgOuter = _Outer; + var propertyValue = string.Empty; + + // Deserialize Value + switch (type) + { + case PropertyType.BoolProperty: + { + bool value; + if (Size == 0) + { + Debug.Assert(BoolValue != null, nameof(BoolValue) + " != null"); + value = BoolValue.Value; + } + else + { + value = _Buffer.ReadByte() > 0; + } + + Record(nameof(value), value); + propertyValue = value ? "true" : "false"; + break; + } + + case PropertyType.StrProperty: + { + string value = _Buffer.ReadText(); + Record(nameof(value), value); + propertyValue = PropertyDisplay.FormatLiteral(value); + break; + } + + case PropertyType.NameProperty: + { + var value = _Buffer.ReadNameReference(); + Record(nameof(value), value); + propertyValue = $"\"{value}\""; + break; + } +#if GIGANTIC + case PropertyType.JsonRefProperty: + { + var jsonObjectName = _Buffer.ReadNameReference(); + var jsonObject = _Buffer.ReadObject(); + + if (jsonObject == null) + { + propertyValue = "none"; + break; + } + + Contract.Assert(jsonObject.Class != null); + propertyValue = $"JsonRef<{jsonObject.Class?.GetFriendlyType()}>'{jsonObjectName}'"; + break; + } +#endif + case PropertyType.IntProperty: + { + int value = _Buffer.ReadInt32(); + Record(nameof(value), value); + propertyValue = PropertyDisplay.FormatLiteral(value); + break; + } +#if BIOSHOCK + case PropertyType.QwordProperty: + { + long value = _Buffer.ReadInt64(); + Record(nameof(value), value); + propertyValue = PropertyDisplay.FormatLiteral(value); + break; + } + + case PropertyType.XWeakReferenceProperty: + propertyValue = "/* XWeakReference: (?=" + _Buffer.ReadName() + ",?=" + _Buffer.ReadName() + + ",?=" + _Buffer.ReadByte() + ",?=" + _Buffer.ReadName() + ") */"; break; #endif - - case PropertyType.FloatProperty: - { - float value = _Buffer.ReadFloat(); - Record(nameof(value), value); - propertyValue = PropertyDisplay.FormatLiteral(value); - break; - } - - case PropertyType.ByteProperty: - { - if (_Buffer.Version >= (uint)PackageObjectLegacyVersion.EnumTagNameAddedToBytePropertyTag && - Size == 8) - { - string enumTagName = _Buffer.ReadName(); - Record(nameof(enumTagName), enumTagName); - propertyValue = _Buffer.Version >= - (uint)PackageObjectLegacyVersion.EnumNameAddedToBytePropertyTag - ? $"{_TypeData.EnumName}.{enumTagName}" - : enumTagName; - } - else - { - byte value = _Buffer.ReadByte(); - Record(nameof(value), value); - propertyValue = PropertyDisplay.FormatLiteral(value); - } - - break; - } - - case PropertyType.InterfaceProperty: - { - var interfaceClass = _Buffer.ReadObject(); - Record(nameof(interfaceClass), interfaceClass); - propertyValue = PropertyDisplay.FormatLiteral(interfaceClass); - break; - } - - case PropertyType.ComponentProperty: - case PropertyType.ObjectProperty: - { - var constantObject = _Buffer.ReadObject(); - Record(nameof(constantObject), constantObject); - if (constantObject != null) - { - var inline = false; - // If true, object is an archetype or subobject. - if (constantObject.Outer == _Container && - (deserializeFlags & DeserializeFlags.WithinStruct) == 0) - { - // Unknown objects are only deserialized on demand. - constantObject.BeginDeserializing(); - if (constantObject.Properties != null && constantObject.Properties.Count > 0) - { - inline = true; - propertyValue = constantObject.Decompile() + "\r\n" + UDecompilingState.Tabs; - - _TempFlags |= DoNotAppendName; - if ((deserializeFlags & DeserializeFlags.WithinArray) != 0) - { - _TempFlags |= ReplaceNameMarker; - propertyValue += $"%ARRAYNAME%={constantObject.Name}"; - } - else - { - propertyValue += $"{Name}={constantObject.Name}"; - } - } - } - - if (!inline) - // =CLASS'Package.Group(s)+.Name' - { - propertyValue = PropertyDisplay.FormatLiteral(constantObject); - } - } - else - { - // =none - propertyValue = "none"; - } - - break; - } - - case PropertyType.ClassProperty: - { - var classObject = _Buffer.ReadObject(); - Record(nameof(classObject), classObject); - propertyValue = PropertyDisplay.FormatLiteral(classObject); - break; - } - - // Old StringProperty with a fixed size and null termination. - case PropertyType.StringProperty when _Buffer.Version < 100: - { - string str = _Buffer.ReadAnsiNullString(); - propertyValue = str; - break; - } - - case PropertyType.DelegateProperty when _Buffer.Version >= 100: - { - // Can by any object, usually a class. - var functionOwner = _Buffer.ReadObject(); - Record(nameof(functionOwner), functionOwner); - - string functionName = _Buffer.ReadName(); - Record(nameof(functionName), functionName); - - // Can be null in UE3 packages - propertyValue = functionOwner != null - ? $"{functionOwner.Name}.{functionName}" - : $"{functionName}"; - break; - } - - #region HardCoded Struct Types - - case PropertyType.Color: - { - _Buffer.ReadStructMarshal(out UColor color); - propertyValue += $"R={PropertyDisplay.FormatLiteral(color.R)}," + - $"G={PropertyDisplay.FormatLiteral(color.G)}," + - $"B={PropertyDisplay.FormatLiteral(color.B)}," + - $"A={PropertyDisplay.FormatLiteral(color.A)}"; - break; - } - - case PropertyType.LinearColor: - { - _Buffer.ReadStructMarshal(out ULinearColor color); - propertyValue += $"R={PropertyDisplay.FormatLiteral(color.R)}," + - $"G={PropertyDisplay.FormatLiteral(color.G)}," + - $"B={PropertyDisplay.FormatLiteral(color.B)}," + - $"A={PropertyDisplay.FormatLiteral(color.A)}"; - break; - } - - case PropertyType.Vector2D: - { - _Buffer.ReadStructMarshal(out UVector2D vector); - propertyValue += $"X={PropertyDisplay.FormatLiteral(vector.X)}," + - $"Y={PropertyDisplay.FormatLiteral(vector.Y)}"; - break; - } - - case PropertyType.Vector: - { - _Buffer.ReadStructMarshal(out UVector vector); - propertyValue += $"X={PropertyDisplay.FormatLiteral(vector.X)}," + - $"Y={PropertyDisplay.FormatLiteral(vector.Y)}," + - $"Z={PropertyDisplay.FormatLiteral(vector.Z)}"; - break; - } - - case PropertyType.Vector4: - { - _Buffer.ReadStructMarshal(out UVector4 vector); - propertyValue += $"X={PropertyDisplay.FormatLiteral(vector.X)}," + - $"Y={PropertyDisplay.FormatLiteral(vector.Y)}," + - $"Z={PropertyDisplay.FormatLiteral(vector.Z)}," + - $"W={PropertyDisplay.FormatLiteral(vector.W)}"; - break; - } - - case PropertyType.TwoVectors: - { - string v1 = DeserializeDefaultPropertyValue(PropertyType.Vector, ref deserializeFlags); - string v2 = DeserializeDefaultPropertyValue(PropertyType.Vector, ref deserializeFlags); - propertyValue += $"v1=({v1})," + - $"v2=({v2})"; - break; - } - - case PropertyType.Rotator: - { - _Buffer.ReadStructMarshal(out URotator rotator); - propertyValue += $"Pitch={rotator.Pitch}," + - $"Yaw={rotator.Yaw}," + - $"Roll={rotator.Roll}"; - break; - } - - case PropertyType.Guid: - { - _Buffer.ReadStructMarshal(out UGuid guid); - propertyValue += $"A={guid.A}," + - $"B={guid.B}," + - $"C={guid.C}," + - $"D={guid.D}"; - break; - } - - case PropertyType.Sphere: - { - AssertFastSerialize(_Buffer); - _Buffer.ReadStructMarshal(out USphere sphere); - propertyValue += $"W={PropertyDisplay.FormatLiteral(sphere.W)}," + - $"X={PropertyDisplay.FormatLiteral(sphere.X)}," + - $"Y={PropertyDisplay.FormatLiteral(sphere.Y)}," + - $"Z={PropertyDisplay.FormatLiteral(sphere.Z)}"; - - break; - } - - case PropertyType.Plane: - { - AssertFastSerialize(_Buffer); - _Buffer.ReadStructMarshal(out UPlane plane); - propertyValue += $"W={PropertyDisplay.FormatLiteral(plane.W)}," + - $"X={PropertyDisplay.FormatLiteral(plane.X)}," + - $"Y={PropertyDisplay.FormatLiteral(plane.Y)}," + - $"Z={PropertyDisplay.FormatLiteral(plane.Z)}"; - break; - } - - case PropertyType.Scale: - { - _Buffer.ReadStructMarshal(out UScale scale); - propertyValue += "Scale=(" + - $"X={PropertyDisplay.FormatLiteral(scale.Scale.X)}," + - $"Y={PropertyDisplay.FormatLiteral(scale.Scale.Y)}," + - $"Z={PropertyDisplay.FormatLiteral(scale.Scale.Z)})," + - $"SheerRate={PropertyDisplay.FormatLiteral(scale.SheerRate)}," + - $"SheerAxis={scale.SheerAxis}"; - break; - } - - case PropertyType.Box: - { - AssertFastSerialize(_Buffer); - string min = DeserializeDefaultPropertyValue(PropertyType.Vector, ref deserializeFlags); - string max = DeserializeDefaultPropertyValue(PropertyType.Vector, ref deserializeFlags); - string isValid = - DeserializeDefaultPropertyValue(PropertyType.ByteProperty, ref deserializeFlags); - propertyValue += $"Min=({min})," + - $"Max=({max})," + - $"IsValid={isValid}"; - break; - } - - case PropertyType.Quat: - { - AssertFastSerialize(_Buffer); - _Buffer.ReadStructMarshal(out UQuat quat); - propertyValue += $"X={PropertyDisplay.FormatLiteral(quat.X)}," + - $"Y={PropertyDisplay.FormatLiteral(quat.Y)}," + - $"Z={PropertyDisplay.FormatLiteral(quat.Z)}," + - $"W={PropertyDisplay.FormatLiteral(quat.W)}"; - break; - } - - case PropertyType.Matrix: - { - AssertFastSerialize(_Buffer); - //_Buffer.ReadAtomicStruct(out UMatrix matrix); - string xPlane = DeserializeDefaultPropertyValue(PropertyType.Plane, ref deserializeFlags); - string yPlane = DeserializeDefaultPropertyValue(PropertyType.Plane, ref deserializeFlags); - string zPlane = DeserializeDefaultPropertyValue(PropertyType.Plane, ref deserializeFlags); - string wPlane = DeserializeDefaultPropertyValue(PropertyType.Plane, ref deserializeFlags); - propertyValue += $"XPlane=({xPlane}),YPlane=({yPlane}),ZPlane=({zPlane}),WPlane=({wPlane})"; - break; - } - - case PropertyType.IntPoint: - { - string x = DeserializeDefaultPropertyValue(PropertyType.IntProperty, ref deserializeFlags); - string y = DeserializeDefaultPropertyValue(PropertyType.IntProperty, ref deserializeFlags); - propertyValue += $"X={x},Y={y}"; - break; - } - - case PropertyType.PointRegion: - { - string zone = DeserializeDefaultPropertyValue(PropertyType.ObjectProperty, ref deserializeFlags); - string iLeaf = DeserializeDefaultPropertyValue(PropertyType.IntProperty, ref deserializeFlags); - string zoneNumber = - DeserializeDefaultPropertyValue(PropertyType.ByteProperty, ref deserializeFlags); - propertyValue += $"Zone={zone},iLeaf={iLeaf},ZoneNumber={zoneNumber}"; - break; - } - - #endregion - - case PropertyType.StructProperty: - { - deserializeFlags |= DeserializeFlags.WithinStruct; -#if DNF - if (_Buffer.Package.Build == UnrealPackage.GameBuild.BuildName.DNF) - { - goto nonAtomic; - } -#endif - // Ugly hack, but this will do for now until this entire function gets "rewritten" :D - if (Enum.TryParse(_TypeData.StructName, out PropertyType structPropertyType)) - { - // Not atomic if <=UE2, - // TODO: Figure out all non-atomic structs - if (_Buffer.Version < (uint)PackageObjectLegacyVersion.FastSerializeStructs) - { - switch (structPropertyType) - { - case PropertyType.Quat: - case PropertyType.Scale: // not available in UE3 - case PropertyType.Matrix: - case PropertyType.Box: - case PropertyType.Plane: - goto nonAtomic; - - // None of these exist in older packages (UE2 or older). - case PropertyType.LinearColor: - case PropertyType.Vector2D: - case PropertyType.Vector4: - goto nonAtomic; - } - } - else - { - switch (structPropertyType) - { - //case PropertyType.Coords: - //case PropertyType.Range: - // Deprecated in UDK - case PropertyType.PointRegion: - goto nonAtomic; - } - } - - propertyValue += DeserializeDefaultPropertyValue(structPropertyType, ref deserializeFlags); - goto output; - } - - nonAtomic: - // We have to modify the outer so that dynamic arrays within this struct - // will be able to find its variables to determine the array type. - FindProperty(out _Outer); - var structTags = new LinkedList(); - bool hasMore = true; - while (true) - { - var tag = new UDefaultProperty(_Container, _Outer); - try - { - // Might throw an exception if the struct is atomic - if (tag.Deserialize()) - { - structTags.AddLast(tag); - continue; - } - } - catch (Exception ex) - { - // ignored - } - - break; - } - - foreach (var tag in structTags) - { - string tagExpr = tag.Name; - if (tag.ArrayIndex > 0) - { - tagExpr += $"[{tag.ArrayIndex}]"; - } - propertyValue += $"{tagExpr}={tag.Value}"; - - if (tag != structTags.Last.Value) - { - propertyValue += ","; - } - } - - output: - propertyValue = propertyValue.Length != 0 - ? $"({propertyValue})" - : "none"; - break; - } - - case PropertyType.ArrayProperty: - { - int arraySize = _Buffer.ReadIndex(); - Record(nameof(arraySize), arraySize); - if (arraySize == 0) - { - propertyValue = "none"; - break; - } - - var arrayType = PropertyType.None; - if (_TypeData.InnerTypeName != null && !Enum.TryParse(_TypeData.InnerTypeName, out arrayType)) - { - throw new Exception( - $"Couldn't convert InnerTypeName \"{_TypeData.InnerTypeName}\" to PropertyType"); - } - - // Find the property within the outer/owner or its inheritances. - // If found it has to modify the outer so structs within this array can find their array variables. - // Additionally we need to know the property to determine the array's type. - if (arrayType == PropertyType.None) - { - var property = FindProperty(out _Outer); - if (property?.InnerProperty != null) - { - arrayType = property.InnerProperty.Type; - } - // If we did not find a reference to the associated property(because of imports) - // then try to determine the array's type by scanning the defined array types. - else if (UnrealConfig.VariableTypes != null && UnrealConfig.VariableTypes.ContainsKey(Name)) - { - var varTuple = UnrealConfig.VariableTypes[Name]; - if (varTuple != null) - { - arrayType = varTuple.Item2; - } - } - } - - if (arrayType == PropertyType.None) - { - propertyValue = "/* Array type was not detected. */"; - break; - } - - deserializeFlags |= DeserializeFlags.WithinArray; - if ((deserializeFlags & DeserializeFlags.WithinStruct) != 0) - { - // Hardcoded fix for InterpCurve and InterpCurvePoint. - if (string.Compare(Name, "Points", StringComparison.OrdinalIgnoreCase) == 0) - { - arrayType = PropertyType.StructProperty; - } - - for (var i = 0; i < arraySize; ++i) - { - propertyValue += DeserializeDefaultPropertyValue(arrayType, ref deserializeFlags) - + (i != arraySize - 1 ? "," : string.Empty); - } - - propertyValue = $"({propertyValue})"; - } - else - { - for (var i = 0; i < arraySize; ++i) - { - string elementValue = DeserializeDefaultPropertyValue(arrayType, ref deserializeFlags); - if ((_TempFlags & ReplaceNameMarker) != 0) - { - propertyValue += elementValue.Replace("%ARRAYNAME%", $"{Name}({i})"); - _TempFlags = 0x00; - } - else - { - propertyValue += $"{Name}({i})={elementValue}"; - } - - if (i != arraySize - 1) - { - propertyValue += "\r\n" + UDecompilingState.Tabs; - } - } - } - - _TempFlags |= DoNotAppendName; - break; - } - - case PropertyType.MapProperty: - { - if (Size == 0) break; - - int count = _Buffer.ReadIndex(); - Record(nameof(count), count); - - var property = FindProperty(out _Outer); - if (property == null) - { - propertyValue = "// Unable to decompile Map data."; - break; - } - - propertyValue = "("; - for (int i = 0; i < count; ++i) - { - propertyValue += DeserializeDefaultPropertyValue(property.ValueProperty.Type, ref deserializeFlags); - if (i + 1 != count) - { - propertyValue += ","; - } - } - propertyValue += ")"; - break; - } - - case PropertyType.FixedArrayProperty: - { - // We require the InnerProperty to properly deserialize this data type. - var property = FindProperty(out _Outer); - if (property == null) - { - propertyValue = "// Unable to decompile FixedArray data."; - break; - } - - var innerType = property.InnerProperty.Type; - propertyValue = "("; - for (int i = 0; i < property.Count; ++i) - { - propertyValue += DeserializeDefaultPropertyValue(innerType, ref deserializeFlags); - if (i + 1 != property.Count) - { - propertyValue += ","; - } - } - propertyValue += ")"; - break; - } - - // Note: We don't have to verify the package's version here. - case PropertyType.PointerProperty: - { - int offset = _Buffer.ReadInt32(); - propertyValue = PropertyDisplay.FormatLiteral(offset); - break; - } - - default: - throw new Exception($"Unsupported property tag type {Type}"); - } - - _Outer = orgOuter; - return propertyValue; - } - - #endregion - - public static PropertyType ResolvePropertyType(ushort propertyType) - { - return (PropertyType)propertyType; - } - - public static string ResolvePropertyTypeName(PropertyType propertyType) - { - return Enum.GetName(typeof(PropertyType), propertyType); - } - } - - [ComVisible(false)] - public sealed class DefaultPropertiesCollection : List - { - [CanBeNull] - public UDefaultProperty Find(string name) - { - return Find(prop => prop.Name == name); - } - - [CanBeNull] - public UDefaultProperty Find(UName name) - { - return Find(prop => prop.Name == name); - } - - public bool Contains(string name) - { - return Find(name) != null; - } - - public bool Contains(UName name) - { - return Find(name) != null; - } - } -} + + case PropertyType.FloatProperty: + { + float value = _Buffer.ReadFloat(); + Record(nameof(value), value); + propertyValue = PropertyDisplay.FormatLiteral(value); + break; + } + + case PropertyType.ByteProperty: + { + if (_Buffer.Version >= (uint)PackageObjectLegacyVersion.EnumTagNameAddedToBytePropertyTag && + Size == 8) + { + string enumTagName = _Buffer.ReadName(); + Record(nameof(enumTagName), enumTagName); + propertyValue = _Buffer.Version >= + (uint)PackageObjectLegacyVersion.EnumNameAddedToBytePropertyTag + ? $"{_TypeData.EnumName}.{enumTagName}" + : enumTagName; + } + else + { + byte value = _Buffer.ReadByte(); + Record(nameof(value), value); + propertyValue = PropertyDisplay.FormatLiteral(value); + } + + break; + } + + case PropertyType.InterfaceProperty: + { + var interfaceClass = _Buffer.ReadObject(); + Record(nameof(interfaceClass), interfaceClass); + propertyValue = PropertyDisplay.FormatLiteral(interfaceClass); + break; + } + + case PropertyType.ComponentProperty: + case PropertyType.ObjectProperty: + { + var constantObject = _Buffer.ReadObject(); + Record(nameof(constantObject), constantObject); + if (constantObject != null) + { + var inline = false; + // If true, object is an archetype or subobject. + if (constantObject.Outer == _Container && + (deserializeFlags & DeserializeFlags.WithinStruct) == 0) + { + // Unknown objects are only deserialized on demand. + constantObject.BeginDeserializing(); + if (constantObject.Properties != null && constantObject.Properties.Count > 0) + { + inline = true; + propertyValue = constantObject.Decompile() + "\r\n" + UDecompilingState.Tabs; + + _TempFlags |= DoNotAppendName; + if ((deserializeFlags & DeserializeFlags.WithinArray) != 0) + { + _TempFlags |= ReplaceNameMarker; + propertyValue += $"%ARRAYNAME%={constantObject.Name}"; + } + else + { + propertyValue += $"{Name}={constantObject.Name}"; + } + } + } + + if (!inline) + // =CLASS'Package.Group(s)+.Name' + { + propertyValue = PropertyDisplay.FormatLiteral(constantObject); + } + } + else + { + // =none + propertyValue = "none"; + } + + break; + } + + case PropertyType.ClassProperty: + { + var classObject = _Buffer.ReadObject(); + Record(nameof(classObject), classObject); + propertyValue = PropertyDisplay.FormatLiteral(classObject); + break; + } + + // Old StringProperty with a fixed size and null termination. + case PropertyType.StringProperty when _Buffer.Version < 100: + { + string str = _Buffer.ReadAnsiNullString(); + propertyValue = str; + break; + } + + case PropertyType.DelegateProperty when _Buffer.Version >= 100: + { + // Can by any object, usually a class. + var functionOwner = _Buffer.ReadObject(); + Record(nameof(functionOwner), functionOwner); + + string functionName = _Buffer.ReadName(); + Record(nameof(functionName), functionName); + + // Can be null in UE3 packages + propertyValue = functionOwner != null + ? $"{functionOwner.Name}.{functionName}" + : $"{functionName}"; + break; + } + + #region HardCoded Struct Types + + case PropertyType.Color: + { + _Buffer.ReadStructMarshal(out UColor color); + propertyValue += $"R={PropertyDisplay.FormatLiteral(color.R)}," + + $"G={PropertyDisplay.FormatLiteral(color.G)}," + + $"B={PropertyDisplay.FormatLiteral(color.B)}," + + $"A={PropertyDisplay.FormatLiteral(color.A)}"; + break; + } + + case PropertyType.LinearColor: + { + _Buffer.ReadStructMarshal(out ULinearColor color); + propertyValue += $"R={PropertyDisplay.FormatLiteral(color.R)}," + + $"G={PropertyDisplay.FormatLiteral(color.G)}," + + $"B={PropertyDisplay.FormatLiteral(color.B)}," + + $"A={PropertyDisplay.FormatLiteral(color.A)}"; + break; + } + + case PropertyType.Vector2D: + { + _Buffer.ReadStructMarshal(out UVector2D vector); + propertyValue += $"X={PropertyDisplay.FormatLiteral(vector.X)}," + + $"Y={PropertyDisplay.FormatLiteral(vector.Y)}"; + break; + } + + case PropertyType.Vector: + { + _Buffer.ReadStructMarshal(out UVector vector); + propertyValue += $"X={PropertyDisplay.FormatLiteral(vector.X)}," + + $"Y={PropertyDisplay.FormatLiteral(vector.Y)}," + + $"Z={PropertyDisplay.FormatLiteral(vector.Z)}"; + break; + } + + case PropertyType.Vector4: + { + _Buffer.ReadStructMarshal(out UVector4 vector); + propertyValue += $"X={PropertyDisplay.FormatLiteral(vector.X)}," + + $"Y={PropertyDisplay.FormatLiteral(vector.Y)}," + + $"Z={PropertyDisplay.FormatLiteral(vector.Z)}," + + $"W={PropertyDisplay.FormatLiteral(vector.W)}"; + break; + } + + case PropertyType.TwoVectors: + { + string v1 = DeserializeDefaultPropertyValue(PropertyType.Vector, ref deserializeFlags); + string v2 = DeserializeDefaultPropertyValue(PropertyType.Vector, ref deserializeFlags); + propertyValue += $"v1=({v1})," + + $"v2=({v2})"; + break; + } + + case PropertyType.Rotator: + { + _Buffer.ReadStructMarshal(out URotator rotator); + propertyValue += $"Pitch={rotator.Pitch}," + + $"Yaw={rotator.Yaw}," + + $"Roll={rotator.Roll}"; + break; + } + + case PropertyType.Guid: + { + _Buffer.ReadStructMarshal(out UGuid guid); + propertyValue += $"A={guid.A}," + + $"B={guid.B}," + + $"C={guid.C}," + + $"D={guid.D}"; + break; + } + + case PropertyType.Sphere: + { + AssertFastSerialize(_Buffer); + _Buffer.ReadStructMarshal(out USphere sphere); + propertyValue += $"W={PropertyDisplay.FormatLiteral(sphere.W)}," + + $"X={PropertyDisplay.FormatLiteral(sphere.X)}," + + $"Y={PropertyDisplay.FormatLiteral(sphere.Y)}," + + $"Z={PropertyDisplay.FormatLiteral(sphere.Z)}"; + + break; + } + + case PropertyType.Plane: + { + AssertFastSerialize(_Buffer); + _Buffer.ReadStructMarshal(out UPlane plane); + propertyValue += $"W={PropertyDisplay.FormatLiteral(plane.W)}," + + $"X={PropertyDisplay.FormatLiteral(plane.X)}," + + $"Y={PropertyDisplay.FormatLiteral(plane.Y)}," + + $"Z={PropertyDisplay.FormatLiteral(plane.Z)}"; + break; + } + + case PropertyType.Scale: + { + _Buffer.ReadStructMarshal(out UScale scale); + propertyValue += "Scale=(" + + $"X={PropertyDisplay.FormatLiteral(scale.Scale.X)}," + + $"Y={PropertyDisplay.FormatLiteral(scale.Scale.Y)}," + + $"Z={PropertyDisplay.FormatLiteral(scale.Scale.Z)})," + + $"SheerRate={PropertyDisplay.FormatLiteral(scale.SheerRate)}," + + $"SheerAxis={scale.SheerAxis}"; + break; + } + + case PropertyType.Box: + { + AssertFastSerialize(_Buffer); + string min = DeserializeDefaultPropertyValue(PropertyType.Vector, ref deserializeFlags); + string max = DeserializeDefaultPropertyValue(PropertyType.Vector, ref deserializeFlags); + string isValid = + DeserializeDefaultPropertyValue(PropertyType.ByteProperty, ref deserializeFlags); + propertyValue += $"Min=({min})," + + $"Max=({max})," + + $"IsValid={isValid}"; + break; + } + + case PropertyType.Quat: + { + AssertFastSerialize(_Buffer); + _Buffer.ReadStructMarshal(out UQuat quat); + propertyValue += $"X={PropertyDisplay.FormatLiteral(quat.X)}," + + $"Y={PropertyDisplay.FormatLiteral(quat.Y)}," + + $"Z={PropertyDisplay.FormatLiteral(quat.Z)}," + + $"W={PropertyDisplay.FormatLiteral(quat.W)}"; + break; + } + + case PropertyType.Matrix: + { + AssertFastSerialize(_Buffer); + //_Buffer.ReadAtomicStruct(out UMatrix matrix); + string xPlane = DeserializeDefaultPropertyValue(PropertyType.Plane, ref deserializeFlags); + string yPlane = DeserializeDefaultPropertyValue(PropertyType.Plane, ref deserializeFlags); + string zPlane = DeserializeDefaultPropertyValue(PropertyType.Plane, ref deserializeFlags); + string wPlane = DeserializeDefaultPropertyValue(PropertyType.Plane, ref deserializeFlags); + propertyValue += $"XPlane=({xPlane}),YPlane=({yPlane}),ZPlane=({zPlane}),WPlane=({wPlane})"; + break; + } + + case PropertyType.IntPoint: + { + string x = DeserializeDefaultPropertyValue(PropertyType.IntProperty, ref deserializeFlags); + string y = DeserializeDefaultPropertyValue(PropertyType.IntProperty, ref deserializeFlags); + propertyValue += $"X={x},Y={y}"; + break; + } + + case PropertyType.PointRegion: + { + string zone = DeserializeDefaultPropertyValue(PropertyType.ObjectProperty, ref deserializeFlags); + string iLeaf = DeserializeDefaultPropertyValue(PropertyType.IntProperty, ref deserializeFlags); + string zoneNumber = + DeserializeDefaultPropertyValue(PropertyType.ByteProperty, ref deserializeFlags); + propertyValue += $"Zone={zone},iLeaf={iLeaf},ZoneNumber={zoneNumber}"; + break; + } + + #endregion + + case PropertyType.StructProperty: + { + deserializeFlags |= DeserializeFlags.WithinStruct; +#if DNF + if (_Buffer.Package.Build == UnrealPackage.GameBuild.BuildName.DNF) + { + goto nonAtomic; + } +#endif + // Ugly hack, but this will do for now until this entire function gets "rewritten" :D + if (Enum.TryParse(_TypeData.StructName, out PropertyType structPropertyType)) + { + // Not atomic if <=UE2, + // TODO: Figure out all non-atomic structs + if (_Buffer.Version < (uint)PackageObjectLegacyVersion.FastSerializeStructs) + { + switch (structPropertyType) + { + case PropertyType.Quat: + case PropertyType.Scale: // not available in UE3 + case PropertyType.Matrix: + case PropertyType.Box: + case PropertyType.Plane: + goto nonAtomic; + + // None of these exist in older packages (UE2 or older). + case PropertyType.LinearColor: + case PropertyType.Vector2D: + case PropertyType.Vector4: + goto nonAtomic; + } + } + else + { + switch (structPropertyType) + { + //case PropertyType.Coords: + //case PropertyType.Range: + // Deprecated in UDK + case PropertyType.PointRegion: + goto nonAtomic; + } + } + + propertyValue += DeserializeDefaultPropertyValue(structPropertyType, ref deserializeFlags); + goto output; + } + + nonAtomic: + // We have to modify the outer so that dynamic arrays within this struct + // will be able to find its variables to determine the array type. + FindProperty(out _Outer); + var structTags = new LinkedList(); + bool hasMore = true; + while (true) + { + var tag = new UDefaultProperty(_Container, _Outer); + try + { + // Might throw an exception if the struct is atomic + if (tag.Deserialize()) + { + structTags.AddLast(tag); + continue; + } + } + catch (Exception ex) + { + // ignored + } + + break; + } + + foreach (var tag in structTags) + { + string tagExpr = tag.Name; + if (tag.ArrayIndex > 0) + { + tagExpr += $"[{tag.ArrayIndex}]"; + } + propertyValue += $"{tagExpr}={tag.Value}"; + + if (tag != structTags.Last.Value) + { + propertyValue += ","; + } + } + + output: + propertyValue = propertyValue.Length != 0 + ? $"({propertyValue})" + : "none"; + break; + } + + case PropertyType.ArrayProperty: + { + int arraySize = _Buffer.ReadIndex(); + Record(nameof(arraySize), arraySize); + if (arraySize == 0) + { + propertyValue = "none"; + break; + } + + var arrayType = PropertyType.None; + if (_TypeData.InnerTypeName != null && !Enum.TryParse(_TypeData.InnerTypeName, out arrayType)) + { + throw new Exception( + $"Couldn't convert InnerTypeName \"{_TypeData.InnerTypeName}\" to PropertyType"); + } + + // Find the property within the outer/owner or its inheritances. + // If found it has to modify the outer so structs within this array can find their array variables. + // Additionally we need to know the property to determine the array's type. + if (arrayType == PropertyType.None) + { + var property = FindProperty(out _Outer); + if (property?.InnerProperty != null) + { + arrayType = property.InnerProperty.Type; + } + // If we did not find a reference to the associated property(because of imports) + // then try to determine the array's type by scanning the defined array types. + else if (UnrealConfig.VariableTypes != null && UnrealConfig.VariableTypes.ContainsKey(Name)) + { + var varTuple = UnrealConfig.VariableTypes[Name]; + if (varTuple != null) + { + arrayType = varTuple.Item2; + } + } + } + + if (arrayType == PropertyType.None) + { + propertyValue = "/* Array type was not detected. */"; + break; + } + + deserializeFlags |= DeserializeFlags.WithinArray; + if ((deserializeFlags & DeserializeFlags.WithinStruct) != 0) + { + // Hardcoded fix for InterpCurve and InterpCurvePoint. + if (string.Compare(Name, "Points", StringComparison.OrdinalIgnoreCase) == 0) + { + arrayType = PropertyType.StructProperty; + } + + for (var i = 0; i < arraySize; ++i) + { + propertyValue += DeserializeDefaultPropertyValue(arrayType, ref deserializeFlags) + + (i != arraySize - 1 ? "," : string.Empty); + } + + propertyValue = $"({propertyValue})"; + } + else + { + for (var i = 0; i < arraySize; ++i) + { + string elementValue = DeserializeDefaultPropertyValue(arrayType, ref deserializeFlags); + if ((_TempFlags & ReplaceNameMarker) != 0) + { + propertyValue += elementValue.Replace("%ARRAYNAME%", $"{Name}({i})"); + _TempFlags = 0x00; + } + else + { + propertyValue += $"{Name}({i})={elementValue}"; + } + + if (i != arraySize - 1) + { + propertyValue += "\r\n" + UDecompilingState.Tabs; + } + } + } + + _TempFlags |= DoNotAppendName; + break; + } + + case PropertyType.MapProperty: + { + if (Size == 0) break; + + int count = _Buffer.ReadIndex(); + Record(nameof(count), count); + + var property = FindProperty(out _Outer); + if (property == null) + { + propertyValue = "// Unable to decompile Map data."; + break; + } + + propertyValue = "("; + for (int i = 0; i < count; ++i) + { + propertyValue += DeserializeDefaultPropertyValue(property.ValueProperty.Type, ref deserializeFlags); + if (i + 1 != count) + { + propertyValue += ","; + } + } + propertyValue += ")"; + break; + } + + case PropertyType.FixedArrayProperty: + { + // We require the InnerProperty to properly deserialize this data type. + var property = FindProperty(out _Outer); + if (property == null) + { + propertyValue = "// Unable to decompile FixedArray data."; + break; + } + + var innerType = property.InnerProperty.Type; + propertyValue = "("; + for (int i = 0; i < property.Count; ++i) + { + propertyValue += DeserializeDefaultPropertyValue(innerType, ref deserializeFlags); + if (i + 1 != property.Count) + { + propertyValue += ","; + } + } + propertyValue += ")"; + break; + } + + // Note: We don't have to verify the package's version here. + case PropertyType.PointerProperty: + { + int offset = _Buffer.ReadInt32(); + propertyValue = PropertyDisplay.FormatLiteral(offset); + break; + } + + default: + throw new Exception($"Unsupported property tag type {Type}"); + } + + _Outer = orgOuter; + return propertyValue; + } + + #endregion + + public static PropertyType ResolvePropertyType(ushort propertyType) + { + return (PropertyType)propertyType; + } + + public static string ResolvePropertyTypeName(PropertyType propertyType) + { + return Enum.GetName(typeof(PropertyType), propertyType); + } + } + + [ComVisible(false)] + public sealed class DefaultPropertiesCollection : List + { + [CanBeNull] + public UDefaultProperty Find(string name) + { + return Find(prop => prop.Name == name); + } + + [CanBeNull] + public UDefaultProperty Find(UName name) + { + return Find(prop => prop.Name == name); + } + + public bool Contains(string name) + { + return Find(name) != null; + } + + public bool Contains(UName name) + { + return Find(name) != null; + } + } +} From ab8b9905e98992335963d92f1cd9e646e8a96ebd Mon Sep 17 00:00:00 2001 From: Eliot Date: Sat, 4 May 2024 08:16:47 +0200 Subject: [PATCH 23/50] Support for "Gigantic" UnrealScript features. --- .../UE3/GIGANTIC/EngineBranchGigantic.cs | 33 + .../GIGANTIC/Tokens/JsonRefVariableToken.cs | 23 + src/Core/Classes/Props/UPropertyDecompiler.cs | 14 +- src/Core/Classes/UClassDecompiler.cs | 10 +- src/Core/Tokens/CastTokens.cs | 19 +- src/UnrealPackage.cs | 4627 +++++++++-------- 6 files changed, 2409 insertions(+), 2317 deletions(-) create mode 100644 src/Branch/UE3/GIGANTIC/EngineBranchGigantic.cs create mode 100644 src/Branch/UE3/GIGANTIC/Tokens/JsonRefVariableToken.cs diff --git a/src/Branch/UE3/GIGANTIC/EngineBranchGigantic.cs b/src/Branch/UE3/GIGANTIC/EngineBranchGigantic.cs new file mode 100644 index 00000000..33af3ad2 --- /dev/null +++ b/src/Branch/UE3/GIGANTIC/EngineBranchGigantic.cs @@ -0,0 +1,33 @@ +using System; +using UELib.Branch.UE3.GIGANTIC.Tokens; +using UELib.Core.Tokens; + +namespace UELib.Branch.UE3.GIGANTIC +{ + public class EngineBranchGigantic : DefaultEngineBranch + { + [Flags] + public enum PropertyFlags : ulong + { + JsonTransient = 0x80000000000U, + } + + [Flags] + public enum ClassFlags : ulong + { + JsonImport = 0x00008000U, + } + + public EngineBranchGigantic(BuildGeneration generation) : base(BuildGeneration.UE3) + { + } + + protected override TokenMap BuildTokenMap(UnrealPackage linker) + { + var tokenMap = base.BuildTokenMap(linker); + tokenMap[0x4D] = typeof(JsonRefVariableToken); + + return tokenMap; + } + } +} diff --git a/src/Branch/UE3/GIGANTIC/Tokens/JsonRefVariableToken.cs b/src/Branch/UE3/GIGANTIC/Tokens/JsonRefVariableToken.cs new file mode 100644 index 00000000..f28404e7 --- /dev/null +++ b/src/Branch/UE3/GIGANTIC/Tokens/JsonRefVariableToken.cs @@ -0,0 +1,23 @@ +using UELib.Core; +using UELib.ObjectModel.Annotations; +using UELib.Tokens; + +namespace UELib.Branch.UE3.GIGANTIC.Tokens +{ + [ExprToken(ExprToken.MetaCast)] + public class JsonRefVariableToken : UStruct.UByteCodeDecompiler.Token + { + /// + /// FIXME: Unknown format, appears to be a token wrapper for a "InstanceVariable" where the variable is a JsonRef object. + /// + public override string Decompile() + { + return DecompileNext(); + } + + public override void Deserialize(IUnrealStream stream) + { + DeserializeNext(); + } + } +} diff --git a/src/Core/Classes/Props/UPropertyDecompiler.cs b/src/Core/Classes/Props/UPropertyDecompiler.cs index 922b72e1..a939f8ee 100644 --- a/src/Core/Classes/Props/UPropertyDecompiler.cs +++ b/src/Core/Classes/Props/UPropertyDecompiler.cs @@ -302,7 +302,17 @@ public string FormatFlags() output += "serializetext "; copyFlags &= ~(ulong)Flags.PropertyFlagsLO.SerializeText; } - +#if GIGANTIC + if (Package.Build == UnrealPackage.GameBuild.BuildName.Gigantic) + { + if ((PropertyFlags & (ulong)Branch.UE3.GIGANTIC.EngineBranchGigantic.PropertyFlags.JsonTransient) != 0) + { + // jsonserialize? + output += "jsontransient "; + copyFlags &= ~(ulong)Branch.UE3.GIGANTIC.EngineBranchGigantic.PropertyFlags.JsonTransient; + } + } +#endif #if AHIT if (Package.Build == UnrealPackage.GameBuild.BuildName.AHIT) { @@ -531,4 +541,4 @@ public string FormatFlags() } } } -#endif \ No newline at end of file +#endif diff --git a/src/Core/Classes/UClassDecompiler.cs b/src/Core/Classes/UClassDecompiler.cs index 754e770c..0ce38567 100644 --- a/src/Core/Classes/UClassDecompiler.cs +++ b/src/Core/Classes/UClassDecompiler.cs @@ -375,7 +375,15 @@ private string FormatFlags() } } #endif - +#if GIGANTIC + if (Package.Build == UnrealPackage.GameBuild.BuildName.Gigantic) + { + if ((ClassFlags & (ulong)Branch.UE3.GIGANTIC.EngineBranchGigantic.ClassFlags.JsonImport) != 0) + { + output += "\r\n\tjsonimport"; + } + } +#endif #if AHIT if (Package.Build == UnrealPackage.GameBuild.BuildName.AHIT) { diff --git a/src/Core/Tokens/CastTokens.cs b/src/Core/Tokens/CastTokens.cs index 871d8fab..ba63600d 100644 --- a/src/Core/Tokens/CastTokens.cs +++ b/src/Core/Tokens/CastTokens.cs @@ -104,7 +104,24 @@ public override string Decompile() } GetFriendlyCastName(out string castTypeName); - Debug.Assert(castTypeName != default, "Detected an unresolved token."); + if (castTypeName == default) + { +#if GIGANTIC + // HACK: Should implement a cast tokens table in the engine branch instead. + if (Package.Build == UnrealPackage.GameBuild.BuildName.Gigantic) + { + switch ((uint)CastOpCode) + { + case 0x32: + case 0x34: + // FIXME: Unknown format + castTypeName = "JsonRef"; + break; + } + } +#endif + } + Debug.Assert(castTypeName != default, $"Detected an unresolved token '0x{CastOpCode:X}'."); return $"{castTypeName}({DecompileNext()})"; } } diff --git a/src/UnrealPackage.cs b/src/UnrealPackage.cs index e66f5656..6351f964 100644 --- a/src/UnrealPackage.cs +++ b/src/UnrealPackage.cs @@ -1,2319 +1,2320 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Diagnostics.Contracts; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Runtime.CompilerServices; -using UELib.Annotations; -using UELib.Branch; -using UELib.Branch.UE2.AA2; -using UELib.Branch.UE2.DNF; -using UELib.Branch.UE3.APB; -using UELib.Branch.UE3.DD2; -using UELib.Branch.UE3.MOH; -using UELib.Branch.UE3.RSS; -using UELib.Branch.UE4; -using UELib.Flags; - -namespace UELib -{ - using Core; - using Decoding; - using Branch.UE2.DVS; - - /// - /// Represents the method that will handle the UELib.UnrealPackage.NotifyObjectAdded - /// event of a new added UELib.Core.UObject. - /// - /// The source of the event. - /// A UELib.UnrealPackage.ObjectEventArgs that contains the event data. - public delegate void NotifyObjectAddedEventHandler(object sender, ObjectEventArgs e); - - /// - /// Represents the method that will handle the UELib.UnrealPackage.NotifyPackageEvent - /// event of a triggered event within the UELib.UnrealPackage. - /// - /// The source of the event. - /// A UELib.UnrealPackage.PackageEventArgs that contains the event data. - public delegate void PackageEventHandler(object sender, UnrealPackage.PackageEventArgs e); - - /// - /// Represents the method that will handle the UELib.UnrealPackage.NotifyInitializeUpdate - /// event of a UELib.Core.UObject update. - /// - [PublicAPI] - public delegate void NotifyUpdateEvent(); - - /// - /// Registers the class as an Unreal class. The class's name is required to begin with the letter "U". - /// When an Unreal Package is initializing, all described objects will be initialized as the registered class if its name matches as described by its export item. - /// - /// Note: Usage restricted to the executing assembly(UELib) only! - /// - [MeansImplicitUse] - [AttributeUsage(AttributeTargets.Class)] - public sealed class UnrealRegisterClassAttribute : Attribute - { - } - - /// - /// Represents data of a loaded unreal package. - /// - public sealed class UnrealPackage : IDisposable, IBinaryData - { - #region General Members - - public BinaryMetaData BinaryMetaData { get; } = new BinaryMetaData(); - - // Reference to the stream used when reading this package - public UPackageStream Stream; - - /// - /// The signature of a 'Unreal Package'. - /// - public const uint Signature = 0x9E2A83C1; - - public const uint Signature_BigEndian = 0xC1832A9E; - - /// - /// The full name of this package including directory. - /// - private readonly string _FullPackageName = "UnrealPackage"; - - [PublicAPI] public string FullPackageName => _FullPackageName; - - [PublicAPI] public string PackageName => Path.GetFileNameWithoutExtension(_FullPackageName); - - [PublicAPI] public string PackageDirectory => Path.GetDirectoryName(_FullPackageName); - - #endregion - - // TODO: Move to UnrealBuild.cs - public sealed class GameBuild : object - { - [AttributeUsage(AttributeTargets.Field, AllowMultiple = true)] - private sealed class BuildAttribute : Attribute - { - private readonly int _MinVersion; - private readonly int _MaxVersion; - private readonly uint _MinLicensee; - private readonly uint _MaxLicensee; - - public readonly BuildGeneration Generation; - public readonly BuildFlags Flags; - - private readonly bool _VerifyEqual; - - public BuildAttribute(int minVersion, uint minLicensee, - BuildGeneration gen = BuildGeneration.Undefined) - { - _MinVersion = minVersion; - _MinLicensee = minLicensee; - Generation = gen; - _VerifyEqual = true; - } - - - public BuildAttribute(int minVersion, uint minLicensee, - BuildFlags flags, - BuildGeneration gen = BuildGeneration.Undefined) - { - _MinVersion = minVersion; - _MinLicensee = minLicensee; - Flags = flags; - Generation = gen; - _VerifyEqual = true; - } - - public BuildAttribute(int minVersion, int maxVersion, uint minLicensee, uint maxLicensee, - BuildGeneration gen = BuildGeneration.Undefined) - { - _MinVersion = minVersion; - _MaxVersion = maxVersion; - _MinLicensee = minLicensee; - _MaxLicensee = maxLicensee; - Generation = gen; - } - - public BuildAttribute(int minVersion, int maxVersion, uint minLicensee, uint maxLicensee, - BuildFlags flags, - BuildGeneration gen = BuildGeneration.Undefined) - { - _MinVersion = minVersion; - _MaxVersion = maxVersion; - _MinLicensee = minLicensee; - _MaxLicensee = maxLicensee; - Flags = flags; - Generation = gen; - } - - public bool Verify(GameBuild gb, PackageFileSummary summary) - { - return _VerifyEqual - ? summary.Version == _MinVersion && summary.LicenseeVersion == _MinLicensee - : summary.Version >= _MinVersion && summary.Version <= _MaxVersion - && summary.LicenseeVersion >= _MinLicensee - && summary.LicenseeVersion <= _MaxLicensee; - } - } - - // Note: Some builds use the EngineVersion to represent as the LicenseeVersion, e.g Unreal2 and DCUO. - public enum BuildName - { - Unset, - Default, - Unknown, - - /// - /// Standard - /// - /// 61/000 - /// - [Build(61, 0, BuildGeneration.UE1)] Unreal1, - - /// - /// Standard, Unreal Tournament & Deus Ex - /// - /// 68:69/000 - /// - [Build(68, 69, 0u, 0u, BuildGeneration.UE1)] - UT, - - //[Build(80, 0, BuildGeneration.UE1)] - BrotherBear, - - /// - /// Clive Barker's Undying - /// - /// 72:85/000 (Only 84 to 85 is auto detected, other versions are overlapping with older unrelated games) - /// - [Build(84, 85, 0u, 0u, BuildGeneration.UE1)] - Undying, - - /// - /// Deus Ex: Invisible War - /// - /// Missing support for custom classes such as BitfieldProperty and BitfieldEnum among others. - /// 95/69 - /// - [Build(95, 69, BuildGeneration.Flesh)] DeusEx_IW, - - /// - /// Thief: Deadly Shadows - /// - /// 95/133 - /// - [Build(95, 133, BuildGeneration.Flesh)] - Thief_DS, - - /// - /// 99:117/005:008 - /// Latest patch? Same structure as UT2004's UE2.5 - /// 121/029 (Overlapped with UT2004) - /// - [Build(99, 117, 5u, 8u)] UT2003, - - [Build(100, 17)] SC1, - - /// - /// 100/058 - /// - [Build(100, 58)] XIII, - - /// - /// 110/2609 - /// - [Build(110, 110, 2481u, 2609u)] Unreal2, - - /// - /// 118:120/004:008 - /// - [BuildEngineBranch(typeof(EngineBranchDVS))] [Build(118, 120, 4u, 8u)] - Devastation, - - /// - /// Tom Clancy's Rainbow Six 3: Raven Shield - /// - /// 118/011:014 - /// - [Build(118, 118, 11u, 14u)] R6RS, - - /// - /// Unreal II: eXpanded MultiPlayer - /// - /// 126/000 - /// - [Build(123, 126, 0u, 0u)] Unreal2XMP, - - /// - /// 118:128/025:029 - /// (Overlaps latest UT2003) - /// - [Build(118, 128, 25u, 29u, BuildGeneration.UE2_5)] - UT2004, - - /// - /// America's Army 2.X - /// Represents both AAO and AAA - /// - /// Built on UT2004 - /// 128/032:033 - /// - /// For now we have three AA2 versions defined here to help us distinguish the byte-code token map. - /// - [Build(128, 32u, BuildGeneration.AGP)] [BuildEngineBranch(typeof(EngineBranchAA2))] - AA2_2_5, - - [Build(128, 32u, BuildGeneration.AGP)] [BuildEngineBranch(typeof(EngineBranchAA2))] - AA2_2_6, - - [Build(128, 33u, BuildGeneration.AGP)] [BuildEngineBranch(typeof(EngineBranchAA2))] - AA2_2_8, - - /// - /// Vanguard: Saga of Heroes - /// - /// 129/035 - /// Some packages have 128/025 but those are in conflict with UT2004. - /// - [Build(128, 129, 34u, 35u, BuildGeneration.UE2_5)] - Vanguard_SOH, - - // IrrationalGames/Vengeance - 129:143/027:059 - - /// - /// Tribes: Vengeance - /// - /// 130/027 - /// - [Build(130, 27, BuildGeneration.Vengeance)] - Tribes_VG, - - /// - /// 129/027 - /// - [Build(129, 27, BuildGeneration.Vengeance)] - Swat4, - - /// - /// Lemony Snicket's A Series of Unfortunate Events - /// - /// 129/003 - /// - [Build(129, 3, BuildGeneration.UE2)] LSGame, - - /// - /// BioShock 1 & 2 - /// - /// 130:143/056:059 - /// - [Build(130, 143, 56u, 59u, BuildGeneration.Vengeance)] - BioShock, - - /// - /// Duke Nukem Forever - /// - /// 156/036 - /// - [Build(156, 36u, BuildGeneration.UE2)] [BuildEngineBranch(typeof(EngineBranchDNF))] - DNF, - - /// - /// The Chronicles of Spellborn - /// - /// Built on UT2004 - /// 159/029 - /// Comes with several new non-standard UnrealScript features, these are however not supported. - /// - [Build(159, 29u, BuildGeneration.UE2_5)] - Spellborn, - - /// - /// Standard - /// - /// 369/006 - /// - [Build(369, 6)] RoboBlitz, - - /// - /// Medal of Honor: Airborne - /// - /// 421/011 - /// - [Build(421, 11)] MOHA, - - /// - /// 472/046 - /// - [Build(472, 46, BuildFlags.ConsoleCooked)] - MKKE, - - /// - /// Gears of War - /// - /// 490/009 - /// - [Build(490, 9)] GoW1, - - [Build(511, 039, BuildGeneration.HMS)] // The Bourne Conspiracy - [Build(511, 145, BuildGeneration.HMS)] // Transformers: War for Cybertron (PC version) - [Build(511, 144, BuildGeneration.HMS)] - // Transformers: War for Cybertron (PS3 and XBox 360 version) - Transformers, - - /// - /// 512/000 - /// - [Build(512, 0)] UT3, - - /// - /// 536/043 - /// - [Build(536, 43)] MirrorsEdge, - - /// - /// Transformers: Dark of the Moon - /// - [Build(537, 174, BuildGeneration.HMS)] Transformers2, - - /// - /// 539/091 - /// - [Build(539, 91)] AlphaProtocol, - - /// - /// APB: All Points Bulletin & APB: Reloaded - /// - /// 547/028:032 - /// - [Build(547, 547, 28u, 32u)] [BuildEngineBranch(typeof(EngineBranchAPB))] - APB, - - /// - /// Standard, Gears of War 2 - /// - /// 575/000 - /// Xenon is enabled here, because the package is missing editor data, the editor data of UStruct is however still serialized. - /// - [Build(575, 0, BuildFlags.XenonCooked)] - GoW2, - - /// - /// 576/005 - /// - [Build(576, 5)] CrimeCraft, - - /// - /// Batman: Arkham Asylum - /// - /// 576/021 - /// No Special support, but there's no harm in recognizing this build. - /// - [Build(576, 21)] Batman1, - - /// - /// 576/100 - /// - [Build(576, 100)] Homefront, - - /// - /// Medal of Honor (2010) - /// Windows, PS3, Xbox 360 - /// Defaulting to ConsoleCooked. - /// XenonCooked is required to read the Xbox 360 packages. - /// 581/058 - /// - [Build(581, 58, BuildFlags.ConsoleCooked)] [BuildEngineBranch(typeof(EngineBranchMOH))] - MOH, - - /// - /// 584/058 - /// - [Build(584, 58)] Borderlands, - - /// - /// 584/126 - /// - [Build(584, 126)] Singularity, - - /// - /// 590/001 - /// - [Build(590, 1, BuildFlags.XenonCooked)] - ShadowComplex, - - /// - /// 610/014 - /// - [Build(610, 14)] Tera, - - /// - /// DC Universe Online - /// - /// 648/6405 - /// - [Build(648, 6405)] DCUO, - - /// - /// Dungeon Defenders 2 - /// - /// 687-688/111-117 - /// - [Build(687, 688, 111, 117)] [BuildEngineBranch(typeof(EngineBranchDD2))] - DD2, - - /// - /// BioShock Infinite - /// 727/075 (partially upgraded to 756 or higher) - /// - [Build(727, 75)] [OverridePackageVersion((uint)PackageObjectLegacyVersion.SuperReferenceMovedToUStruct)] - Bioshock_Infinite, - - /// - /// 742/029 - /// - [Build(742, 29, BuildFlags.ConsoleCooked)] - BulletStorm, - - /// - /// 801/030 - /// - [Build(801, 30)] Dishonored, - - /// - /// 788/001,828/000 - /// - [Build(788, 1, BuildFlags.ConsoleCooked)] - // Conflict with GoW3 - //[Build(828, 0, BuildFlags.ConsoleCooked)] - InfinityBlade, - - /// - /// Standard, Gears of War 3 - /// - /// 828/000 - /// - [Build(828, 0, BuildFlags.ConsoleCooked)] - GoW3, - - /// - /// 832/021 - /// - [Build(832, 21)] RememberMe, - - /// - /// 832/046 - /// - [Build(832, 46)] Borderlands2, - - /// - /// 842-864/001 - /// - [Build(842, 1, BuildFlags.ConsoleCooked)] - [Build(864, 1, BuildFlags.ConsoleCooked)] - InfinityBlade2, - - // Cannot auto-detect, ambiguous with UDK-2015-01-29 - //[Build(868, 0, BuildFlags.ConsoleCooked)] - InfinityBlade3, - - /// - /// 868/008 - /// - [Build(868, 8, BuildFlags.ConsoleCooked)] - InjusticeMobile, - - /// - /// XCom - /// - /// 845/059 - /// - [Build(845, 59)] XCOM_EU, - - /// - /// XCom 2: War of The Chosen - /// - /// 845/120 - /// - [Build(845, 120)] XCOM2WotC, - - /// - /// Transformers: Fall of Cybertron - /// 846/181 - /// - [Build(846, 181, BuildGeneration.HMS)] [OverridePackageVersion(587)] - Transformers3, - - /// - /// 860/002-004 - /// - [Build(860, 860, 2, 4)] Hawken, - - /// - /// Batman: Arkham City - /// - /// 805/101 - /// - [Build(805, 101)] [BuildEngineBranch(typeof(EngineBranchRSS))] - Batman2, - - /// - /// Batman: Arkham Origins - /// - /// 806/103 - /// 807/137-138 - /// - [Build(806, 103)] [Build(807, 807, 137, 138)] [BuildEngineBranch(typeof(EngineBranchRSS))] - Batman3, - - /// - /// 807/104 - /// - [Build(807, 104)] [BuildEngineBranch(typeof(EngineBranchRSS))] - Batman3MP, - - /// - /// Batman: Arkham Knight - /// - /// 863/32995(227 & ~8000) - /// - [Build(863, 32995)] [OverridePackageVersion(863, 227)] [BuildEngineBranch(typeof(EngineBranchRSS))] - Batman4, - - /// - /// Gigantic: Rampage Edition - /// - /// 867/008:010 - /// - [Build(867, 867, 8u, 10u, BuildGeneration.UE3)] Gigantic, - - /// - /// Rocket League - /// - /// 867/009:032 - /// Requires third-party decompression and decryption - /// - [Build(867, 868, 9u, 32u)] RocketLeague, - - /// - /// Battleborn - /// - /// 874/078 - /// - /// EngineVersion and CookerVersion are packed with the respective Licensee version. - /// - [Build(874, 78u)] Battleborn, - - /// - /// A Hat in Time - /// - /// 877:893/005 - /// - /// The earliest available version with any custom specifiers is 1.0 (877) - Un-Drew. - /// - [Build(877, 893, 5, 5)] AHIT, - - /// - /// Special Force 2 - /// - /// 904/009 (Non-standard version, actual Epic version might be 692 or higher) - /// - [Build(904, 904, 09u, 014u)] - [OverridePackageVersion((uint)PackageObjectLegacyVersion.ProbeMaskReducedAndIgnoreMaskRemoved)] - SpecialForce2, - } - - public BuildName Name { get; } - - [Obsolete] public uint Version { get; } - - [Obsolete] public uint LicenseeVersion { get; } - - public uint? OverrideVersion { get; } - public ushort? OverrideLicenseeVersion { get; } - - public BuildGeneration Generation { get; } - [CanBeNull] public readonly Type EngineBranchType; - - [Obsolete("To be deprecated")] public readonly BuildFlags Flags; - - public GameBuild(uint overrideVersion, ushort overrideLicenseeVersion, BuildGeneration generation, - Type engineBranchType, - BuildFlags flags) - { - OverrideVersion = overrideVersion; - OverrideLicenseeVersion = overrideLicenseeVersion; - Generation = generation; - EngineBranchType = engineBranchType; - Flags = flags; - } - - public GameBuild(UnrealPackage package) - { - // If UE Explorer's PlatformMenuItem is equal to "Console", set ConsoleCooked flag. - // This is required for correct serialization of unrecognized Console packages - if (UnrealConfig.Platform == UnrealConfig.CookedPlatform.Console) - { - Flags |= BuildFlags.ConsoleCooked; - } - - var buildInfo = FindBuildInfo(package, out var buildAttribute); - if (buildInfo == null) - { - Name = package.Summary.LicenseeVersion == 0 - ? BuildName.Default - : BuildName.Unknown; - return; - } - - Name = (BuildName)Enum.Parse(typeof(BuildName), buildInfo.Name); - Version = package.Summary.Version; - LicenseeVersion = package.Summary.LicenseeVersion; - - if (buildAttribute != null) - { - Generation = buildAttribute.Generation; - Flags = buildAttribute.Flags; - } - - var overrideAttribute = buildInfo.GetCustomAttribute(false); - if (overrideAttribute != null) - { - OverrideVersion = overrideAttribute.FixedVersion; - OverrideLicenseeVersion = overrideAttribute.FixedLicenseeVersion; - } - - var engineBranchAttribute = buildInfo.GetCustomAttribute(false); - if (engineBranchAttribute != null) - { - // We cannot create the instance here, because the instance itself may be dependent on GameBuild. - EngineBranchType = engineBranchAttribute.EngineBranchType; - } - } - - [CanBeNull] - private FieldInfo FindBuildInfo(UnrealPackage linker, [CanBeNull] out BuildAttribute buildAttribute) - { - buildAttribute = null; - - // Auto-detect - if (linker.BuildTarget == BuildName.Unset) - { - var builds = typeof(BuildName).GetFields(); - foreach (var build in builds) - { - var buildAttributes = build.GetCustomAttributes(false); - buildAttribute = buildAttributes.FirstOrDefault(attr => attr.Verify(this, linker.Summary)); - if (buildAttribute == null) - continue; - - return build; - } - - return null; - } - - if (linker.BuildTarget != BuildName.Unknown) - { - string buildName = Enum.GetName(typeof(BuildName), linker.BuildTarget); - var build = typeof(BuildName).GetField(buildName); - return build; - } - - return null; - } - - public static bool operator ==(GameBuild b, BuildGeneration gen) - { - return b.Generation == gen; - } - - public static bool operator !=(GameBuild b, BuildGeneration gen) - { - return b.Generation != gen; - } - - public static bool operator ==(GameBuild b, BuildName name) - { - return b.Name == name; - } - - public static bool operator !=(GameBuild b, BuildName name) - { - return b.Name != name; - } - - /// - public override bool Equals(object obj) - { - return Name == (BuildName)obj; - } - - /// - public override int GetHashCode() - { - return (int)Name; - } - - public override string ToString() - { - return Name.ToString(); - } - } - - public GameBuild.BuildName BuildTarget = GameBuild.BuildName.Unset; - - /// - /// The auto-detected (can be set before deserialization to override auto-detection). - /// This needs to be set to the correct build in order to load some game-specific data from packages. - /// - public GameBuild Build; - - /// - /// The branch that we are using to load the data contained within this package. - /// - public EngineBranch Branch; - - /// - /// The platform that the cooker was cooking this package for. - /// Needs to be set to Console for decompressed .xxx packages etc. - /// - public BuildPlatform CookerPlatform; - - public struct PackageFileEngineVersion : IUnrealDeserializableClass - { - public uint Major, Minor, Patch; - public uint Changelist; - public string Branch; - - public void Deserialize(IUnrealStream stream) - { - Major = stream.ReadUInt16(); - Minor = stream.ReadUInt16(); - Patch = stream.ReadUInt16(); - Changelist = stream.ReadUInt32(); - Branch = stream.ReadText(); - } - - public override string ToString() - { - return $"{Major}.{Minor}.{Patch}"; - } - } - - public struct PackageFileSummary : IUnrealSerializableClass - { - public uint Version; - public ushort LicenseeVersion; - - public uint UE4Version; - public uint UE4LicenseeVersion; - - public UnrealFlags PackageFlags; - - [Obsolete] - private const int VHeaderSize = 249; - public int HeaderSize; - - [Obsolete] - private const int VFolderName = 269; - - /// - /// UPK content category e.g. Weapons, Sounds or Meshes. - /// - public string FolderName; - - public string LocalizationId; - - public int NameCount, NameOffset; - public int ExportCount, ExportOffset; - public int ImportCount, ImportOffset; - public int HeritageCount, HeritageOffset; - - /// - /// List of heritages. UE1 way of defining generations. - /// - public UArray Heritages; - - [Obsolete] - private const int VDependsOffset = 415; - public int DependsOffset; - - public UGuid Guid; - public UArray Generations; - - private PackageFileEngineVersion PackageEngineVersion; - private PackageFileEngineVersion PackageCompatibleEngineVersion; - - [Obsolete] - private const int VEngineVersion = 245; - - [Obsolete] - public const int VCookerVersion = 277; - - public int EngineVersion; - public int CookerVersion; - - [Obsolete] - private const int VCompression = 334; - public uint CompressionFlags; - public UArray CompressedChunks; - - [Obsolete] - private const int VPackageSource = 482; - public uint PackageSource; - - [Obsolete] - private const int VAdditionalPackagesToCook = 516; - public UArray AdditionalPackagesToCook; - - [Obsolete] - private const int VImportExportGuidsOffset = 623; - public int ImportExportGuidsOffset; - public int ImportGuidsCount; - public int ExportGuidsCount; - - [Obsolete] - private const int VThumbnailTableOffset = 584; - public int ThumbnailTableOffset; - - [Obsolete] - private const int VTextureAllocations = 767; - - public int GatherableTextDataCount; - public int GatherableTextDataOffset; - - public int StringAssetReferencesCount; - public int StringAssetReferencesOffset; - - public int SearchableNamesOffset; - - public UGuid PersistentGuid; - public UGuid OwnerPersistentGuid; - - private void SetupBuild(UnrealPackage package) - { - // Auto-detect - if (package.Build == null) - { - package.Build = new GameBuild(package); - if (package.Build.Flags.HasFlag(BuildFlags.ConsoleCooked)) - { - package.CookerPlatform = BuildPlatform.Console; - } - } - - if (package.Build.OverrideVersion.HasValue) Version = package.Build.OverrideVersion.Value; - if (package.Build.OverrideLicenseeVersion.HasValue) - LicenseeVersion = package.Build.OverrideLicenseeVersion.Value; - - if (OverrideVersion != 0) Version = OverrideVersion; - if (OverrideLicenseeVersion != 0) LicenseeVersion = OverrideLicenseeVersion; - } - - // TODO: Re-use an instantiated branch if available and if the package's version and licensee are an identical match. - private void SetupBranch(UnrealPackage package) - { - if (package.Build.EngineBranchType != null) - { - package.Branch = (EngineBranch)Activator.CreateInstance(package.Build.EngineBranchType, - package.Build.Generation); - } - else if (package.Summary.UE4Version > 0) - { - package.Branch = new EngineBranchUE4(); - } - else - { - package.Branch = new DefaultEngineBranch(package.Build.Generation); - } - - package.Branch.Setup(package); - } - - public void Serialize(IUnrealStream stream) - { - throw new NotImplementedException(); - } - - public void Deserialize(IUnrealStream stream) - { - const short maxLegacyVersion = -7; - - // Read as one variable due Big Endian Encoding. - int legacyVersion = stream.ReadInt32(); - // FIXME: >= -7 is true for the game Quantum - if (legacyVersion < 0 && legacyVersion >= maxLegacyVersion) - { -#if UE4 - uint ue3Version = 0; - if (legacyVersion != -4) - { - ue3Version = stream.ReadUInt32(); - } - - UE4Version = stream.ReadUInt32(); - UE4LicenseeVersion = stream.ReadUInt32(); - - Version = ue3Version; - - // Really old, probably no longer in production files? Other than some UE4 assets found in the first public release - if (UE4Version >= 138 && UE4Version < 142) - { - stream.Skip(8); // CookedVersion, CookedLicenseeVersion - } - - if (legacyVersion <= -2) - { - // Read enum based version - if (legacyVersion == -2) - { - int count = stream.ReadInt32(); // Versions - stream.Skip(count * (4 + 4)); // Tag, Version - } - else if (legacyVersion >= -5) - { - int count = stream.ReadInt32(); - for (var i = 0; i < count; ++i) - { - stream.ReadStruct(out UGuid key); - stream.Read(out int version); - stream.Read(out string friendlyName); - } - } - else - { - int count = stream.ReadInt32(); - for (var i = 0; i < count; ++i) - { - stream.ReadStruct(out UGuid key); - stream.Read(out int version); - } - } - } -#else - throw new NotSupportedException("This version of the Unreal Engine 4 is not supported!"); -#endif - } - else - { - Version = (uint)legacyVersion; - } - - LicenseeVersion = (ushort)(Version >> 16); - Version &= 0xFFFFU; - Console.WriteLine("Package Version:" + Version + "/" + LicenseeVersion); - - SetupBuild(stream.Package); - Debug.Assert(stream.Package.Build != null); - Console.WriteLine("Build:" + stream.Package.Build); - - SetupBranch(stream.Package); - Debug.Assert(stream.Package.Branch != null); - Console.WriteLine("Branch:" + stream.Package.Branch); -#if BIOSHOCK - if (stream.Package.Build == GameBuild.BuildName.Bioshock_Infinite) - { - int unk = stream.ReadInt32(); - } -#endif -#if MKKE - if (stream.Package.Build == GameBuild.BuildName.MKKE) stream.Skip(8); -#endif -#if TRANSFORMERS - if (stream.Package.Build == BuildGeneration.HMS && - stream.LicenseeVersion >= 55) - { - if (stream.LicenseeVersion >= 181) stream.Skip(16); - - stream.Skip(4); - } -#endif - if (stream.Version >= VHeaderSize) - { - // Offset to the first class(not object) in the package. - HeaderSize = stream.ReadInt32(); - Console.WriteLine("Header Size: " + HeaderSize); - } - - if (stream.Version >= VFolderName) - { - FolderName = stream.ReadText(); - // Console.WriteLine("Folder Name:" + FolderName); - } - - PackageFlags = stream.ReadFlags32(); - Console.WriteLine("Package Flags:" + PackageFlags); -#if HAWKEN || GIGANTIC - if ((stream.Package.Build == GameBuild.BuildName.Hawken || - stream.Package.Build == GameBuild.BuildName.Gigantic) && - stream.LicenseeVersion >= 2) +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.Contracts; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; +using UELib.Annotations; +using UELib.Branch; +using UELib.Branch.UE2.AA2; +using UELib.Branch.UE2.DNF; +using UELib.Branch.UE3.APB; +using UELib.Branch.UE3.DD2; +using UELib.Branch.UE3.GIGANTIC; +using UELib.Branch.UE3.MOH; +using UELib.Branch.UE3.RSS; +using UELib.Branch.UE4; +using UELib.Flags; + +namespace UELib +{ + using Core; + using Decoding; + using Branch.UE2.DVS; + + /// + /// Represents the method that will handle the UELib.UnrealPackage.NotifyObjectAdded + /// event of a new added UELib.Core.UObject. + /// + /// The source of the event. + /// A UELib.UnrealPackage.ObjectEventArgs that contains the event data. + public delegate void NotifyObjectAddedEventHandler(object sender, ObjectEventArgs e); + + /// + /// Represents the method that will handle the UELib.UnrealPackage.NotifyPackageEvent + /// event of a triggered event within the UELib.UnrealPackage. + /// + /// The source of the event. + /// A UELib.UnrealPackage.PackageEventArgs that contains the event data. + public delegate void PackageEventHandler(object sender, UnrealPackage.PackageEventArgs e); + + /// + /// Represents the method that will handle the UELib.UnrealPackage.NotifyInitializeUpdate + /// event of a UELib.Core.UObject update. + /// + [PublicAPI] + public delegate void NotifyUpdateEvent(); + + /// + /// Registers the class as an Unreal class. The class's name is required to begin with the letter "U". + /// When an Unreal Package is initializing, all described objects will be initialized as the registered class if its name matches as described by its export item. + /// + /// Note: Usage restricted to the executing assembly(UELib) only! + /// + [MeansImplicitUse] + [AttributeUsage(AttributeTargets.Class)] + public sealed class UnrealRegisterClassAttribute : Attribute + { + } + + /// + /// Represents data of a loaded unreal package. + /// + public sealed class UnrealPackage : IDisposable, IBinaryData + { + #region General Members + + public BinaryMetaData BinaryMetaData { get; } = new BinaryMetaData(); + + // Reference to the stream used when reading this package + public UPackageStream Stream; + + /// + /// The signature of a 'Unreal Package'. + /// + public const uint Signature = 0x9E2A83C1; + + public const uint Signature_BigEndian = 0xC1832A9E; + + /// + /// The full name of this package including directory. + /// + private readonly string _FullPackageName = "UnrealPackage"; + + [PublicAPI] public string FullPackageName => _FullPackageName; + + [PublicAPI] public string PackageName => Path.GetFileNameWithoutExtension(_FullPackageName); + + [PublicAPI] public string PackageDirectory => Path.GetDirectoryName(_FullPackageName); + + #endregion + + // TODO: Move to UnrealBuild.cs + public sealed class GameBuild : object + { + [AttributeUsage(AttributeTargets.Field, AllowMultiple = true)] + private sealed class BuildAttribute : Attribute + { + private readonly int _MinVersion; + private readonly int _MaxVersion; + private readonly uint _MinLicensee; + private readonly uint _MaxLicensee; + + public readonly BuildGeneration Generation; + public readonly BuildFlags Flags; + + private readonly bool _VerifyEqual; + + public BuildAttribute(int minVersion, uint minLicensee, + BuildGeneration gen = BuildGeneration.Undefined) + { + _MinVersion = minVersion; + _MinLicensee = minLicensee; + Generation = gen; + _VerifyEqual = true; + } + + + public BuildAttribute(int minVersion, uint minLicensee, + BuildFlags flags, + BuildGeneration gen = BuildGeneration.Undefined) + { + _MinVersion = minVersion; + _MinLicensee = minLicensee; + Flags = flags; + Generation = gen; + _VerifyEqual = true; + } + + public BuildAttribute(int minVersion, int maxVersion, uint minLicensee, uint maxLicensee, + BuildGeneration gen = BuildGeneration.Undefined) + { + _MinVersion = minVersion; + _MaxVersion = maxVersion; + _MinLicensee = minLicensee; + _MaxLicensee = maxLicensee; + Generation = gen; + } + + public BuildAttribute(int minVersion, int maxVersion, uint minLicensee, uint maxLicensee, + BuildFlags flags, + BuildGeneration gen = BuildGeneration.Undefined) + { + _MinVersion = minVersion; + _MaxVersion = maxVersion; + _MinLicensee = minLicensee; + _MaxLicensee = maxLicensee; + Flags = flags; + Generation = gen; + } + + public bool Verify(GameBuild gb, PackageFileSummary summary) + { + return _VerifyEqual + ? summary.Version == _MinVersion && summary.LicenseeVersion == _MinLicensee + : summary.Version >= _MinVersion && summary.Version <= _MaxVersion + && summary.LicenseeVersion >= _MinLicensee + && summary.LicenseeVersion <= _MaxLicensee; + } + } + + // Note: Some builds use the EngineVersion to represent as the LicenseeVersion, e.g Unreal2 and DCUO. + public enum BuildName + { + Unset, + Default, + Unknown, + + /// + /// Standard + /// + /// 61/000 + /// + [Build(61, 0, BuildGeneration.UE1)] Unreal1, + + /// + /// Standard, Unreal Tournament & Deus Ex + /// + /// 68:69/000 + /// + [Build(68, 69, 0u, 0u, BuildGeneration.UE1)] + UT, + + //[Build(80, 0, BuildGeneration.UE1)] + BrotherBear, + + /// + /// Clive Barker's Undying + /// + /// 72:85/000 (Only 84 to 85 is auto detected, other versions are overlapping with older unrelated games) + /// + [Build(84, 85, 0u, 0u, BuildGeneration.UE1)] + Undying, + + /// + /// Deus Ex: Invisible War + /// + /// Missing support for custom classes such as BitfieldProperty and BitfieldEnum among others. + /// 95/69 + /// + [Build(95, 69, BuildGeneration.Flesh)] DeusEx_IW, + + /// + /// Thief: Deadly Shadows + /// + /// 95/133 + /// + [Build(95, 133, BuildGeneration.Flesh)] + Thief_DS, + + /// + /// 99:117/005:008 + /// Latest patch? Same structure as UT2004's UE2.5 + /// 121/029 (Overlapped with UT2004) + /// + [Build(99, 117, 5u, 8u)] UT2003, + + [Build(100, 17)] SC1, + + /// + /// 100/058 + /// + [Build(100, 58)] XIII, + + /// + /// 110/2609 + /// + [Build(110, 110, 2481u, 2609u)] Unreal2, + + /// + /// 118:120/004:008 + /// + [BuildEngineBranch(typeof(EngineBranchDVS))] [Build(118, 120, 4u, 8u)] + Devastation, + + /// + /// Tom Clancy's Rainbow Six 3: Raven Shield + /// + /// 118/011:014 + /// + [Build(118, 118, 11u, 14u)] R6RS, + + /// + /// Unreal II: eXpanded MultiPlayer + /// + /// 126/000 + /// + [Build(123, 126, 0u, 0u)] Unreal2XMP, + + /// + /// 118:128/025:029 + /// (Overlaps latest UT2003) + /// + [Build(118, 128, 25u, 29u, BuildGeneration.UE2_5)] + UT2004, + + /// + /// America's Army 2.X + /// Represents both AAO and AAA + /// + /// Built on UT2004 + /// 128/032:033 + /// + /// For now we have three AA2 versions defined here to help us distinguish the byte-code token map. + /// + [Build(128, 32u, BuildGeneration.AGP)] [BuildEngineBranch(typeof(EngineBranchAA2))] + AA2_2_5, + + [Build(128, 32u, BuildGeneration.AGP)] [BuildEngineBranch(typeof(EngineBranchAA2))] + AA2_2_6, + + [Build(128, 33u, BuildGeneration.AGP)] [BuildEngineBranch(typeof(EngineBranchAA2))] + AA2_2_8, + + /// + /// Vanguard: Saga of Heroes + /// + /// 129/035 + /// Some packages have 128/025 but those are in conflict with UT2004. + /// + [Build(128, 129, 34u, 35u, BuildGeneration.UE2_5)] + Vanguard_SOH, + + // IrrationalGames/Vengeance - 129:143/027:059 + + /// + /// Tribes: Vengeance + /// + /// 130/027 + /// + [Build(130, 27, BuildGeneration.Vengeance)] + Tribes_VG, + + /// + /// 129/027 + /// + [Build(129, 27, BuildGeneration.Vengeance)] + Swat4, + + /// + /// Lemony Snicket's A Series of Unfortunate Events + /// + /// 129/003 + /// + [Build(129, 3, BuildGeneration.UE2)] LSGame, + + /// + /// BioShock 1 & 2 + /// + /// 130:143/056:059 + /// + [Build(130, 143, 56u, 59u, BuildGeneration.Vengeance)] + BioShock, + + /// + /// Duke Nukem Forever + /// + /// 156/036 + /// + [Build(156, 36u, BuildGeneration.UE2)] [BuildEngineBranch(typeof(EngineBranchDNF))] + DNF, + + /// + /// The Chronicles of Spellborn + /// + /// Built on UT2004 + /// 159/029 + /// Comes with several new non-standard UnrealScript features, these are however not supported. + /// + [Build(159, 29u, BuildGeneration.UE2_5)] + Spellborn, + + /// + /// Standard + /// + /// 369/006 + /// + [Build(369, 6)] RoboBlitz, + + /// + /// Medal of Honor: Airborne + /// + /// 421/011 + /// + [Build(421, 11)] MOHA, + + /// + /// 472/046 + /// + [Build(472, 46, BuildFlags.ConsoleCooked)] + MKKE, + + /// + /// Gears of War + /// + /// 490/009 + /// + [Build(490, 9)] GoW1, + + [Build(511, 039, BuildGeneration.HMS)] // The Bourne Conspiracy + [Build(511, 145, BuildGeneration.HMS)] // Transformers: War for Cybertron (PC version) + [Build(511, 144, BuildGeneration.HMS)] + // Transformers: War for Cybertron (PS3 and XBox 360 version) + Transformers, + + /// + /// 512/000 + /// + [Build(512, 0)] UT3, + + /// + /// 536/043 + /// + [Build(536, 43)] MirrorsEdge, + + /// + /// Transformers: Dark of the Moon + /// + [Build(537, 174, BuildGeneration.HMS)] Transformers2, + + /// + /// 539/091 + /// + [Build(539, 91)] AlphaProtocol, + + /// + /// APB: All Points Bulletin & APB: Reloaded + /// + /// 547/028:032 + /// + [Build(547, 547, 28u, 32u)] [BuildEngineBranch(typeof(EngineBranchAPB))] + APB, + + /// + /// Standard, Gears of War 2 + /// + /// 575/000 + /// Xenon is enabled here, because the package is missing editor data, the editor data of UStruct is however still serialized. + /// + [Build(575, 0, BuildFlags.XenonCooked)] + GoW2, + + /// + /// 576/005 + /// + [Build(576, 5)] CrimeCraft, + + /// + /// Batman: Arkham Asylum + /// + /// 576/021 + /// No Special support, but there's no harm in recognizing this build. + /// + [Build(576, 21)] Batman1, + + /// + /// 576/100 + /// + [Build(576, 100)] Homefront, + + /// + /// Medal of Honor (2010) + /// Windows, PS3, Xbox 360 + /// Defaulting to ConsoleCooked. + /// XenonCooked is required to read the Xbox 360 packages. + /// 581/058 + /// + [Build(581, 58, BuildFlags.ConsoleCooked)] [BuildEngineBranch(typeof(EngineBranchMOH))] + MOH, + + /// + /// 584/058 + /// + [Build(584, 58)] Borderlands, + + /// + /// 584/126 + /// + [Build(584, 126)] Singularity, + + /// + /// 590/001 + /// + [Build(590, 1, BuildFlags.XenonCooked)] + ShadowComplex, + + /// + /// 610/014 + /// + [Build(610, 14)] Tera, + + /// + /// DC Universe Online + /// + /// 648/6405 + /// + [Build(648, 6405)] DCUO, + + /// + /// Dungeon Defenders 2 + /// + /// 687-688/111-117 + /// + [Build(687, 688, 111, 117)] [BuildEngineBranch(typeof(EngineBranchDD2))] + DD2, + + /// + /// BioShock Infinite + /// 727/075 (partially upgraded to 756 or higher) + /// + [Build(727, 75)] [OverridePackageVersion((uint)PackageObjectLegacyVersion.SuperReferenceMovedToUStruct)] + Bioshock_Infinite, + + /// + /// 742/029 + /// + [Build(742, 29, BuildFlags.ConsoleCooked)] + BulletStorm, + + /// + /// 801/030 + /// + [Build(801, 30)] Dishonored, + + /// + /// 788/001,828/000 + /// + [Build(788, 1, BuildFlags.ConsoleCooked)] + // Conflict with GoW3 + //[Build(828, 0, BuildFlags.ConsoleCooked)] + InfinityBlade, + + /// + /// Standard, Gears of War 3 + /// + /// 828/000 + /// + [Build(828, 0, BuildFlags.ConsoleCooked)] + GoW3, + + /// + /// 832/021 + /// + [Build(832, 21)] RememberMe, + + /// + /// 832/046 + /// + [Build(832, 46)] Borderlands2, + + /// + /// 842-864/001 + /// + [Build(842, 1, BuildFlags.ConsoleCooked)] + [Build(864, 1, BuildFlags.ConsoleCooked)] + InfinityBlade2, + + // Cannot auto-detect, ambiguous with UDK-2015-01-29 + //[Build(868, 0, BuildFlags.ConsoleCooked)] + InfinityBlade3, + + /// + /// 868/008 + /// + [Build(868, 8, BuildFlags.ConsoleCooked)] + InjusticeMobile, + + /// + /// XCom + /// + /// 845/059 + /// + [Build(845, 59)] XCOM_EU, + + /// + /// XCom 2: War of The Chosen + /// + /// 845/120 + /// + [Build(845, 120)] XCOM2WotC, + + /// + /// Transformers: Fall of Cybertron + /// 846/181 + /// + [Build(846, 181, BuildGeneration.HMS)] [OverridePackageVersion(587)] + Transformers3, + + /// + /// 860/002-004 + /// + [Build(860, 860, 2, 4)] Hawken, + + /// + /// Batman: Arkham City + /// + /// 805/101 + /// + [Build(805, 101)] [BuildEngineBranch(typeof(EngineBranchRSS))] + Batman2, + + /// + /// Batman: Arkham Origins + /// + /// 806/103 + /// 807/137-138 + /// + [Build(806, 103)] [Build(807, 807, 137, 138)] [BuildEngineBranch(typeof(EngineBranchRSS))] + Batman3, + + /// + /// 807/104 + /// + [Build(807, 104)] [BuildEngineBranch(typeof(EngineBranchRSS))] + Batman3MP, + + /// + /// Batman: Arkham Knight + /// + /// 863/32995(227 & ~8000) + /// + [Build(863, 32995)] [OverridePackageVersion(863, 227)] [BuildEngineBranch(typeof(EngineBranchRSS))] + Batman4, + + /// + /// Gigantic: Rampage Edition + /// + /// 867/008:010 + /// + [Build(867, 867, 8u, 10u)] [BuildEngineBranch(typeof(EngineBranchGigantic))] Gigantic, + + /// + /// Rocket League + /// + /// 867/009:032 + /// Requires third-party decompression and decryption + /// + [Build(867, 868, 9u, 32u)] RocketLeague, + + /// + /// Battleborn + /// + /// 874/078 + /// + /// EngineVersion and CookerVersion are packed with the respective Licensee version. + /// + [Build(874, 78u)] Battleborn, + + /// + /// A Hat in Time + /// + /// 877:893/005 + /// + /// The earliest available version with any custom specifiers is 1.0 (877) - Un-Drew. + /// + [Build(877, 893, 5, 5)] AHIT, + + /// + /// Special Force 2 + /// + /// 904/009 (Non-standard version, actual Epic version might be 692 or higher) + /// + [Build(904, 904, 09u, 014u)] + [OverridePackageVersion((uint)PackageObjectLegacyVersion.ProbeMaskReducedAndIgnoreMaskRemoved)] + SpecialForce2, + } + + public BuildName Name { get; } + + [Obsolete] public uint Version { get; } + + [Obsolete] public uint LicenseeVersion { get; } + + public uint? OverrideVersion { get; } + public ushort? OverrideLicenseeVersion { get; } + + public BuildGeneration Generation { get; } + [CanBeNull] public readonly Type EngineBranchType; + + [Obsolete("To be deprecated")] public readonly BuildFlags Flags; + + public GameBuild(uint overrideVersion, ushort overrideLicenseeVersion, BuildGeneration generation, + Type engineBranchType, + BuildFlags flags) + { + OverrideVersion = overrideVersion; + OverrideLicenseeVersion = overrideLicenseeVersion; + Generation = generation; + EngineBranchType = engineBranchType; + Flags = flags; + } + + public GameBuild(UnrealPackage package) + { + // If UE Explorer's PlatformMenuItem is equal to "Console", set ConsoleCooked flag. + // This is required for correct serialization of unrecognized Console packages + if (UnrealConfig.Platform == UnrealConfig.CookedPlatform.Console) + { + Flags |= BuildFlags.ConsoleCooked; + } + + var buildInfo = FindBuildInfo(package, out var buildAttribute); + if (buildInfo == null) + { + Name = package.Summary.LicenseeVersion == 0 + ? BuildName.Default + : BuildName.Unknown; + return; + } + + Name = (BuildName)Enum.Parse(typeof(BuildName), buildInfo.Name); + Version = package.Summary.Version; + LicenseeVersion = package.Summary.LicenseeVersion; + + if (buildAttribute != null) + { + Generation = buildAttribute.Generation; + Flags = buildAttribute.Flags; + } + + var overrideAttribute = buildInfo.GetCustomAttribute(false); + if (overrideAttribute != null) + { + OverrideVersion = overrideAttribute.FixedVersion; + OverrideLicenseeVersion = overrideAttribute.FixedLicenseeVersion; + } + + var engineBranchAttribute = buildInfo.GetCustomAttribute(false); + if (engineBranchAttribute != null) + { + // We cannot create the instance here, because the instance itself may be dependent on GameBuild. + EngineBranchType = engineBranchAttribute.EngineBranchType; + } + } + + [CanBeNull] + private FieldInfo FindBuildInfo(UnrealPackage linker, [CanBeNull] out BuildAttribute buildAttribute) + { + buildAttribute = null; + + // Auto-detect + if (linker.BuildTarget == BuildName.Unset) + { + var builds = typeof(BuildName).GetFields(); + foreach (var build in builds) + { + var buildAttributes = build.GetCustomAttributes(false); + buildAttribute = buildAttributes.FirstOrDefault(attr => attr.Verify(this, linker.Summary)); + if (buildAttribute == null) + continue; + + return build; + } + + return null; + } + + if (linker.BuildTarget != BuildName.Unknown) + { + string buildName = Enum.GetName(typeof(BuildName), linker.BuildTarget); + var build = typeof(BuildName).GetField(buildName); + return build; + } + + return null; + } + + public static bool operator ==(GameBuild b, BuildGeneration gen) + { + return b.Generation == gen; + } + + public static bool operator !=(GameBuild b, BuildGeneration gen) + { + return b.Generation != gen; + } + + public static bool operator ==(GameBuild b, BuildName name) + { + return b.Name == name; + } + + public static bool operator !=(GameBuild b, BuildName name) + { + return b.Name != name; + } + + /// + public override bool Equals(object obj) + { + return Name == (BuildName)obj; + } + + /// + public override int GetHashCode() + { + return (int)Name; + } + + public override string ToString() + { + return Name.ToString(); + } + } + + public GameBuild.BuildName BuildTarget = GameBuild.BuildName.Unset; + + /// + /// The auto-detected (can be set before deserialization to override auto-detection). + /// This needs to be set to the correct build in order to load some game-specific data from packages. + /// + public GameBuild Build; + + /// + /// The branch that we are using to load the data contained within this package. + /// + public EngineBranch Branch; + + /// + /// The platform that the cooker was cooking this package for. + /// Needs to be set to Console for decompressed .xxx packages etc. + /// + public BuildPlatform CookerPlatform; + + public struct PackageFileEngineVersion : IUnrealDeserializableClass + { + public uint Major, Minor, Patch; + public uint Changelist; + public string Branch; + + public void Deserialize(IUnrealStream stream) + { + Major = stream.ReadUInt16(); + Minor = stream.ReadUInt16(); + Patch = stream.ReadUInt16(); + Changelist = stream.ReadUInt32(); + Branch = stream.ReadText(); + } + + public override string ToString() + { + return $"{Major}.{Minor}.{Patch}"; + } + } + + public struct PackageFileSummary : IUnrealSerializableClass + { + public uint Version; + public ushort LicenseeVersion; + + public uint UE4Version; + public uint UE4LicenseeVersion; + + public UnrealFlags PackageFlags; + + [Obsolete] + private const int VHeaderSize = 249; + public int HeaderSize; + + [Obsolete] + private const int VFolderName = 269; + + /// + /// UPK content category e.g. Weapons, Sounds or Meshes. + /// + public string FolderName; + + public string LocalizationId; + + public int NameCount, NameOffset; + public int ExportCount, ExportOffset; + public int ImportCount, ImportOffset; + public int HeritageCount, HeritageOffset; + + /// + /// List of heritages. UE1 way of defining generations. + /// + public UArray Heritages; + + [Obsolete] + private const int VDependsOffset = 415; + public int DependsOffset; + + public UGuid Guid; + public UArray Generations; + + private PackageFileEngineVersion PackageEngineVersion; + private PackageFileEngineVersion PackageCompatibleEngineVersion; + + [Obsolete] + private const int VEngineVersion = 245; + + [Obsolete] + public const int VCookerVersion = 277; + + public int EngineVersion; + public int CookerVersion; + + [Obsolete] + private const int VCompression = 334; + public uint CompressionFlags; + public UArray CompressedChunks; + + [Obsolete] + private const int VPackageSource = 482; + public uint PackageSource; + + [Obsolete] + private const int VAdditionalPackagesToCook = 516; + public UArray AdditionalPackagesToCook; + + [Obsolete] + private const int VImportExportGuidsOffset = 623; + public int ImportExportGuidsOffset; + public int ImportGuidsCount; + public int ExportGuidsCount; + + [Obsolete] + private const int VThumbnailTableOffset = 584; + public int ThumbnailTableOffset; + + [Obsolete] + private const int VTextureAllocations = 767; + + public int GatherableTextDataCount; + public int GatherableTextDataOffset; + + public int StringAssetReferencesCount; + public int StringAssetReferencesOffset; + + public int SearchableNamesOffset; + + public UGuid PersistentGuid; + public UGuid OwnerPersistentGuid; + + private void SetupBuild(UnrealPackage package) + { + // Auto-detect + if (package.Build == null) + { + package.Build = new GameBuild(package); + if (package.Build.Flags.HasFlag(BuildFlags.ConsoleCooked)) + { + package.CookerPlatform = BuildPlatform.Console; + } + } + + if (package.Build.OverrideVersion.HasValue) Version = package.Build.OverrideVersion.Value; + if (package.Build.OverrideLicenseeVersion.HasValue) + LicenseeVersion = package.Build.OverrideLicenseeVersion.Value; + + if (OverrideVersion != 0) Version = OverrideVersion; + if (OverrideLicenseeVersion != 0) LicenseeVersion = OverrideLicenseeVersion; + } + + // TODO: Re-use an instantiated branch if available and if the package's version and licensee are an identical match. + private void SetupBranch(UnrealPackage package) + { + if (package.Build.EngineBranchType != null) + { + package.Branch = (EngineBranch)Activator.CreateInstance(package.Build.EngineBranchType, + package.Build.Generation); + } + else if (package.Summary.UE4Version > 0) + { + package.Branch = new EngineBranchUE4(); + } + else + { + package.Branch = new DefaultEngineBranch(package.Build.Generation); + } + + package.Branch.Setup(package); + } + + public void Serialize(IUnrealStream stream) + { + throw new NotImplementedException(); + } + + public void Deserialize(IUnrealStream stream) + { + const short maxLegacyVersion = -7; + + // Read as one variable due Big Endian Encoding. + int legacyVersion = stream.ReadInt32(); + // FIXME: >= -7 is true for the game Quantum + if (legacyVersion < 0 && legacyVersion >= maxLegacyVersion) + { +#if UE4 + uint ue3Version = 0; + if (legacyVersion != -4) + { + ue3Version = stream.ReadUInt32(); + } + + UE4Version = stream.ReadUInt32(); + UE4LicenseeVersion = stream.ReadUInt32(); + + Version = ue3Version; + + // Really old, probably no longer in production files? Other than some UE4 assets found in the first public release + if (UE4Version >= 138 && UE4Version < 142) + { + stream.Skip(8); // CookedVersion, CookedLicenseeVersion + } + + if (legacyVersion <= -2) + { + // Read enum based version + if (legacyVersion == -2) + { + int count = stream.ReadInt32(); // Versions + stream.Skip(count * (4 + 4)); // Tag, Version + } + else if (legacyVersion >= -5) + { + int count = stream.ReadInt32(); + for (var i = 0; i < count; ++i) + { + stream.ReadStruct(out UGuid key); + stream.Read(out int version); + stream.Read(out string friendlyName); + } + } + else + { + int count = stream.ReadInt32(); + for (var i = 0; i < count; ++i) + { + stream.ReadStruct(out UGuid key); + stream.Read(out int version); + } + } + } +#else + throw new NotSupportedException("This version of the Unreal Engine 4 is not supported!"); +#endif + } + else + { + Version = (uint)legacyVersion; + } + + LicenseeVersion = (ushort)(Version >> 16); + Version &= 0xFFFFU; + Console.WriteLine("Package Version:" + Version + "/" + LicenseeVersion); + + SetupBuild(stream.Package); + Debug.Assert(stream.Package.Build != null); + Console.WriteLine("Build:" + stream.Package.Build); + + SetupBranch(stream.Package); + Debug.Assert(stream.Package.Branch != null); + Console.WriteLine("Branch:" + stream.Package.Branch); +#if BIOSHOCK + if (stream.Package.Build == GameBuild.BuildName.Bioshock_Infinite) + { + int unk = stream.ReadInt32(); + } +#endif +#if MKKE + if (stream.Package.Build == GameBuild.BuildName.MKKE) stream.Skip(8); +#endif +#if TRANSFORMERS + if (stream.Package.Build == BuildGeneration.HMS && + stream.LicenseeVersion >= 55) + { + if (stream.LicenseeVersion >= 181) stream.Skip(16); + stream.Skip(4); -#endif - NameCount = stream.ReadInt32(); - NameOffset = stream.ReadInt32(); -#if UE4 - if (stream.UE4Version >= 516 && stream.Package.ContainsEditorData()) - { - LocalizationId = stream.ReadText(); - } - - if (stream.UE4Version >= 459) - { - GatherableTextDataCount = stream.ReadInt32(); - GatherableTextDataOffset = stream.ReadInt32(); - } -#endif - ExportCount = stream.ReadInt32(); - ExportOffset = stream.ReadInt32(); -#if APB - if (stream.Package.Build == GameBuild.BuildName.APB && - stream.LicenseeVersion >= 28) - { - if (stream.LicenseeVersion >= 29) - { - stream.Skip(4); - } - - stream.Skip(20); - } -#endif - ImportCount = stream.ReadInt32(); - ImportOffset = stream.ReadInt32(); - - Console.WriteLine("Names Count:" + NameCount + " Names Offset:" + NameOffset - + " Exports Count:" + ExportCount + " Exports Offset:" + ExportOffset - + " Imports Count:" + ImportCount + " Imports Offset:" + ImportOffset - ); - - if (stream.Version < 68) - { - HeritageCount = stream.ReadInt32(); - Contract.Assert(HeritageCount > 0); - HeritageOffset = stream.ReadInt32(); - return; - } - - if (stream.Version >= VDependsOffset) - { - DependsOffset = stream.ReadInt32(); - //Console.WriteLine("Depends Offset:" + DependsOffset); - } -#if THIEF_DS || DEUSEX_IW || GIGANTIC - if (stream.Package.Build == GameBuild.BuildName.Thief_DS || - stream.Package.Build == GameBuild.BuildName.DeusEx_IW || - stream.Package.Build == GameBuild.BuildName.Gigantic) - { - //stream.Skip( 4 ); - int unknown = stream.ReadInt32(); - Console.WriteLine("Unknown:" + unknown); - } -#endif -#if SPLINTERCELL - if (stream.Package.Build == GameBuild.BuildName.SC1 && - stream.LicenseeVersion >= 12) - { - // compiled-constant: 0xff0adde - stream.Read(out int uStack_10c); - - // An FString converted to an FArray? Concatenating appUserName, appComputerName, appBaseDir, and appTimestamp. - stream.ReadArray(out UArray iStack_fc); - } -#endif -#if BORDERLANDS - if (stream.Package.Build == GameBuild.BuildName.Borderlands) stream.Skip(4); -#endif - if (stream.UE4Version >= 384) - { - StringAssetReferencesCount = stream.ReadInt32(); - StringAssetReferencesOffset = stream.ReadInt32(); - } - - if (stream.UE4Version >= 510) - { - SearchableNamesOffset = stream.ReadInt32(); - } - - if (stream.Version >= VImportExportGuidsOffset && - stream.UE4Version == 0 - // FIXME: Correct the output version of these games instead. -#if BIOSHOCK - && stream.Package.Build != GameBuild.BuildName.Bioshock_Infinite -#endif - ) - { - ImportExportGuidsOffset = stream.ReadInt32(); - ImportGuidsCount = stream.ReadInt32(); - ExportGuidsCount = stream.ReadInt32(); - // Console.WriteLine("ImportExportGuidsOffset:" + ImportExportGuidsOffset + " ImportsGuidCount:" + ImportGuidsCount - // + " ExportsGuid Count:" + ExportGuidsCount); - } -#if TRANSFORMERS - if (stream.Package.Build == BuildGeneration.HMS && - stream.Version >= 535) - { - // ThumbnailTableOffset? But if so, the partial-upgrade must have skipped @AdditionalPackagesToCook - stream.Skip(4); - return; - } -#endif -#if DD2 - // No version check found in the .exe - if (stream.Package.Build == GameBuild.BuildName.DD2 && PackageFlags.HasFlag(PackageFlag.Cooked)) - stream.Skip(4); -#endif - if (stream.Version >= VThumbnailTableOffset -#if GIGANTIC + } +#endif + if (stream.Version >= VHeaderSize) + { + // Offset to the first class(not object) in the package. + HeaderSize = stream.ReadInt32(); + Console.WriteLine("Header Size: " + HeaderSize); + } + + if (stream.Version >= VFolderName) + { + FolderName = stream.ReadText(); + // Console.WriteLine("Folder Name:" + FolderName); + } + + PackageFlags = stream.ReadFlags32(); + Console.WriteLine("Package Flags:" + PackageFlags); +#if HAWKEN || GIGANTIC + if ((stream.Package.Build == GameBuild.BuildName.Hawken || + stream.Package.Build == GameBuild.BuildName.Gigantic) && + stream.LicenseeVersion >= 2) + stream.Skip(4); +#endif + NameCount = stream.ReadInt32(); + NameOffset = stream.ReadInt32(); +#if UE4 + if (stream.UE4Version >= 516 && stream.Package.ContainsEditorData()) + { + LocalizationId = stream.ReadText(); + } + + if (stream.UE4Version >= 459) + { + GatherableTextDataCount = stream.ReadInt32(); + GatherableTextDataOffset = stream.ReadInt32(); + } +#endif + ExportCount = stream.ReadInt32(); + ExportOffset = stream.ReadInt32(); +#if APB + if (stream.Package.Build == GameBuild.BuildName.APB && + stream.LicenseeVersion >= 28) + { + if (stream.LicenseeVersion >= 29) + { + stream.Skip(4); + } + + stream.Skip(20); + } +#endif + ImportCount = stream.ReadInt32(); + ImportOffset = stream.ReadInt32(); + + Console.WriteLine("Names Count:" + NameCount + " Names Offset:" + NameOffset + + " Exports Count:" + ExportCount + " Exports Offset:" + ExportOffset + + " Imports Count:" + ImportCount + " Imports Offset:" + ImportOffset + ); + + if (stream.Version < 68) + { + HeritageCount = stream.ReadInt32(); + Contract.Assert(HeritageCount > 0); + HeritageOffset = stream.ReadInt32(); + return; + } + + if (stream.Version >= VDependsOffset) + { + DependsOffset = stream.ReadInt32(); + //Console.WriteLine("Depends Offset:" + DependsOffset); + } +#if THIEF_DS || DEUSEX_IW || GIGANTIC + if (stream.Package.Build == GameBuild.BuildName.Thief_DS || + stream.Package.Build == GameBuild.BuildName.DeusEx_IW || + stream.Package.Build == GameBuild.BuildName.Gigantic) + { + //stream.Skip( 4 ); + int unknown = stream.ReadInt32(); + Console.WriteLine("Unknown:" + unknown); + } +#endif +#if SPLINTERCELL + if (stream.Package.Build == GameBuild.BuildName.SC1 && + stream.LicenseeVersion >= 12) + { + // compiled-constant: 0xff0adde + stream.Read(out int uStack_10c); + + // An FString converted to an FArray? Concatenating appUserName, appComputerName, appBaseDir, and appTimestamp. + stream.ReadArray(out UArray iStack_fc); + } +#endif +#if BORDERLANDS + if (stream.Package.Build == GameBuild.BuildName.Borderlands) stream.Skip(4); +#endif + if (stream.UE4Version >= 384) + { + StringAssetReferencesCount = stream.ReadInt32(); + StringAssetReferencesOffset = stream.ReadInt32(); + } + + if (stream.UE4Version >= 510) + { + SearchableNamesOffset = stream.ReadInt32(); + } + + if (stream.Version >= VImportExportGuidsOffset && + stream.UE4Version == 0 + // FIXME: Correct the output version of these games instead. +#if BIOSHOCK + && stream.Package.Build != GameBuild.BuildName.Bioshock_Infinite +#endif + ) + { + ImportExportGuidsOffset = stream.ReadInt32(); + ImportGuidsCount = stream.ReadInt32(); + ExportGuidsCount = stream.ReadInt32(); + // Console.WriteLine("ImportExportGuidsOffset:" + ImportExportGuidsOffset + " ImportsGuidCount:" + ImportGuidsCount + // + " ExportsGuid Count:" + ExportGuidsCount); + } +#if TRANSFORMERS + if (stream.Package.Build == BuildGeneration.HMS && + stream.Version >= 535) + { + // ThumbnailTableOffset? But if so, the partial-upgrade must have skipped @AdditionalPackagesToCook + stream.Skip(4); + return; + } +#endif +#if DD2 + // No version check found in the .exe + if (stream.Package.Build == GameBuild.BuildName.DD2 && PackageFlags.HasFlag(PackageFlag.Cooked)) + stream.Skip(4); +#endif + if (stream.Version >= VThumbnailTableOffset +#if GIGANTIC && stream.Package.Build !=GameBuild.BuildName.Gigantic) -#endif - { - ThumbnailTableOffset = stream.ReadInt32(); - // Console.WriteLine("ThumbnailTableOffset:" + ThumbnailTableOffset); - } -#if MKKE - if (stream.Package.Build == GameBuild.BuildName.MKKE) stream.Skip(4); -#endif -#if SPELLBORN - if (stream.Package.Build == GameBuild.BuildName.Spellborn - && stream.Version >= 148) +#endif + { + ThumbnailTableOffset = stream.ReadInt32(); + // Console.WriteLine("ThumbnailTableOffset:" + ThumbnailTableOffset); + } +#if MKKE + if (stream.Package.Build == GameBuild.BuildName.MKKE) stream.Skip(4); +#endif +#if SPELLBORN + if (stream.Package.Build == GameBuild.BuildName.Spellborn + && stream.Version >= 148) goto skipGuid; -#endif - stream.ReadStruct(out Guid); - Console.WriteLine("GUID:" + Guid); - skipGuid: -#if TERA - if (stream.Package.Build == GameBuild.BuildName.Tera) stream.Position -= 4; -#endif -#if MKKE - if (stream.Package.Build != GameBuild.BuildName.MKKE) - { -#endif - int generationCount = stream.ReadInt32(); - Contract.Assert(generationCount >= 0); - Console.WriteLine("Generations Count:" + generationCount); -#if APB - // Guid, however only serialized for the first generation item. - if (stream.Package.Build == GameBuild.BuildName.APB && - stream.LicenseeVersion >= 32) - { - stream.Skip(16); - } -#endif - stream.ReadArray(out Generations, generationCount); - +#endif + stream.ReadStruct(out Guid); + Console.WriteLine("GUID:" + Guid); + skipGuid: +#if TERA + if (stream.Package.Build == GameBuild.BuildName.Tera) stream.Position -= 4; +#endif +#if MKKE + if (stream.Package.Build != GameBuild.BuildName.MKKE) + { +#endif + int generationCount = stream.ReadInt32(); + Contract.Assert(generationCount >= 0); + Console.WriteLine("Generations Count:" + generationCount); +#if APB + // Guid, however only serialized for the first generation item. + if (stream.Package.Build == GameBuild.BuildName.APB && + stream.LicenseeVersion >= 32) + { + stream.Skip(16); + } +#endif + stream.ReadArray(out Generations, generationCount); + foreach (UGenerationTableItem i in Generations) { int index = Generations.IndexOf(i) + 1; // Console.WriteLine($"Generation {index}: \n\tExport Count:" + i.ExportCount + " \n\tName Count:" + i.NameCount + " \n\tNetObjectCount:" + i.NetObjectCount); - } -#if MKKE - } -#endif -#if DNF - if (stream.Package.Build == GameBuild.BuildName.DNF && - stream.Version >= 151) - { - if (PackageFlags.HasFlags(0x20U)) - { - int buildMonth = stream.ReadInt32(); - int buildYear = stream.ReadInt32(); - int buildDay = stream.ReadInt32(); - int buildSeconds = stream.ReadInt32(); - } - - string dnfString = stream.ReadText(); - - // DLC package - if (PackageFlags.HasFlags(0x80U)) - { - // No additional data, just DLC authentication. - } - } -#endif - if (stream.Version >= VEngineVersion && - stream.UE4Version == 0) - { - // The Engine Version this package was created with - EngineVersion = stream.ReadInt32(); - Console.WriteLine("EngineVersion:" + EngineVersion); - } -#if UE4 - if (stream.Package.ContainsEditorData()) - { - if (stream.UE4Version >= 518) - { - stream.ReadStruct(out PersistentGuid); - if (stream.UE4Version < 520) - { - stream.ReadStruct(out OwnerPersistentGuid); - } - } - } - - if (stream.UE4Version >= 336) - { - // EngineVersion - PackageEngineVersion = new PackageFileEngineVersion(); - PackageEngineVersion.Deserialize(stream); - } - - if (stream.UE4Version >= 444) - { - // Compatible EngineVersion - PackageCompatibleEngineVersion = new PackageFileEngineVersion(); - PackageCompatibleEngineVersion.Deserialize(stream); - } -#endif - if (stream.Version >= VCookerVersion && - stream.UE4Version == 0) - { - // The Cooker Version this package was cooked with - CookerVersion = stream.ReadInt32(); - Console.WriteLine("CookerVersion:" + CookerVersion); - } - - // Read compressed info? - if (stream.Version >= VCompression) - { - CompressionFlags = stream.ReadUInt32(); - Console.WriteLine("CompressionFlags:" + CompressionFlags); - stream.ReadArray(out CompressedChunks); - } - - if (stream.Version >= VPackageSource) - { - PackageSource = stream.ReadUInt32(); - Console.WriteLine("PackageSource:" + PackageSource); - } -#if UE4 - if (stream.UE4Version > 0) - return; -#endif - if (stream.Version >= VAdditionalPackagesToCook) - { -#if TRANSFORMERS - if (stream.Package.Build == BuildGeneration.HMS) - { - return; - } -#endif - stream.ReadArray(out AdditionalPackagesToCook); -#if DCUO - if (stream.Package.Build == GameBuild.BuildName.DCUO) - { - var realNameOffset = (int)stream.Position; - Debug.Assert( - realNameOffset <= NameOffset, - "realNameOffset is > the parsed name offset for a DCUO package, we don't know where to go now!" - ); - - int offsetDif = NameOffset - realNameOffset; - NameOffset -= offsetDif; - ImportOffset -= offsetDif; - ExportOffset -= offsetDif; - DependsOffset = 0; // not working - ImportExportGuidsOffset -= offsetDif; - ThumbnailTableOffset -= offsetDif; - } -#endif - } -#if BORDERLANDS - if (stream.Package.Build == GameBuild.BuildName.Battleborn) - { - // FIXME: Package format is being deserialzied incorrectly and fails here. - stream.ReadUInt32(); - return; - } -#endif - if (stream.Version >= VTextureAllocations) - { - // TextureAllocations, TextureTypes - int count = stream.ReadInt32(); - for (var i = 0; i < count; i++) - { - stream.ReadInt32(); - stream.ReadInt32(); - stream.ReadInt32(); - stream.ReadUInt32(); - stream.ReadUInt32(); - int count2 = stream.ReadInt32(); - stream.Skip(count2 * 4); - } - } -#if ROCKETLEAGUE - if (stream.Package.Build == GameBuild.BuildName.RocketLeague - && PackageFlags.HasFlag(PackageFlag.Cooked)) - { - int garbageSize = stream.ReadInt32(); - Debug.WriteLine(garbageSize, "GarbageSize"); - int compressedChunkInfoOffset = stream.ReadInt32(); - Debug.WriteLine(compressedChunkInfoOffset, "CompressedChunkInfoOffset"); - int lastBlockSize = stream.ReadInt32(); - Debug.WriteLine(lastBlockSize, "LastBlockSize"); - Debug.Assert(stream.Position == NameOffset, "There is more data before the NameTable"); - // Data after this is encrypted - } -#endif - } - } - - public PackageFileSummary Summary; - - /// - /// Whether the package was serialized in BigEndian encoding. - /// - public bool IsBigEndianEncoded { get; } - - [Obsolete] - public const int VSIZEPREFIXDEPRECATED = 64; - - [Obsolete] - public const int VINDEXDEPRECATED = 178; - - [Obsolete] - public const int VDLLBIND = 655; - - [Obsolete] - public const int VCLASSGROUP = 789; - - [Obsolete] - public const int VCOOKEDPACKAGES = 277; - - public uint Version => Summary.Version; - - /// - /// For debugging purposes. Change this to override the present Version deserialized from the package. - /// - public static ushort OverrideVersion; - - public ushort LicenseeVersion => Summary.LicenseeVersion; - - /// - /// For debugging purposes. Change this to override the present Version deserialized from the package. - /// - public static ushort OverrideLicenseeVersion; - - /// - /// The bitflags of this package. - /// - [Obsolete("See Summary.PackageFlags")] public uint PackageFlags; - - /// - /// Size of the Header. Basically points to the first Object in the package. - /// - [Obsolete("See Summary.HeaderSize")] - public int HeaderSize => Summary.HeaderSize; - - /// - /// The group the package is associated with in the Content Browser. - /// - [Obsolete("See Summary.FolderName")] public string Group; - - /// - /// The guid of this package. Used to test if the package on a client is equal to the one on a server. - /// - [Obsolete("See Summary.Guid")] - public string GUID => Summary.Guid.ToString(); - - /// - /// List of package generations. - /// - [Obsolete("See Summary.Generations")] - public UArray Generations => Summary.Generations; - - /// - /// The Engine version the package was created with. - /// - [Obsolete("See Summary.EngineVersion")] - public int EngineVersion => Summary.EngineVersion; - - /// - /// The Cooker version the package was cooked with. - /// - [Obsolete("See Summary.CookerVersion")] - public int CookerVersion => Summary.CookerVersion; - - /// - /// The type of compression the package is compressed with. - /// - [Obsolete("See Summary.CompressionFlags")] - public uint CompressionFlags => Summary.CompressionFlags; - - /// - /// List of compressed chunks throughout the package. - /// Null if package version less is than - /// - [Obsolete("See Summary.CompressedChunks")] - public UArray CompressedChunks => Summary.CompressedChunks; - - /// - /// List of unique unreal names. - /// - [PublicAPI] - public List Names { get; private set; } - - /// - /// List of info about exported objects. - /// - [PublicAPI] - public List Exports { get; private set; } - - /// - /// List of info about imported objects. - /// - [PublicAPI] - public List Imports { get; private set; } - - /// - /// List of info about dependency objects. - /// - //public List Dependencies{ get; private set; } - - #region Initialized Members - - /// - /// Class types that should get added to the ObjectsList. - /// - private readonly Dictionary _ClassTypes = new Dictionary(); - - /// - /// List of UObjects that were constructed by function ConstructObjects, later deserialized and linked. - /// - /// Includes Exports and Imports!. - /// - [PublicAPI] - public List Objects { get; private set; } - - [PublicAPI] public NativesTablePackage NTLPackage; - - [Obsolete("See UPackageStream.Decoder", true)] - public IBufferDecoder Decoder; - - #endregion - - #region Constructors - - /// - /// A Collection of flags describing how a package should be initialized. - /// - [Flags] - [Obfuscation(Exclude = true)] - public enum InitFlags : ushort - { - Construct = 0x0001, - Deserialize = 0x0002, - [Obsolete] Import = 0x0004, - Link = 0x0008, - All = RegisterClasses | Construct | Deserialize | Link, - RegisterClasses = 0x0010 - } - - [PublicAPI] - [Obsolete] - public static UnrealPackage DeserializePackage(string packagePath, FileAccess fileAccess = FileAccess.Read) - { - var stream = new UPackageStream(packagePath, FileMode.Open, fileAccess); - var pkg = new UnrealPackage(stream); - pkg.Deserialize(stream); - return pkg; - } - - /// - /// Creates a new instance of the UELib.UnrealPackage class with a PackageStream and name. - /// - /// A loaded UELib.PackageStream. - public UnrealPackage(UPackageStream stream) - { - _FullPackageName = stream.Name; - Stream = stream; - Stream.PostInit(this); - - // File Type - // Signature is tested in UPackageStream - IsBigEndianEncoded = stream.BigEndianCode; - } - - public void Serialize(IUnrealStream stream) - { - throw new NotImplementedException(); - } - - public void Deserialize(UPackageStream stream) - { - Summary = new PackageFileSummary(); - Summary.Deserialize(stream); - BinaryMetaData.AddField(nameof(Summary), Summary, 0, stream.Position); - - // FIXME: For backwards compatibility. - PackageFlags = (uint)Summary.PackageFlags; - Group = Summary.FolderName; - Branch.PostDeserializeSummary(this, stream, ref Summary); - Debug.Assert(Branch.Serializer != null, - "Branch.Serializer cannot be null. Did you forget to initialize the Serializer in PostDeserializeSummary?"); - - // We can't continue without decompressing. - if (Summary.CompressedChunks != null && - Summary.CompressedChunks.Any()) - { - if (Summary.CompressionFlags != 0) - { - return; - } - - // Flags 0? Let's pretend that we no longer possess any chunks. - Summary.CompressedChunks.Clear(); - } -#if TERA - if (Build == GameBuild.BuildName.Tera) Summary.NameCount = Generations.Last().NameCount; -#endif - // Read the name table - if (Summary.NameCount > 0) - { - stream.Seek(Summary.NameOffset, SeekOrigin.Begin); - Names = new List(Summary.NameCount); - for (var i = 0; i < Summary.NameCount; ++i) - { - var nameEntry = new UNameTableItem { Offset = (int)stream.Position, Index = i }; - Branch.Serializer.Deserialize(stream, nameEntry); - nameEntry.Size = (int)(stream.Position - nameEntry.Offset); - Names.Add(nameEntry); - } - - BinaryMetaData.AddField(nameof(Names), Names, Summary.NameOffset, stream.Position - Summary.NameOffset); - -#if SPELLBORN - // WTF were they thinking? Change DRFORTHEWIN to None - if (Build == GameBuild.BuildName.Spellborn - && Names[0].Name == "DRFORTHEWIN") - Names[0].Name = "None"; - // False?? - //Debug.Assert(stream.Position == Summary.ImportsOffset); -#endif - } - - // Read Heritages - if (Summary.HeritageCount > 0) - { - stream.Seek(Summary.HeritageOffset, SeekOrigin.Begin); - stream.ReadArray(out Summary.Heritages, Summary.HeritageCount); - - BinaryMetaData.AddField(nameof(Summary.Heritages), Summary.Heritages, Summary.HeritageOffset, - stream.Position - Summary.HeritageOffset); - } - - // Read Import Table - if (Summary.ImportCount > 0) - { - stream.Seek(Summary.ImportOffset, SeekOrigin.Begin); - Imports = new List(Summary.ImportCount); - for (var i = 0; i < Summary.ImportCount; ++i) - { - var imp = new UImportTableItem { Offset = (int)stream.Position, Index = i, Owner = this }; - Branch.Serializer.Deserialize(stream, imp); - imp.Size = (int)(stream.Position - imp.Offset); - Imports.Add(imp); - } - - BinaryMetaData.AddField(nameof(Imports), Imports, Summary.ImportOffset, - stream.Position - Summary.ImportOffset); - } - - // Read Export Table - if (Summary.ExportCount > 0) - { - stream.Seek(Summary.ExportOffset, SeekOrigin.Begin); - Exports = new List(Summary.ExportCount); - for (var i = 0; i < Summary.ExportCount; ++i) - { - var exp = new UExportTableItem { Offset = (int)stream.Position, Index = i, Owner = this }; - Branch.Serializer.Deserialize(stream, exp); - exp.Size = (int)(stream.Position - exp.Offset); - Exports.Add(exp); - } - - BinaryMetaData.AddField(nameof(Exports), Exports, Summary.ExportOffset, - stream.Position - Summary.ExportOffset); - - if (Summary.DependsOffset > 0) - { - try - { - stream.Seek(Summary.DependsOffset, SeekOrigin.Begin); - int dependsCount = Summary.ExportCount; -#if BIOSHOCK - // FIXME: Version? - if (Build == GameBuild.BuildName.Bioshock_Infinite) - { - dependsCount = stream.ReadInt32(); - } -#endif - var dependsMap = new List(dependsCount); - for (var i = 0; i < dependsCount; ++i) - { - // DependencyList, index to import table - int count = stream.ReadInt32(); // -1 in DCUO? - var imports = new int[count]; - for (var j = 0; j < count; ++j) - { - imports[j] = stream.ReadInt32(); - } - - dependsMap.Add(imports); - } - - BinaryMetaData.AddField(nameof(dependsMap), dependsMap, Summary.DependsOffset, - stream.Position - Summary.DependsOffset); - } - catch (Exception ex) - { - // Errors shouldn't be fatal here because this feature is not necessary for our purposes. - Console.Error.WriteLine("Couldn't parse DependenciesTable"); - Console.Error.WriteLine(ex.ToString()); -#if STRICT - throw new UnrealException("Couldn't parse DependenciesTable", ex); -#endif - } - } - } - - if (Summary.ImportExportGuidsOffset > 0) - { - try - { - for (var i = 0; i < Summary.ImportGuidsCount; ++i) - { - string levelName = stream.ReadText(); - int guidCount = stream.ReadInt32(); - stream.Skip(guidCount * 16); - } - - for (var i = 0; i < Summary.ExportGuidsCount; ++i) - { - stream.ReadStruct(out UGuid objectGuid); - int exportIndex = stream.ReadInt32(); - } - - if (stream.Position != Summary.ImportExportGuidsOffset) - { - BinaryMetaData.AddField("ImportExportGuids", null, Summary.ImportExportGuidsOffset, - stream.Position - Summary.ImportExportGuidsOffset); - } - } - catch (Exception ex) - { - // Errors shouldn't be fatal here because this feature is not necessary for our purposes. - Console.Error.WriteLine("Couldn't parse ImportExportGuidsTable"); - Console.Error.WriteLine(ex.ToString()); -#if STRICT - throw new UnrealException("Couldn't parse ImportExportGuidsTable", ex); -#endif - } - } - - if (Summary.ThumbnailTableOffset != 0) - { - try - { - int thumbnailCount = stream.ReadInt32(); - // TODO: Serialize - BinaryMetaData.AddField("Thumbnails", null, Summary.ThumbnailTableOffset, - stream.Position - Summary.ThumbnailTableOffset); - } - catch (Exception ex) - { - // Errors shouldn't be fatal here because this feature is not necessary for our purposes. - Console.Error.WriteLine("Couldn't parse ThumbnailTable"); - Console.Error.WriteLine(ex.ToString()); -#if STRICT - throw new UnrealException("Couldn't parse ThumbnailTable", ex); -#endif - } - } - - Debug.Assert(stream.Position <= int.MaxValue); - if (Summary.HeaderSize == 0) Summary.HeaderSize = (int)stream.Position; - - Branch.PostDeserializePackage(stream.Package, stream); - } - - /// - /// Constructs all export objects. - /// - /// Initializing rules such as deserializing and/or linking. - [PublicAPI] - public void InitializeExportObjects(InitFlags initFlags = InitFlags.All) - { - Objects = new List(Exports.Count); - foreach (var exp in Exports) CreateObject(exp); - - if ((initFlags & InitFlags.Deserialize) == 0) - return; - - DeserializeObjects(); - if ((initFlags & InitFlags.Link) != 0) LinkObjects(); - } - - /// - /// Constructs all import objects. - /// - /// If TRUE initialize all constructed objects. - [Obsolete("Pending deprecation")] - public void InitializeImportObjects(bool initialize = true) - { - Objects = new List(Imports.Count); - foreach (var imp in Imports) CreateObject(imp); - - if (!initialize) return; - - foreach (var obj in Objects) obj.PostInitialize(); - } - - /// - /// Initializes all the objects that resist in this package as well tries to import deserialized data from imported objects. - /// - /// A collection of initializing flags to notify what should be initialized. - /// InitializePackage( UnrealPackage.InitFlags.All ) - [PublicAPI] - public void InitializePackage(InitFlags initFlags = InitFlags.All) - { - if ((initFlags & InitFlags.RegisterClasses) != 0) RegisterExportedClassTypes(); - - if ((initFlags & InitFlags.Construct) == 0) - { - return; - } - - ConstructObjects(); - if ((initFlags & InitFlags.Deserialize) == 0) - return; - - try - { - DeserializeObjects(); - } - catch (Exception ex) - { - throw new UnrealException("Deserialization", ex); - } - - try - { - if ((initFlags & InitFlags.Link) != 0) LinkObjects(); - } - catch (Exception ex) - { - throw new UnrealException("Linking", ex); - } - } - - /// - /// - /// - public class PackageEventArgs : EventArgs - { - /// - /// Event identification. - /// - public enum Id : byte - { - /// - /// Constructing Export/Import objects. - /// - Construct = 0, - - /// - /// Deserializing objects. - /// - Deserialize = 1, - - /// - /// Importing objects from linked packages. - /// - [Obsolete] Import = 2, - - /// - /// Connecting deserialized object indexes. - /// - Link = 3, - - /// - /// Deserialized a Export/Import object. - /// - Object = 0xFF - } - - /// - /// The event identification. - /// - [PublicAPI] public readonly Id EventId; - - /// - /// Constructs a new event with @eventId. - /// - /// Event identification. - public PackageEventArgs(Id eventId) - { - EventId = eventId; - } - } - - /// - /// - /// - [PublicAPI] - public event PackageEventHandler NotifyPackageEvent; - - private void OnNotifyPackageEvent(PackageEventArgs e) - { - NotifyPackageEvent?.Invoke(this, e); - } - - /// - /// Called when an object is added to the ObjectsList via the AddObject function. - /// - [PublicAPI] - public event NotifyObjectAddedEventHandler NotifyObjectAdded; - - /// - /// Constructs all the objects based on data from _ExportTableList and _ImportTableList, and - /// all constructed objects are added to the _ObjectsList. - /// - private void ConstructObjects() - { - Objects = new List(); - OnNotifyPackageEvent(new PackageEventArgs(PackageEventArgs.Id.Construct)); - foreach (var exp in Exports) - try - { - CreateObject(exp); - } - catch (Exception exc) - { - throw new UnrealException("couldn't create export object for " + exp, exc); - } - - foreach (var imp in Imports) - try - { - CreateObject(imp); - } - catch (Exception exc) - { - throw new UnrealException("couldn't create import object for " + imp, exc); - } - } - - /// - /// Deserializes all exported objects. - /// - private void DeserializeObjects() - { - // Only exports should be deserialized and PostInitialized! - OnNotifyPackageEvent(new PackageEventArgs(PackageEventArgs.Id.Deserialize)); - foreach (var exp in Exports) - { - if (!(exp.Object is UnknownObject || exp.Object.ShouldDeserializeOnDemand)) - // Console.WriteLine( "Deserializing object:" + exp.ObjectName ); - exp.Object.BeginDeserializing(); - - OnNotifyPackageEvent(new PackageEventArgs(PackageEventArgs.Id.Object)); - } - } - - /// - /// Initializes all exported objects. - /// - private void LinkObjects() - { - // Notify that deserializing is done on all objects, now objects can read properties that were dependent on deserializing - OnNotifyPackageEvent(new PackageEventArgs(PackageEventArgs.Id.Link)); - foreach (var exp in Exports) - try - { - if (!(exp.Object is UnknownObject)) exp.Object.PostInitialize(); - } - catch (InvalidCastException) - { - Console.WriteLine("InvalidCastException occurred on object: " + exp.Object); - } - finally - { - OnNotifyPackageEvent(new PackageEventArgs(PackageEventArgs.Id.Object)); - } - } - - private void RegisterExportedClassTypes() - { - var exportedTypes = Assembly.GetExecutingAssembly().GetExportedTypes(); - foreach (var exportedType in exportedTypes) - { - object[] attributes = exportedType.GetCustomAttributes(typeof(UnrealRegisterClassAttribute), false); - if (attributes.Length == 1) AddClassType(exportedType.Name.Substring(1), exportedType); - } - } - - #endregion - - #region Methods - - // Create pseudo objects for imports so that we have non-null references to imports. - private void CreateObject(UImportTableItem item) - { - var classType = GetClassType(item.ClassName); - item.Object = (UObject)Activator.CreateInstance(classType); - AddObject(item.Object, item); - OnNotifyPackageEvent(new PackageEventArgs(PackageEventArgs.Id.Object)); - } - - private void CreateObject(UExportTableItem item) - { - var objectClass = item.Class; - var classType = GetClassType(objectClass != null ? objectClass.ObjectName : "Class"); - // Try one of the "super" classes for unregistered classes. - loop: - if (objectClass != null && classType == typeof(UnknownObject)) - { - switch (objectClass) - { - case UExportTableItem classExp: - var super = classExp.Super; - switch (super) - { - //case UImportTableItem superImport: - // CreateObject(superImport); - // return; - - case UExportTableItem superExport: - objectClass = superExport; - classType = GetClassType(objectClass.ObjectName); - goto loop; - } - - break; - } - } - - item.Object = (UObject)Activator.CreateInstance(classType); - AddObject(item.Object, item); - OnNotifyPackageEvent(new PackageEventArgs(PackageEventArgs.Id.Object)); - } - - private void AddObject(UObject obj, UObjectTableItem table) - { - table.Object = obj; - obj.Package = this; - obj.Table = table; - - Objects.Add(obj); - NotifyObjectAdded?.Invoke(this, new ObjectEventArgs(obj)); - } - - /// - /// Writes the present PackageFlags to disk. HardCoded! - /// Only supports UT2004. - /// - [PublicAPI] - [Obsolete] - public void WritePackageFlags() - { - Stream.Position = 8; - Stream.Writer.Write(PackageFlags); - } - - [PublicAPI] - [Obsolete] - public void RegisterClass(string className, Type classObject) - { - AddClassType(className, classObject); - } - - [PublicAPI] - public void AddClassType(string className, Type classObject) - { - _ClassTypes.Add(className.ToLower(), classObject); - } - - [PublicAPI] - [NotNull] - public Type GetClassType(string className) - { - _ClassTypes.TryGetValue(className.ToLower(), out var classType); - return classType ?? typeof(UnknownObject); - } - - [PublicAPI] - public bool HasClassType(string className) - { - return _ClassTypes.ContainsKey(className.ToLower()); - } - - [PublicAPI] - [Obsolete] - public bool IsRegisteredClass(string className) - { - return HasClassType(className); - } - - /// - /// Returns an Object that resides at the specified ObjectIndex. - /// - /// if index is positive an exported Object will be returned. - /// if index is negative an imported Object will be returned. - /// if index is zero null will be returned. - /// - [PublicAPI] - public UObject GetIndexObject(int objectIndex) - { - return objectIndex < 0 - ? Imports[-objectIndex - 1].Object - : objectIndex > 0 - ? Exports[objectIndex - 1].Object - : null; - } - - [PublicAPI] - public string GetIndexObjectName(int objectIndex) - { - return GetIndexTable(objectIndex).ObjectName; - } - - /// - /// Returns a name that resides at the specified NameIndex. - /// - [PublicAPI] - public string GetIndexName(int nameIndex) - { - return Names[nameIndex].Name; - } - - /// - /// Returns an UnrealTable that resides at the specified TableIndex. - /// - /// if index is positive an ExportTable will be returned. - /// if index is negative an ImportTable will be returned. - /// if index is zero null will be returned. - /// - [PublicAPI] - public UObjectTableItem GetIndexTable(int tableIndex) - { - return tableIndex < 0 - ? Imports[-tableIndex - 1] - : tableIndex > 0 - ? (UObjectTableItem)Exports[tableIndex - 1] - : null; - } - - [PublicAPI] - [Obsolete("See below")] - public UObject FindObject(string objectName, Type classType, bool checkForSubclass = false) - { - var obj = Objects?.Find(o => string.Compare(o.Name, objectName, StringComparison.OrdinalIgnoreCase) == 0 && - (checkForSubclass - ? o.GetType().IsSubclassOf(classType) - : o.GetType() == classType)); - return obj; - } - - [PublicAPI] - public T FindObject(string objectName, bool checkForSubclass = false) where T : UObject - { - var obj = Objects?.Find(o => string.Compare(o.Name, objectName, StringComparison.OrdinalIgnoreCase) == 0 && - (checkForSubclass - ? o.GetType().IsSubclassOf(typeof(T)) - : o.GetType() == typeof(T))); - return obj as T; - } - - [PublicAPI] - public UObject FindObjectByGroup(string objectGroup) - { - if (Objects == null) - { - return null; - } - - string[] groups = objectGroup.Split('.'); - UObject lastObj = null; - for (var i = 0; i < groups.Length; ++i) - { - var obj = Objects.Find(o => - string.Compare(o.Name, groups[i], StringComparison.OrdinalIgnoreCase) == 0 && o.Outer == lastObj); - if (obj != null) - { - lastObj = obj; - } - else - { - lastObj = Objects.Find(o => - string.Compare(o.Name, groups[i], StringComparison.OrdinalIgnoreCase) == 0); - break; - } - } - - return lastObj; - } - - /// - /// If true, the package won't have any editor data such as HideCategories, ScriptText etc. - /// - /// However this condition is not only determined by the package flags property. - /// Thus it is necessary to explicitly indicate this state. - /// - /// Whether package is cooked for consoles. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool IsConsoleCooked() - { - return Summary.PackageFlags.HasFlag(PackageFlag.Cooked) - && CookerPlatform == BuildPlatform.Console; - } - - /// - /// Checks whether this package is marked with @flags. - /// - /// The enum @flag to test. - /// Whether this package is marked with @flag. - [Obsolete("See Summary.PackageFlags.HasFlag")] - public bool HasPackageFlag(PackageFlags flags) - { - return Summary.PackageFlags.HasFlags((uint)flags); - } - - /// - /// Checks whether this package is marked with @flags. - /// - /// The uint @flag to test - /// Whether this package is marked with @flag. - [Obsolete("See Summary.PackageFlags.HasFlag")] - public bool HasPackageFlag(uint flags) - { - return (PackageFlags & flags) != 0; - } - - /// - /// Tests the packageflags of this UELib.UnrealPackage instance whether it is cooked. - /// - /// True if cooked or False if not. - [Obsolete] - public bool IsCooked() - { - return Summary.PackageFlags.HasFlag(PackageFlag.Cooked); - } - - /// - /// Checks for the Map flag in PackageFlags. - /// - /// Whether if this package is a map. - [Obsolete] - public bool IsMap() - { - return Summary.PackageFlags.HasFlag(PackageFlag.ContainsMap); - } - - /// - /// Checks if this package contains code classes. - /// - /// Whether if this package contains code classes. - [Obsolete] - public bool IsScript() - { - return Summary.PackageFlags.HasFlag(PackageFlag.ContainsScript); - } - - /// - /// Checks if this package was built using the debug configuration. - /// - /// Whether if this package was built in debug configuration. - [Obsolete] - public bool IsDebug() - { - return Summary.PackageFlags.HasFlag(PackageFlag.ContainsDebugData); - } - - /// - /// Checks for the Stripped flag in PackageFlags. - /// - /// Whether if this package is stripped. - [Obsolete] - public bool IsStripped() - { - return Summary.PackageFlags.HasFlag(PackageFlag.StrippedSource); - } - - /// - /// Tests the packageflags of this UELib.UnrealPackage instance whether it is encrypted. - /// - /// True if encrypted or False if not. - [Obsolete] - public bool IsEncrypted() - { - return Summary.PackageFlags.HasFlag(PackageFlag.Encrypted); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool ContainsEditorData() - { - return Summary.UE4Version > 0 && !Summary.PackageFlags.HasFlag(PackageFlag.FilterEditorOnly); - } - - #region IBuffered - - public byte[] CopyBuffer() - { - var buff = new byte[HeaderSize]; - Stream.Seek(0, SeekOrigin.Begin); - Stream.Read(buff, 0, HeaderSize); - if (Stream.BigEndianCode) Array.Reverse(buff); - - return buff; - } - - public IUnrealStream GetBuffer() - { - return Stream; - } - - public int GetBufferPosition() - { - return 0; - } - - public int GetBufferSize() - { - return HeaderSize; - } - - public string GetBufferId(bool fullName = false) - { - return fullName ? FullPackageName : PackageName; - } - - #endregion - - /// - public override string ToString() - { - return PackageName; - } - - /// - public void Dispose() - { - if (Objects != null && Objects.Any()) - { - foreach (var obj in Objects) obj.Dispose(); - - Objects.Clear(); - Objects = null; - } - - if (Stream == null) - return; - - Stream.Close(); - Stream = null; - } - - #endregion - } -} + } +#if MKKE + } +#endif +#if DNF + if (stream.Package.Build == GameBuild.BuildName.DNF && + stream.Version >= 151) + { + if (PackageFlags.HasFlags(0x20U)) + { + int buildMonth = stream.ReadInt32(); + int buildYear = stream.ReadInt32(); + int buildDay = stream.ReadInt32(); + int buildSeconds = stream.ReadInt32(); + } + + string dnfString = stream.ReadText(); + + // DLC package + if (PackageFlags.HasFlags(0x80U)) + { + // No additional data, just DLC authentication. + } + } +#endif + if (stream.Version >= VEngineVersion && + stream.UE4Version == 0) + { + // The Engine Version this package was created with + EngineVersion = stream.ReadInt32(); + Console.WriteLine("EngineVersion:" + EngineVersion); + } +#if UE4 + if (stream.Package.ContainsEditorData()) + { + if (stream.UE4Version >= 518) + { + stream.ReadStruct(out PersistentGuid); + if (stream.UE4Version < 520) + { + stream.ReadStruct(out OwnerPersistentGuid); + } + } + } + + if (stream.UE4Version >= 336) + { + // EngineVersion + PackageEngineVersion = new PackageFileEngineVersion(); + PackageEngineVersion.Deserialize(stream); + } + + if (stream.UE4Version >= 444) + { + // Compatible EngineVersion + PackageCompatibleEngineVersion = new PackageFileEngineVersion(); + PackageCompatibleEngineVersion.Deserialize(stream); + } +#endif + if (stream.Version >= VCookerVersion && + stream.UE4Version == 0) + { + // The Cooker Version this package was cooked with + CookerVersion = stream.ReadInt32(); + Console.WriteLine("CookerVersion:" + CookerVersion); + } + + // Read compressed info? + if (stream.Version >= VCompression) + { + CompressionFlags = stream.ReadUInt32(); + Console.WriteLine("CompressionFlags:" + CompressionFlags); + stream.ReadArray(out CompressedChunks); + } + + if (stream.Version >= VPackageSource) + { + PackageSource = stream.ReadUInt32(); + Console.WriteLine("PackageSource:" + PackageSource); + } +#if UE4 + if (stream.UE4Version > 0) + return; +#endif + if (stream.Version >= VAdditionalPackagesToCook) + { +#if TRANSFORMERS + if (stream.Package.Build == BuildGeneration.HMS) + { + return; + } +#endif + stream.ReadArray(out AdditionalPackagesToCook); +#if DCUO + if (stream.Package.Build == GameBuild.BuildName.DCUO) + { + var realNameOffset = (int)stream.Position; + Debug.Assert( + realNameOffset <= NameOffset, + "realNameOffset is > the parsed name offset for a DCUO package, we don't know where to go now!" + ); + + int offsetDif = NameOffset - realNameOffset; + NameOffset -= offsetDif; + ImportOffset -= offsetDif; + ExportOffset -= offsetDif; + DependsOffset = 0; // not working + ImportExportGuidsOffset -= offsetDif; + ThumbnailTableOffset -= offsetDif; + } +#endif + } +#if BORDERLANDS + if (stream.Package.Build == GameBuild.BuildName.Battleborn) + { + // FIXME: Package format is being deserialzied incorrectly and fails here. + stream.ReadUInt32(); + return; + } +#endif + if (stream.Version >= VTextureAllocations) + { + // TextureAllocations, TextureTypes + int count = stream.ReadInt32(); + for (var i = 0; i < count; i++) + { + stream.ReadInt32(); + stream.ReadInt32(); + stream.ReadInt32(); + stream.ReadUInt32(); + stream.ReadUInt32(); + int count2 = stream.ReadInt32(); + stream.Skip(count2 * 4); + } + } +#if ROCKETLEAGUE + if (stream.Package.Build == GameBuild.BuildName.RocketLeague + && PackageFlags.HasFlag(PackageFlag.Cooked)) + { + int garbageSize = stream.ReadInt32(); + Debug.WriteLine(garbageSize, "GarbageSize"); + int compressedChunkInfoOffset = stream.ReadInt32(); + Debug.WriteLine(compressedChunkInfoOffset, "CompressedChunkInfoOffset"); + int lastBlockSize = stream.ReadInt32(); + Debug.WriteLine(lastBlockSize, "LastBlockSize"); + Debug.Assert(stream.Position == NameOffset, "There is more data before the NameTable"); + // Data after this is encrypted + } +#endif + } + } + + public PackageFileSummary Summary; + + /// + /// Whether the package was serialized in BigEndian encoding. + /// + public bool IsBigEndianEncoded { get; } + + [Obsolete] + public const int VSIZEPREFIXDEPRECATED = 64; + + [Obsolete] + public const int VINDEXDEPRECATED = 178; + + [Obsolete] + public const int VDLLBIND = 655; + + [Obsolete] + public const int VCLASSGROUP = 789; + + [Obsolete] + public const int VCOOKEDPACKAGES = 277; + + public uint Version => Summary.Version; + + /// + /// For debugging purposes. Change this to override the present Version deserialized from the package. + /// + public static ushort OverrideVersion; + + public ushort LicenseeVersion => Summary.LicenseeVersion; + + /// + /// For debugging purposes. Change this to override the present Version deserialized from the package. + /// + public static ushort OverrideLicenseeVersion; + + /// + /// The bitflags of this package. + /// + [Obsolete("See Summary.PackageFlags")] public uint PackageFlags; + + /// + /// Size of the Header. Basically points to the first Object in the package. + /// + [Obsolete("See Summary.HeaderSize")] + public int HeaderSize => Summary.HeaderSize; + + /// + /// The group the package is associated with in the Content Browser. + /// + [Obsolete("See Summary.FolderName")] public string Group; + + /// + /// The guid of this package. Used to test if the package on a client is equal to the one on a server. + /// + [Obsolete("See Summary.Guid")] + public string GUID => Summary.Guid.ToString(); + + /// + /// List of package generations. + /// + [Obsolete("See Summary.Generations")] + public UArray Generations => Summary.Generations; + + /// + /// The Engine version the package was created with. + /// + [Obsolete("See Summary.EngineVersion")] + public int EngineVersion => Summary.EngineVersion; + + /// + /// The Cooker version the package was cooked with. + /// + [Obsolete("See Summary.CookerVersion")] + public int CookerVersion => Summary.CookerVersion; + + /// + /// The type of compression the package is compressed with. + /// + [Obsolete("See Summary.CompressionFlags")] + public uint CompressionFlags => Summary.CompressionFlags; + + /// + /// List of compressed chunks throughout the package. + /// Null if package version less is than + /// + [Obsolete("See Summary.CompressedChunks")] + public UArray CompressedChunks => Summary.CompressedChunks; + + /// + /// List of unique unreal names. + /// + [PublicAPI] + public List Names { get; private set; } + + /// + /// List of info about exported objects. + /// + [PublicAPI] + public List Exports { get; private set; } + + /// + /// List of info about imported objects. + /// + [PublicAPI] + public List Imports { get; private set; } + + /// + /// List of info about dependency objects. + /// + //public List Dependencies{ get; private set; } + + #region Initialized Members + + /// + /// Class types that should get added to the ObjectsList. + /// + private readonly Dictionary _ClassTypes = new Dictionary(); + + /// + /// List of UObjects that were constructed by function ConstructObjects, later deserialized and linked. + /// + /// Includes Exports and Imports!. + /// + [PublicAPI] + public List Objects { get; private set; } + + [PublicAPI] public NativesTablePackage NTLPackage; + + [Obsolete("See UPackageStream.Decoder", true)] + public IBufferDecoder Decoder; + + #endregion + + #region Constructors + + /// + /// A Collection of flags describing how a package should be initialized. + /// + [Flags] + [Obfuscation(Exclude = true)] + public enum InitFlags : ushort + { + Construct = 0x0001, + Deserialize = 0x0002, + [Obsolete] Import = 0x0004, + Link = 0x0008, + All = RegisterClasses | Construct | Deserialize | Link, + RegisterClasses = 0x0010 + } + + [PublicAPI] + [Obsolete] + public static UnrealPackage DeserializePackage(string packagePath, FileAccess fileAccess = FileAccess.Read) + { + var stream = new UPackageStream(packagePath, FileMode.Open, fileAccess); + var pkg = new UnrealPackage(stream); + pkg.Deserialize(stream); + return pkg; + } + + /// + /// Creates a new instance of the UELib.UnrealPackage class with a PackageStream and name. + /// + /// A loaded UELib.PackageStream. + public UnrealPackage(UPackageStream stream) + { + _FullPackageName = stream.Name; + Stream = stream; + Stream.PostInit(this); + + // File Type + // Signature is tested in UPackageStream + IsBigEndianEncoded = stream.BigEndianCode; + } + + public void Serialize(IUnrealStream stream) + { + throw new NotImplementedException(); + } + + public void Deserialize(UPackageStream stream) + { + Summary = new PackageFileSummary(); + Summary.Deserialize(stream); + BinaryMetaData.AddField(nameof(Summary), Summary, 0, stream.Position); + + // FIXME: For backwards compatibility. + PackageFlags = (uint)Summary.PackageFlags; + Group = Summary.FolderName; + Branch.PostDeserializeSummary(this, stream, ref Summary); + Debug.Assert(Branch.Serializer != null, + "Branch.Serializer cannot be null. Did you forget to initialize the Serializer in PostDeserializeSummary?"); + + // We can't continue without decompressing. + if (Summary.CompressedChunks != null && + Summary.CompressedChunks.Any()) + { + if (Summary.CompressionFlags != 0) + { + return; + } + + // Flags 0? Let's pretend that we no longer possess any chunks. + Summary.CompressedChunks.Clear(); + } +#if TERA + if (Build == GameBuild.BuildName.Tera) Summary.NameCount = Generations.Last().NameCount; +#endif + // Read the name table + if (Summary.NameCount > 0) + { + stream.Seek(Summary.NameOffset, SeekOrigin.Begin); + Names = new List(Summary.NameCount); + for (var i = 0; i < Summary.NameCount; ++i) + { + var nameEntry = new UNameTableItem { Offset = (int)stream.Position, Index = i }; + Branch.Serializer.Deserialize(stream, nameEntry); + nameEntry.Size = (int)(stream.Position - nameEntry.Offset); + Names.Add(nameEntry); + } + + BinaryMetaData.AddField(nameof(Names), Names, Summary.NameOffset, stream.Position - Summary.NameOffset); + +#if SPELLBORN + // WTF were they thinking? Change DRFORTHEWIN to None + if (Build == GameBuild.BuildName.Spellborn + && Names[0].Name == "DRFORTHEWIN") + Names[0].Name = "None"; + // False?? + //Debug.Assert(stream.Position == Summary.ImportsOffset); +#endif + } + + // Read Heritages + if (Summary.HeritageCount > 0) + { + stream.Seek(Summary.HeritageOffset, SeekOrigin.Begin); + stream.ReadArray(out Summary.Heritages, Summary.HeritageCount); + + BinaryMetaData.AddField(nameof(Summary.Heritages), Summary.Heritages, Summary.HeritageOffset, + stream.Position - Summary.HeritageOffset); + } + + // Read Import Table + if (Summary.ImportCount > 0) + { + stream.Seek(Summary.ImportOffset, SeekOrigin.Begin); + Imports = new List(Summary.ImportCount); + for (var i = 0; i < Summary.ImportCount; ++i) + { + var imp = new UImportTableItem { Offset = (int)stream.Position, Index = i, Owner = this }; + Branch.Serializer.Deserialize(stream, imp); + imp.Size = (int)(stream.Position - imp.Offset); + Imports.Add(imp); + } + + BinaryMetaData.AddField(nameof(Imports), Imports, Summary.ImportOffset, + stream.Position - Summary.ImportOffset); + } + + // Read Export Table + if (Summary.ExportCount > 0) + { + stream.Seek(Summary.ExportOffset, SeekOrigin.Begin); + Exports = new List(Summary.ExportCount); + for (var i = 0; i < Summary.ExportCount; ++i) + { + var exp = new UExportTableItem { Offset = (int)stream.Position, Index = i, Owner = this }; + Branch.Serializer.Deserialize(stream, exp); + exp.Size = (int)(stream.Position - exp.Offset); + Exports.Add(exp); + } + + BinaryMetaData.AddField(nameof(Exports), Exports, Summary.ExportOffset, + stream.Position - Summary.ExportOffset); + + if (Summary.DependsOffset > 0) + { + try + { + stream.Seek(Summary.DependsOffset, SeekOrigin.Begin); + int dependsCount = Summary.ExportCount; +#if BIOSHOCK + // FIXME: Version? + if (Build == GameBuild.BuildName.Bioshock_Infinite) + { + dependsCount = stream.ReadInt32(); + } +#endif + var dependsMap = new List(dependsCount); + for (var i = 0; i < dependsCount; ++i) + { + // DependencyList, index to import table + int count = stream.ReadInt32(); // -1 in DCUO? + var imports = new int[count]; + for (var j = 0; j < count; ++j) + { + imports[j] = stream.ReadInt32(); + } + + dependsMap.Add(imports); + } + + BinaryMetaData.AddField(nameof(dependsMap), dependsMap, Summary.DependsOffset, + stream.Position - Summary.DependsOffset); + } + catch (Exception ex) + { + // Errors shouldn't be fatal here because this feature is not necessary for our purposes. + Console.Error.WriteLine("Couldn't parse DependenciesTable"); + Console.Error.WriteLine(ex.ToString()); +#if STRICT + throw new UnrealException("Couldn't parse DependenciesTable", ex); +#endif + } + } + } + + if (Summary.ImportExportGuidsOffset > 0) + { + try + { + for (var i = 0; i < Summary.ImportGuidsCount; ++i) + { + string levelName = stream.ReadText(); + int guidCount = stream.ReadInt32(); + stream.Skip(guidCount * 16); + } + + for (var i = 0; i < Summary.ExportGuidsCount; ++i) + { + stream.ReadStruct(out UGuid objectGuid); + int exportIndex = stream.ReadInt32(); + } + + if (stream.Position != Summary.ImportExportGuidsOffset) + { + BinaryMetaData.AddField("ImportExportGuids", null, Summary.ImportExportGuidsOffset, + stream.Position - Summary.ImportExportGuidsOffset); + } + } + catch (Exception ex) + { + // Errors shouldn't be fatal here because this feature is not necessary for our purposes. + Console.Error.WriteLine("Couldn't parse ImportExportGuidsTable"); + Console.Error.WriteLine(ex.ToString()); +#if STRICT + throw new UnrealException("Couldn't parse ImportExportGuidsTable", ex); +#endif + } + } + + if (Summary.ThumbnailTableOffset != 0) + { + try + { + int thumbnailCount = stream.ReadInt32(); + // TODO: Serialize + BinaryMetaData.AddField("Thumbnails", null, Summary.ThumbnailTableOffset, + stream.Position - Summary.ThumbnailTableOffset); + } + catch (Exception ex) + { + // Errors shouldn't be fatal here because this feature is not necessary for our purposes. + Console.Error.WriteLine("Couldn't parse ThumbnailTable"); + Console.Error.WriteLine(ex.ToString()); +#if STRICT + throw new UnrealException("Couldn't parse ThumbnailTable", ex); +#endif + } + } + + Debug.Assert(stream.Position <= int.MaxValue); + if (Summary.HeaderSize == 0) Summary.HeaderSize = (int)stream.Position; + + Branch.PostDeserializePackage(stream.Package, stream); + } + + /// + /// Constructs all export objects. + /// + /// Initializing rules such as deserializing and/or linking. + [PublicAPI] + public void InitializeExportObjects(InitFlags initFlags = InitFlags.All) + { + Objects = new List(Exports.Count); + foreach (var exp in Exports) CreateObject(exp); + + if ((initFlags & InitFlags.Deserialize) == 0) + return; + + DeserializeObjects(); + if ((initFlags & InitFlags.Link) != 0) LinkObjects(); + } + + /// + /// Constructs all import objects. + /// + /// If TRUE initialize all constructed objects. + [Obsolete("Pending deprecation")] + public void InitializeImportObjects(bool initialize = true) + { + Objects = new List(Imports.Count); + foreach (var imp in Imports) CreateObject(imp); + + if (!initialize) return; + + foreach (var obj in Objects) obj.PostInitialize(); + } + + /// + /// Initializes all the objects that resist in this package as well tries to import deserialized data from imported objects. + /// + /// A collection of initializing flags to notify what should be initialized. + /// InitializePackage( UnrealPackage.InitFlags.All ) + [PublicAPI] + public void InitializePackage(InitFlags initFlags = InitFlags.All) + { + if ((initFlags & InitFlags.RegisterClasses) != 0) RegisterExportedClassTypes(); + + if ((initFlags & InitFlags.Construct) == 0) + { + return; + } + + ConstructObjects(); + if ((initFlags & InitFlags.Deserialize) == 0) + return; + + try + { + DeserializeObjects(); + } + catch (Exception ex) + { + throw new UnrealException("Deserialization", ex); + } + + try + { + if ((initFlags & InitFlags.Link) != 0) LinkObjects(); + } + catch (Exception ex) + { + throw new UnrealException("Linking", ex); + } + } + + /// + /// + /// + public class PackageEventArgs : EventArgs + { + /// + /// Event identification. + /// + public enum Id : byte + { + /// + /// Constructing Export/Import objects. + /// + Construct = 0, + + /// + /// Deserializing objects. + /// + Deserialize = 1, + + /// + /// Importing objects from linked packages. + /// + [Obsolete] Import = 2, + + /// + /// Connecting deserialized object indexes. + /// + Link = 3, + + /// + /// Deserialized a Export/Import object. + /// + Object = 0xFF + } + + /// + /// The event identification. + /// + [PublicAPI] public readonly Id EventId; + + /// + /// Constructs a new event with @eventId. + /// + /// Event identification. + public PackageEventArgs(Id eventId) + { + EventId = eventId; + } + } + + /// + /// + /// + [PublicAPI] + public event PackageEventHandler NotifyPackageEvent; + + private void OnNotifyPackageEvent(PackageEventArgs e) + { + NotifyPackageEvent?.Invoke(this, e); + } + + /// + /// Called when an object is added to the ObjectsList via the AddObject function. + /// + [PublicAPI] + public event NotifyObjectAddedEventHandler NotifyObjectAdded; + + /// + /// Constructs all the objects based on data from _ExportTableList and _ImportTableList, and + /// all constructed objects are added to the _ObjectsList. + /// + private void ConstructObjects() + { + Objects = new List(); + OnNotifyPackageEvent(new PackageEventArgs(PackageEventArgs.Id.Construct)); + foreach (var exp in Exports) + try + { + CreateObject(exp); + } + catch (Exception exc) + { + throw new UnrealException("couldn't create export object for " + exp, exc); + } + + foreach (var imp in Imports) + try + { + CreateObject(imp); + } + catch (Exception exc) + { + throw new UnrealException("couldn't create import object for " + imp, exc); + } + } + + /// + /// Deserializes all exported objects. + /// + private void DeserializeObjects() + { + // Only exports should be deserialized and PostInitialized! + OnNotifyPackageEvent(new PackageEventArgs(PackageEventArgs.Id.Deserialize)); + foreach (var exp in Exports) + { + if (!(exp.Object is UnknownObject || exp.Object.ShouldDeserializeOnDemand)) + // Console.WriteLine( "Deserializing object:" + exp.ObjectName ); + exp.Object.BeginDeserializing(); + + OnNotifyPackageEvent(new PackageEventArgs(PackageEventArgs.Id.Object)); + } + } + + /// + /// Initializes all exported objects. + /// + private void LinkObjects() + { + // Notify that deserializing is done on all objects, now objects can read properties that were dependent on deserializing + OnNotifyPackageEvent(new PackageEventArgs(PackageEventArgs.Id.Link)); + foreach (var exp in Exports) + try + { + if (!(exp.Object is UnknownObject)) exp.Object.PostInitialize(); + } + catch (InvalidCastException) + { + Console.WriteLine("InvalidCastException occurred on object: " + exp.Object); + } + finally + { + OnNotifyPackageEvent(new PackageEventArgs(PackageEventArgs.Id.Object)); + } + } + + private void RegisterExportedClassTypes() + { + var exportedTypes = Assembly.GetExecutingAssembly().GetExportedTypes(); + foreach (var exportedType in exportedTypes) + { + object[] attributes = exportedType.GetCustomAttributes(typeof(UnrealRegisterClassAttribute), false); + if (attributes.Length == 1) AddClassType(exportedType.Name.Substring(1), exportedType); + } + } + + #endregion + + #region Methods + + // Create pseudo objects for imports so that we have non-null references to imports. + private void CreateObject(UImportTableItem item) + { + var classType = GetClassType(item.ClassName); + item.Object = (UObject)Activator.CreateInstance(classType); + AddObject(item.Object, item); + OnNotifyPackageEvent(new PackageEventArgs(PackageEventArgs.Id.Object)); + } + + private void CreateObject(UExportTableItem item) + { + var objectClass = item.Class; + var classType = GetClassType(objectClass != null ? objectClass.ObjectName : "Class"); + // Try one of the "super" classes for unregistered classes. + loop: + if (objectClass != null && classType == typeof(UnknownObject)) + { + switch (objectClass) + { + case UExportTableItem classExp: + var super = classExp.Super; + switch (super) + { + //case UImportTableItem superImport: + // CreateObject(superImport); + // return; + + case UExportTableItem superExport: + objectClass = superExport; + classType = GetClassType(objectClass.ObjectName); + goto loop; + } + + break; + } + } + + item.Object = (UObject)Activator.CreateInstance(classType); + AddObject(item.Object, item); + OnNotifyPackageEvent(new PackageEventArgs(PackageEventArgs.Id.Object)); + } + + private void AddObject(UObject obj, UObjectTableItem table) + { + table.Object = obj; + obj.Package = this; + obj.Table = table; + + Objects.Add(obj); + NotifyObjectAdded?.Invoke(this, new ObjectEventArgs(obj)); + } + + /// + /// Writes the present PackageFlags to disk. HardCoded! + /// Only supports UT2004. + /// + [PublicAPI] + [Obsolete] + public void WritePackageFlags() + { + Stream.Position = 8; + Stream.Writer.Write(PackageFlags); + } + + [PublicAPI] + [Obsolete] + public void RegisterClass(string className, Type classObject) + { + AddClassType(className, classObject); + } + + [PublicAPI] + public void AddClassType(string className, Type classObject) + { + _ClassTypes.Add(className.ToLower(), classObject); + } + + [PublicAPI] + [NotNull] + public Type GetClassType(string className) + { + _ClassTypes.TryGetValue(className.ToLower(), out var classType); + return classType ?? typeof(UnknownObject); + } + + [PublicAPI] + public bool HasClassType(string className) + { + return _ClassTypes.ContainsKey(className.ToLower()); + } + + [PublicAPI] + [Obsolete] + public bool IsRegisteredClass(string className) + { + return HasClassType(className); + } + + /// + /// Returns an Object that resides at the specified ObjectIndex. + /// + /// if index is positive an exported Object will be returned. + /// if index is negative an imported Object will be returned. + /// if index is zero null will be returned. + /// + [PublicAPI] + public UObject GetIndexObject(int objectIndex) + { + return objectIndex < 0 + ? Imports[-objectIndex - 1].Object + : objectIndex > 0 + ? Exports[objectIndex - 1].Object + : null; + } + + [PublicAPI] + public string GetIndexObjectName(int objectIndex) + { + return GetIndexTable(objectIndex).ObjectName; + } + + /// + /// Returns a name that resides at the specified NameIndex. + /// + [PublicAPI] + public string GetIndexName(int nameIndex) + { + return Names[nameIndex].Name; + } + + /// + /// Returns an UnrealTable that resides at the specified TableIndex. + /// + /// if index is positive an ExportTable will be returned. + /// if index is negative an ImportTable will be returned. + /// if index is zero null will be returned. + /// + [PublicAPI] + public UObjectTableItem GetIndexTable(int tableIndex) + { + return tableIndex < 0 + ? Imports[-tableIndex - 1] + : tableIndex > 0 + ? (UObjectTableItem)Exports[tableIndex - 1] + : null; + } + + [PublicAPI] + [Obsolete("See below")] + public UObject FindObject(string objectName, Type classType, bool checkForSubclass = false) + { + var obj = Objects?.Find(o => string.Compare(o.Name, objectName, StringComparison.OrdinalIgnoreCase) == 0 && + (checkForSubclass + ? o.GetType().IsSubclassOf(classType) + : o.GetType() == classType)); + return obj; + } + + [PublicAPI] + public T FindObject(string objectName, bool checkForSubclass = false) where T : UObject + { + var obj = Objects?.Find(o => string.Compare(o.Name, objectName, StringComparison.OrdinalIgnoreCase) == 0 && + (checkForSubclass + ? o.GetType().IsSubclassOf(typeof(T)) + : o.GetType() == typeof(T))); + return obj as T; + } + + [PublicAPI] + public UObject FindObjectByGroup(string objectGroup) + { + if (Objects == null) + { + return null; + } + + string[] groups = objectGroup.Split('.'); + UObject lastObj = null; + for (var i = 0; i < groups.Length; ++i) + { + var obj = Objects.Find(o => + string.Compare(o.Name, groups[i], StringComparison.OrdinalIgnoreCase) == 0 && o.Outer == lastObj); + if (obj != null) + { + lastObj = obj; + } + else + { + lastObj = Objects.Find(o => + string.Compare(o.Name, groups[i], StringComparison.OrdinalIgnoreCase) == 0); + break; + } + } + + return lastObj; + } + + /// + /// If true, the package won't have any editor data such as HideCategories, ScriptText etc. + /// + /// However this condition is not only determined by the package flags property. + /// Thus it is necessary to explicitly indicate this state. + /// + /// Whether package is cooked for consoles. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool IsConsoleCooked() + { + return Summary.PackageFlags.HasFlag(PackageFlag.Cooked) + && CookerPlatform == BuildPlatform.Console; + } + + /// + /// Checks whether this package is marked with @flags. + /// + /// The enum @flag to test. + /// Whether this package is marked with @flag. + [Obsolete("See Summary.PackageFlags.HasFlag")] + public bool HasPackageFlag(PackageFlags flags) + { + return Summary.PackageFlags.HasFlags((uint)flags); + } + + /// + /// Checks whether this package is marked with @flags. + /// + /// The uint @flag to test + /// Whether this package is marked with @flag. + [Obsolete("See Summary.PackageFlags.HasFlag")] + public bool HasPackageFlag(uint flags) + { + return (PackageFlags & flags) != 0; + } + + /// + /// Tests the packageflags of this UELib.UnrealPackage instance whether it is cooked. + /// + /// True if cooked or False if not. + [Obsolete] + public bool IsCooked() + { + return Summary.PackageFlags.HasFlag(PackageFlag.Cooked); + } + + /// + /// Checks for the Map flag in PackageFlags. + /// + /// Whether if this package is a map. + [Obsolete] + public bool IsMap() + { + return Summary.PackageFlags.HasFlag(PackageFlag.ContainsMap); + } + + /// + /// Checks if this package contains code classes. + /// + /// Whether if this package contains code classes. + [Obsolete] + public bool IsScript() + { + return Summary.PackageFlags.HasFlag(PackageFlag.ContainsScript); + } + + /// + /// Checks if this package was built using the debug configuration. + /// + /// Whether if this package was built in debug configuration. + [Obsolete] + public bool IsDebug() + { + return Summary.PackageFlags.HasFlag(PackageFlag.ContainsDebugData); + } + + /// + /// Checks for the Stripped flag in PackageFlags. + /// + /// Whether if this package is stripped. + [Obsolete] + public bool IsStripped() + { + return Summary.PackageFlags.HasFlag(PackageFlag.StrippedSource); + } + + /// + /// Tests the packageflags of this UELib.UnrealPackage instance whether it is encrypted. + /// + /// True if encrypted or False if not. + [Obsolete] + public bool IsEncrypted() + { + return Summary.PackageFlags.HasFlag(PackageFlag.Encrypted); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool ContainsEditorData() + { + return Summary.UE4Version > 0 && !Summary.PackageFlags.HasFlag(PackageFlag.FilterEditorOnly); + } + + #region IBuffered + + public byte[] CopyBuffer() + { + var buff = new byte[HeaderSize]; + Stream.Seek(0, SeekOrigin.Begin); + Stream.Read(buff, 0, HeaderSize); + if (Stream.BigEndianCode) Array.Reverse(buff); + + return buff; + } + + public IUnrealStream GetBuffer() + { + return Stream; + } + + public int GetBufferPosition() + { + return 0; + } + + public int GetBufferSize() + { + return HeaderSize; + } + + public string GetBufferId(bool fullName = false) + { + return fullName ? FullPackageName : PackageName; + } + + #endregion + + /// + public override string ToString() + { + return PackageName; + } + + /// + public void Dispose() + { + if (Objects != null && Objects.Any()) + { + foreach (var obj in Objects) obj.Dispose(); + + Objects.Clear(); + Objects = null; + } + + if (Stream == null) + return; + + Stream.Close(); + Stream = null; + } + + #endregion + } +} From afeb419811d9556b2766ccac1321ddbceca47d87 Mon Sep 17 00:00:00 2001 From: Eliot Date: Sun, 5 May 2024 08:04:45 +0200 Subject: [PATCH 24/50] (Gigantic) Another new primitive cast. --- src/Core/Tokens/CastTokens.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Core/Tokens/CastTokens.cs b/src/Core/Tokens/CastTokens.cs index ba63600d..c624ab8c 100644 --- a/src/Core/Tokens/CastTokens.cs +++ b/src/Core/Tokens/CastTokens.cs @@ -113,9 +113,10 @@ public override string Decompile() switch ((uint)CastOpCode) { case 0x32: + case 0x33: case 0x34: // FIXME: Unknown format - castTypeName = "JsonRef"; + castTypeName = $"JsonRef{(uint)CastOpCode:X}"; break; } } From 05c378e1d708589972ca6134a4210a4cc52521d0 Mon Sep 17 00:00:00 2001 From: Eliot Date: Sun, 5 May 2024 08:05:21 +0200 Subject: [PATCH 25/50] Fix for cases where class is null, let's fallback to the deprecated GetClassName() for now! --- src/Core/Classes/UDefaultProperty.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Core/Classes/UDefaultProperty.cs b/src/Core/Classes/UDefaultProperty.cs index 68a77d13..2b3808ee 100644 --- a/src/Core/Classes/UDefaultProperty.cs +++ b/src/Core/Classes/UDefaultProperty.cs @@ -627,8 +627,9 @@ private string DeserializeDefaultPropertyValue(PropertyType type, ref Deserializ break; } - Contract.Assert(jsonObject.Class != null); - propertyValue = $"JsonRef<{jsonObject.Class?.GetFriendlyType()}>'{jsonObjectName}'"; + // !!! Could be null for imports + //Contract.Assert(jsonObject.Class != null); + propertyValue = $"JsonRef<{jsonObject.GetClassName()}>'{jsonObjectName}'"; break; } #endif From 3896b0b87fa66d6f678010db7ee7537dabec996b Mon Sep 17 00:00:00 2001 From: Eliot Date: Sun, 5 May 2024 08:06:40 +0200 Subject: [PATCH 26/50] Fix: better solution to detect whether a component or unknown object is actually a "template" --- src/Core/Classes/UObject.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Core/Classes/UObject.cs b/src/Core/Classes/UObject.cs index 4fc69f53..13f4cbc9 100644 --- a/src/Core/Classes/UObject.cs +++ b/src/Core/Classes/UObject.cs @@ -333,7 +333,7 @@ protected virtual void Deserialize() // HACK: Ugly work around for unregistered component classes... // Simply for checking for the parent's class is not reliable without importing objects. - case UnknownObject _ when _Buffer.Length >= 12 && GetClassName().EndsWith("Component"): + case UnknownObject _ when _Buffer.Length >= 12 && IsTemplate(): { var fakeComponent = new UComponent(); DeserializeTemplate(fakeComponent); @@ -525,6 +525,7 @@ public bool IsClassType(string className) /// /// Macro for getting a object instance by index. /// + [Obsolete("To be deprecated", true)] protected UObject GetIndexObject(int index) { return Package.GetIndexObject(index); From d04d8b137acf87a1d2fec0c15f5d59418554b762 Mon Sep 17 00:00:00 2001 From: Eliot Date: Sun, 5 May 2024 08:07:14 +0200 Subject: [PATCH 27/50] Fix fallback for deprecated ClassName so that "UE Explorer" can pickup content again. --- src/Core/Tables/UExportTableItem.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Core/Tables/UExportTableItem.cs b/src/Core/Tables/UExportTableItem.cs index 7bc7a608..be3ed085 100644 --- a/src/Core/Tables/UExportTableItem.cs +++ b/src/Core/Tables/UExportTableItem.cs @@ -61,6 +61,10 @@ public int ArchetypeIndex [Obsolete] protected override int __ClassIndex => _ClassIndex; + [Obsolete] + [NotNull] + protected override string __ClassName => Class?.ObjectName ?? "Class"; + [Obsolete("Use Super"), Browsable(false)] public UObjectTableItem SuperTable => Owner.GetIndexTable(_SuperIndex); [Obsolete("Use Super?.ObjectName"), Browsable(false)] public string SuperName From dd5efc3ca0ef5fe60e4788a2a318e8c135de5a11 Mon Sep 17 00:00:00 2001 From: UnDrew Date: Mon, 13 May 2024 20:53:41 +0300 Subject: [PATCH 28/50] Fix for UStaticMeshComponent extending UModelComponent instead of UMeshComponent --- src/Engine/Classes/UMeshComponent.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Engine/Classes/UMeshComponent.cs b/src/Engine/Classes/UMeshComponent.cs index 3c5dd615..8da7bda1 100644 --- a/src/Engine/Classes/UMeshComponent.cs +++ b/src/Engine/Classes/UMeshComponent.cs @@ -25,7 +25,7 @@ public class USkeletalMeshComponent : UMeshComponent /// [UnrealRegisterClass] [BuildGeneration(BuildGeneration.UE3)] - public class UStaticMeshComponent : UModelComponent + public class UStaticMeshComponent : UMeshComponent { } } From dcce486dce8c509783eddb577d75a0a776ef2339 Mon Sep 17 00:00:00 2001 From: Eliot Date: Wed, 15 May 2024 15:23:26 +0200 Subject: [PATCH 29/50] #80; Add "EndWar" and override the version with 220 --- src/Eliot.UELib.csproj | 2 +- src/UnrealPackage.cs | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/Eliot.UELib.csproj b/src/Eliot.UELib.csproj index 7916d7ca..78fd52b3 100644 --- a/src/Eliot.UELib.csproj +++ b/src/Eliot.UELib.csproj @@ -1,6 +1,6 @@ - DECOMPILE;BINARYMETADATA;Forms;UE1;UE2;UE3;UE4;VENGEANCE;SWAT4;UNREAL2;INFINITYBLADE;BORDERLANDS2;GOW2;APB;SPECIALFORCE2;XIII;SINGULARITY;THIEF_DS;DEUSEX_IW;BORDERLANDS;MIRRORSEDGE;BIOSHOCK;HAWKEN;UT;DISHONORED;REMEMBERME;ALPHAPROTOCOL;VANGUARD;TERA;MKKE;TRANSFORMERS;XCOM2;DD2;DCUO;AA2;SPELLBORN;BATMAN;MOH;ROCKETLEAGUE;DNF;LSGAME;UNDYING;HP;DEVASTATION;BATTLEBORN;SPLINTERCELL;AHIT;GIGANTIC + DECOMPILE;BINARYMETADATA;Forms;UE1;UE2;UE3;UE4;VENGEANCE;SWAT4;UNREAL2;INFINITYBLADE;BORDERLANDS2;GOW2;APB;SPECIALFORCE2;XIII;SINGULARITY;THIEF_DS;DEUSEX_IW;BORDERLANDS;MIRRORSEDGE;BIOSHOCK;HAWKEN;UT;DISHONORED;REMEMBERME;ALPHAPROTOCOL;VANGUARD;TERA;MKKE;TRANSFORMERS;XCOM2;DD2;DCUO;AA2;SPELLBORN;BATMAN;MOH;ROCKETLEAGUE;DNF;LSGAME;UNDYING;HP;DEVASTATION;BATTLEBORN;SPLINTERCELL;AHIT;GIGANTIC;ENDWAR net48 Library UELib diff --git a/src/UnrealPackage.cs b/src/UnrealPackage.cs index 78d07053..699d5767 100644 --- a/src/UnrealPackage.cs +++ b/src/UnrealPackage.cs @@ -331,7 +331,14 @@ public enum BuildName /// [Build(159, 29u, BuildGeneration.UE2_5)] Spellborn, - + + /// + /// EndWar + /// + /// 369/006 + /// + [Build(329, 0)] [OverridePackageVersion(220)] EndWar, + /// /// Standard /// From f355b42a30a6be51734e30e1671acc1009f1fc97 Mon Sep 17 00:00:00 2001 From: Eliot Date: Wed, 15 May 2024 15:29:27 +0200 Subject: [PATCH 30/50] Fix ComponentMap deserialization for 2xx packages; #80 --- src/Branch/PackageObjectLegacyVersion.cs | 6 ++++-- src/Core/Classes/UObject.cs | 2 +- src/Core/Tables/UExportTableItem.cs | 20 +++++++++++++------- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/src/Branch/PackageObjectLegacyVersion.cs b/src/Branch/PackageObjectLegacyVersion.cs index b685566f..5942de3c 100644 --- a/src/Branch/PackageObjectLegacyVersion.cs +++ b/src/Branch/PackageObjectLegacyVersion.cs @@ -108,7 +108,7 @@ public enum PackageObjectLegacyVersion // 321 according to the GoW client ElementOwnerAddedToUPolys = 321, - NetObjectsAdded = 322, + NetObjectCountAdded = 322, NumberAddedToName = 343, @@ -123,7 +123,9 @@ public enum PackageObjectLegacyVersion VerticalOffsetAddedToUFont = 506, CleanupFonts = 511, - + + ComponentMapDeprecated = 543, + AddedTextureFileCacheGuidToTexture2D = 567, LightmassAdded = 600, diff --git a/src/Core/Classes/UObject.cs b/src/Core/Classes/UObject.cs index bef3d078..585fbd6a 100644 --- a/src/Core/Classes/UObject.cs +++ b/src/Core/Classes/UObject.cs @@ -257,7 +257,7 @@ private void DeserializeNetIndex() return; } #endif - if (_Buffer.Version < (uint)PackageObjectLegacyVersion.NetObjectsAdded || + if (_Buffer.Version < (uint)PackageObjectLegacyVersion.NetObjectCountAdded || _Buffer.UE4Version >= 196) { return; diff --git a/src/Core/Tables/UExportTableItem.cs b/src/Core/Tables/UExportTableItem.cs index 49232fff..65c2ad85 100644 --- a/src/Core/Tables/UExportTableItem.cs +++ b/src/Core/Tables/UExportTableItem.cs @@ -102,7 +102,9 @@ public string ArchetypeName public int SerialOffset; public uint ExportFlags; - //public Dictionary Components; + + public UMap ComponentMap; + //public List NetObjects; public UGuid PackageGuid; @@ -210,7 +212,7 @@ public void Deserialize(IUnrealStream stream) if (stream.Version < 220) return; - if (stream.Version < 543 + if (stream.Version < (uint)PackageObjectLegacyVersion.ComponentMapDeprecated #if ALPHAPROTOCOL && stream.Package.Build != UnrealPackage.GameBuild.BuildName.AlphaProtocol #endif @@ -220,9 +222,14 @@ public void Deserialize(IUnrealStream stream) #endif ) { - // NameToObject - int componentMapCount = stream.ReadInt32(); - stream.Skip(componentMapCount * 12); + stream.Read(out int c); + ComponentMap = new UMap(c); + for (int i = 0; i < c; ++i) + { + stream.Read(out UName key); + stream.Read(out int value); + ComponentMap.Add(key, value); + } } if (stream.Version < 247) @@ -230,7 +237,7 @@ public void Deserialize(IUnrealStream stream) streamExportFlags: ExportFlags = stream.ReadUInt32(); - if (stream.Version < (uint)PackageObjectLegacyVersion.NetObjectsAdded) + if (stream.Version < (uint)PackageObjectLegacyVersion.NetObjectCountAdded) return; #if TRANSFORMERS if (stream.Package.Build == BuildGeneration.HMS && @@ -266,7 +273,6 @@ public void Deserialize(IUnrealStream stream) if (stream.Package.Build != UnrealPackage.GameBuild.BuildName.MKKE) { #endif - // Array of objects int netObjectCount = stream.ReadInt32(); stream.Skip(netObjectCount * 4); #if MKKE From 74da92f48b7ad90630bf5679cec69e6f695d8617 Mon Sep 17 00:00:00 2001 From: Eliot Date: Wed, 15 May 2024 15:35:40 +0200 Subject: [PATCH 31/50] Fix UScriptStruct and UClass (WIP) deserialization for 2xx packages; #80 --- src/Branch/PackageObjectLegacyVersion.cs | 12 +- src/Core/Classes/UClass.cs | 147 ++++++++++++----------- src/Core/Classes/UScriptStruct.cs | 12 +- src/UnrealStream.cs | 12 ++ 4 files changed, 110 insertions(+), 73 deletions(-) diff --git a/src/Branch/PackageObjectLegacyVersion.cs b/src/Branch/PackageObjectLegacyVersion.cs index 5942de3c..91723d92 100644 --- a/src/Branch/PackageObjectLegacyVersion.cs +++ b/src/Branch/PackageObjectLegacyVersion.cs @@ -64,6 +64,8 @@ public enum PackageObjectLegacyVersion /// IsLocalAddedToDelegateFunctionToken = 181, + ClassDependenciesDeprecated = 186, + // FIXME: Version RangeConstTokenDeprecated = UE3, @@ -76,6 +78,8 @@ public enum PackageObjectLegacyVersion ObjectFlagsSizeChangedToULong = 195, ArchetypeAddedToExports = 220, + ComponentClassBridgeMapDeprecated = 248, + // 227 according to the GoW client FixedVerticesToArrayFromPoly = 227, @@ -87,6 +91,8 @@ public enum PackageObjectLegacyVersion L8AddedToLazyArray = 260, LazyArrayReplacedWithBulkData = 266, + ComponentTemplatesDeprecated = 267, + // 267 according to the GoW client, // -- albeit the exact nature is not clear // -- whether if this indicates the addition of such an ObjectFlag or just the conditional test. @@ -94,6 +100,8 @@ public enum PackageObjectLegacyVersion ComponentGuidDeprecated = 273, + InterfaceClassesDeprecated = 288, + /// /// Some properties like SizeX, SizeY, Format have been displaced to ScriptProperties. /// @@ -123,9 +131,9 @@ public enum PackageObjectLegacyVersion VerticalOffsetAddedToUFont = 506, CleanupFonts = 511, - + ComponentMapDeprecated = 543, - + AddedTextureFileCacheGuidToTexture2D = 567, LightmassAdded = 600, diff --git a/src/Core/Classes/UClass.cs b/src/Core/Classes/UClass.cs index 19bcc624..712d9f8f 100644 --- a/src/Core/Classes/UClass.cs +++ b/src/Core/Classes/UClass.cs @@ -70,11 +70,9 @@ public void Deserialize(IUnrealStream stream) /// public IList PackageImports; - /// - /// Index of component names into the NameTableList. - /// UE3 - /// + [Obsolete("Use ComponentDefaultObjectMap")] public IList Components = null; + public UMap ComponentDefaultObjectMap; /// /// Index of unsorted categories names into the NameTableList. @@ -190,15 +188,21 @@ protected override void Deserialize() _Buffer.ReadStruct(out ClassGuid); Record(nameof(ClassGuid), ClassGuid); } - skipClassGuid: - if (_Buffer.Version < 248) + + if (_Buffer.Version < (uint)PackageObjectLegacyVersion.ClassDependenciesDeprecated) { _Buffer.ReadArray(out ClassDependencies); Record(nameof(ClassDependencies), ClassDependencies); + } + + // FIXME: version + if (_Buffer.Version < 220) + { PackageImports = DeserializeGroup(nameof(PackageImports)); } - + + serializeWithin: if (_Buffer.Version >= 62) { // Class Name Extends Super.Name Within _WithinIndex @@ -211,7 +215,7 @@ protected override void Deserialize() if (_Buffer.Package.Build == UnrealPackage.GameBuild.BuildName.DNF && _Buffer.Version >= 102) { - DeserializeHideCategories(); + HideCategories = DeserializeGroup("HideCategories"); if (_Buffer.Version >= 137) { _Buffer.ReadArray(out UArray dnfTags); @@ -246,22 +250,76 @@ protected override void Deserialize() // +HideCategories if (_Buffer.Version >= 99) { - // TODO: Corrigate Version - if (_Buffer.Version >= 220) + // FIXME: >= version + if (_Buffer.Version >= 178 + && isHideCategoriesOldOrder + && !Package.IsConsoleCooked() + && !Package.Build.Flags.HasFlag(BuildFlags.XenonCooked) + && _Buffer.UE4Version < 117) { - // TODO: Corrigate Version - if (isHideCategoriesOldOrder && !Package.IsConsoleCooked() && - !Package.Build.Flags.HasFlag(BuildFlags.XenonCooked) && - _Buffer.UE4Version < 117) - DeserializeHideCategories(); + HideCategories = DeserializeGroup("HideCategories"); + } - // Seems to have been removed in transformer packages - if (_Buffer.UE4Version < 118) DeserializeComponentsMap(); + // FIXME: >= version + if (_Buffer.Version >= 178 && _Buffer.Version < + (uint)PackageObjectLegacyVersion.ComponentClassBridgeMapDeprecated) + { + _Buffer.ReadMap(out UMap componentClassBridgeMap); + Record(nameof(componentClassBridgeMap), componentClassBridgeMap); } - // RoboBlitz(369) - // TODO: Corrigate Version - if (_Buffer.Version >= VInterfaceClass) DeserializeInterfaces(); + // FIXME: >= version (187-223) + if (_Buffer.Version >= 220 && + _Buffer.Version < (uint)PackageObjectLegacyVersion.ComponentTemplatesDeprecated) + { + _Buffer.ReadArray(out UArray componentTemplates); + Record(nameof(componentTemplates), componentTemplates); + } + + // FIXME: >= version + if (_Buffer.Version >= 178 && _Buffer.UE4Version < 118) + { + _Buffer.Read(out ComponentDefaultObjectMap); + Record(nameof(ComponentDefaultObjectMap), ComponentDefaultObjectMap); + } + + // FIXME: >= version (187-223) + if (_Buffer.Version >= 220) + { + if (_Buffer.Version < (uint)PackageObjectLegacyVersion.InterfaceClassesDeprecated) + { + _Buffer.ReadArray(out UArray interfaceClasses); + Record(nameof(interfaceClasses), interfaceClasses); + } + else + { + // See http://udn.epicgames.com/Three/UnrealScriptInterfaces.html + int interfacesCount = _Buffer.ReadInt32(); + Record("Implements.Count", interfacesCount); + if (interfacesCount > 0) + { + AssertEOS(interfacesCount * 8, "Implemented"); + ImplementedInterfaces = new List(interfacesCount); + for (int i = 0; i < interfacesCount; ++i) + { + int interfaceIndex = _Buffer.ReadInt32(); + Record("Implemented.InterfaceIndex", interfaceIndex); + int typeIndex = _Buffer.ReadInt32(); + Record("Implemented.TypeIndex", typeIndex); + ImplementedInterfaces.Add(interfaceIndex); +#if UE4 + if (_Buffer.UE4Version <= 0) + { + continue; + } + + bool isImplementedByK2 = _Buffer.ReadInt32() > 0; + Record("Implemented.isImplementedByK2", isImplementedByK2); +#endif + } + } + } + } #if UE4 if (_Buffer.UE4Version > 0) { @@ -269,7 +327,6 @@ protected override void Deserialize() Record(nameof(classGeneratedBy), classGeneratedBy); } #endif - #if AHIT if (Package.Build == UnrealPackage.GameBuild.BuildName.AHIT && _Buffer.Version >= 878) { @@ -291,7 +348,7 @@ protected override void Deserialize() // FIXME: Added in v99, removed in ~220? if (_Buffer.Version < 220 || !isHideCategoriesOldOrder) { - DeserializeHideCategories(); + HideCategories = DeserializeGroup("HideCategories"); #if SPELLBORN if (Package.Build == UnrealPackage.GameBuild.BuildName.Spellborn) { @@ -590,52 +647,6 @@ protected override void Deserialize() #endif } - private void DeserializeInterfaces() - { - // See http://udn.epicgames.com/Three/UnrealScriptInterfaces.html - int interfacesCount = _Buffer.ReadInt32(); - Record("Implements.Count", interfacesCount); - if (interfacesCount <= 0) - return; - - AssertEOS(interfacesCount * 8, "Implemented"); - ImplementedInterfaces = new List(interfacesCount); - for (var i = 0; i < interfacesCount; ++i) - { - int interfaceIndex = _Buffer.ReadInt32(); - Record("Implemented.InterfaceIndex", interfaceIndex); - int typeIndex = _Buffer.ReadInt32(); - Record("Implemented.TypeIndex", typeIndex); - ImplementedInterfaces.Add(interfaceIndex); -#if UE4 - if (_Buffer.UE4Version > 0) - { - var isImplementedByK2 = _Buffer.ReadInt32() > 0; - Record("Implemented.isImplementedByK2", isImplementedByK2); - } -#endif - } - } - - private void DeserializeHideCategories() - { - HideCategories = DeserializeGroup("HideCategories"); - } - - private void DeserializeComponentsMap() - { - int componentsCount = _Buffer.ReadInt32(); - Record("Components.Count", componentsCount); - if (componentsCount <= 0) - return; - - // NameIndex/ObjectIndex - int numBytes = componentsCount * 12; - AssertEOS(numBytes, "Components"); - _Buffer.Skip(numBytes); - _Buffer.ConformRecordPosition(); - } - protected override void FindChildren() { base.FindChildren(); diff --git a/src/Core/Classes/UScriptStruct.cs b/src/Core/Classes/UScriptStruct.cs index 494c8f46..9cbea421 100644 --- a/src/Core/Classes/UScriptStruct.cs +++ b/src/Core/Classes/UScriptStruct.cs @@ -10,11 +10,17 @@ public class UScriptStruct : UStruct protected override void Deserialize() { base.Deserialize(); - StructFlags = _Buffer.ReadUInt32(); - Record(nameof(StructFlags), (StructFlags)StructFlags); + + // FIXME: Version + if (_Buffer.Version >= 221) + { + StructFlags = _Buffer.ReadUInt32(); + Record(nameof(StructFlags), (StructFlags)StructFlags); + } + DeserializeProperties(); } #endregion } -} \ No newline at end of file +} diff --git a/src/UnrealStream.cs b/src/UnrealStream.cs index 71c16871..62eb611e 100644 --- a/src/UnrealStream.cs +++ b/src/UnrealStream.cs @@ -967,6 +967,18 @@ public static void ReadMap(this IUnrealStream stream, out UMap map) + { + int c = stream.ReadLength(); + map = new UMap(c); + for (int i = 0; i < c; ++i) + { + Read(stream, out UObject key); + Read(stream, out UName value); + map.Add(key, value); + } + } + [Obsolete("See UGuid")] public static Guid ReadGuid(this IUnrealStream stream) { From 378ca8abe7230ba394bec2968e888d113247acf3 Mon Sep 17 00:00:00 2001 From: Benjamin Moir Date: Sat, 6 Jul 2024 13:56:54 +1000 Subject: [PATCH 32/50] Support for DNF pointer(T) syntax --- src/Core/Classes/Props/UPointerProperty.cs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/Core/Classes/Props/UPointerProperty.cs b/src/Core/Classes/Props/UPointerProperty.cs index 542c4c79..256d4e42 100644 --- a/src/Core/Classes/Props/UPointerProperty.cs +++ b/src/Core/Classes/Props/UPointerProperty.cs @@ -10,6 +10,10 @@ namespace UELib.Core [UnrealRegisterClass] public class UPointerProperty : UProperty { +#if DNF + public UName PointerType; +#endif + /// /// Creates a new instance of the UELib.Core.UPointerProperty class. /// @@ -18,9 +22,25 @@ public UPointerProperty() Type = PropertyType.PointerProperty; } + /// + protected override void Deserialize() + { + base.Deserialize(); +#if DNF + if (Package.Build == UnrealPackage.GameBuild.BuildName.DNF) + { + PointerType = _Buffer.ReadNameReference(); + } +#endif + } + /// public override string GetFriendlyType() { +#if DNF + if (Package.Build == UnrealPackage.GameBuild.BuildName.DNF) + return "pointer(" + PointerType.Name + ")"; +#endif return "pointer"; } } From c77e09da51eaa6c01da3646a655cd7032ba55207 Mon Sep 17 00:00:00 2001 From: Benjamin Moir Date: Sat, 6 Jul 2024 13:57:37 +1000 Subject: [PATCH 33/50] Support for DNF DevExec specifier --- src/Core/Classes/UFunctionDecompiler.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Core/Classes/UFunctionDecompiler.cs b/src/Core/Classes/UFunctionDecompiler.cs index bb7f2a3c..d16e1dde 100644 --- a/src/Core/Classes/UFunctionDecompiler.cs +++ b/src/Core/Classes/UFunctionDecompiler.cs @@ -146,8 +146,11 @@ private string FormatFlags() #if DNF if (Package.Build == UnrealPackage.GameBuild.BuildName.DNF) { - // 0x20000200 unknown specifier - + if (HasFunctionFlag(0x20000000)) + { + output += "devexec "; + } + if (HasFunctionFlag(0x4000000)) { output += "animevent "; From d814407dcff558e0a7f480283b04ec4a2ea9f779 Mon Sep 17 00:00:00 2001 From: Benjamin Moir Date: Sat, 6 Jul 2024 13:59:03 +1000 Subject: [PATCH 34/50] Support for DNF delegate functions --- src/Core/Classes/UFunction.cs | 9 +++++++++ src/Core/Classes/UFunctionDecompiler.cs | 2 +- src/Core/Classes/UFunctionNode.cs | 2 +- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/Core/Classes/UFunction.cs b/src/Core/Classes/UFunction.cs index 75cbff07..e7d7e49b 100644 --- a/src/Core/Classes/UFunction.cs +++ b/src/Core/Classes/UFunction.cs @@ -182,6 +182,15 @@ public bool IsPre() return IsOperator() && HasFunctionFlag(Flags.FunctionFlags.PreOperator); } + public bool IsDelegate() + { +#if DNF + if (Package.Build == UnrealPackage.GameBuild.BuildName.DNF) + return HasFunctionFlag(0x400000); +#endif + return HasFunctionFlag(Flags.FunctionFlags.Delegate); + } + public bool HasOptionalParamData() { // FIXME: Deprecate version check, and re-map the function flags using the EngineBranch class approach. diff --git a/src/Core/Classes/UFunctionDecompiler.cs b/src/Core/Classes/UFunctionDecompiler.cs index d16e1dde..e01d318a 100644 --- a/src/Core/Classes/UFunctionDecompiler.cs +++ b/src/Core/Classes/UFunctionDecompiler.cs @@ -210,7 +210,7 @@ private string FormatFlags() isNormalFunction = false; } - if (HasFunctionFlag(Flags.FunctionFlags.Delegate)) + if (IsDelegate()) { output += "delegate "; isNormalFunction = false; diff --git a/src/Core/Classes/UFunctionNode.cs b/src/Core/Classes/UFunctionNode.cs index 7f3efa48..08b2e9d4 100644 --- a/src/Core/Classes/UFunctionNode.cs +++ b/src/Core/Classes/UFunctionNode.cs @@ -36,7 +36,7 @@ public override string GetImageName() { name = "Event"; } - else if (HasFunctionFlag(Flags.FunctionFlags.Delegate)) + else if (IsDelegate()) { name = "Delegate"; } From d4269be3d3672d059db80a59cfc202d171cc2adc Mon Sep 17 00:00:00 2001 From: Benjamin Moir Date: Sat, 6 Jul 2024 14:01:35 +1000 Subject: [PATCH 35/50] Don't output delegate fields for DNF --- src/Core/Classes/Props/UDelegateProperty.cs | 8 ++++++++ src/Core/Classes/Props/UProperty.cs | 5 +++++ src/Core/Classes/UStructDecompiler.cs | 3 +++ 3 files changed, 16 insertions(+) diff --git a/src/Core/Classes/Props/UDelegateProperty.cs b/src/Core/Classes/Props/UDelegateProperty.cs index 5b88aa85..dc37b353 100644 --- a/src/Core/Classes/Props/UDelegateProperty.cs +++ b/src/Core/Classes/Props/UDelegateProperty.cs @@ -50,6 +50,14 @@ protected override void Deserialize() } } +#if DNF + public override bool IsCompilerGenerated() + { + // delegate properties are compiler generated in DNF, not part of the source. + return Package.Build == UnrealPackage.GameBuild.BuildName.DNF; + } +#endif + /// public override string GetFriendlyType() { diff --git a/src/Core/Classes/Props/UProperty.cs b/src/Core/Classes/Props/UProperty.cs index b416e317..8a380d07 100644 --- a/src/Core/Classes/Props/UProperty.cs +++ b/src/Core/Classes/Props/UProperty.cs @@ -288,6 +288,11 @@ public bool IsParm() return HasPropertyFlag(PropertyFlagsLO.Parm); } + public virtual bool IsCompilerGenerated() + { + return false; + } + public virtual string GetFriendlyInnerType() { return string.Empty; diff --git a/src/Core/Classes/UStructDecompiler.cs b/src/Core/Classes/UStructDecompiler.cs index 82fbb1ba..e079648e 100644 --- a/src/Core/Classes/UStructDecompiler.cs +++ b/src/Core/Classes/UStructDecompiler.cs @@ -203,6 +203,9 @@ protected string FormatProperties() // Don't use foreach, screws up order. foreach (var property in Variables) { + if (property.IsCompilerGenerated()) + continue; + // Fix for properties within structs output += "\r\n" + property.PreDecompile() + From 606d95c190e598b6c43ada752ce04a21e3d0cc7d Mon Sep 17 00:00:00 2001 From: Benjamin Moir Date: Sat, 6 Jul 2024 18:38:22 +1000 Subject: [PATCH 36/50] Support DNF comment string syntax --- src/Core/Classes/Props/UPropertyDecompiler.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/Core/Classes/Props/UPropertyDecompiler.cs b/src/Core/Classes/Props/UPropertyDecompiler.cs index a939f8ee..d659a79d 100644 --- a/src/Core/Classes/Props/UPropertyDecompiler.cs +++ b/src/Core/Classes/Props/UPropertyDecompiler.cs @@ -37,6 +37,10 @@ private string DecompileEditorData() string[] options = EditorDataText.TrimEnd('\n').Split('\n'); string decodedOptions = string.Join(" ", options.Select(PropertyDisplay.FormatLiteral)); +#if DNF + if (Package.Build == UnrealPackage.GameBuild.BuildName.DNF) + return " ?(" + decodedOptions + ")"; +#endif return " " + decodedOptions; } @@ -378,7 +382,12 @@ public string FormatFlags() } else // Not Automated { - if ((PropertyFlags & (ulong)Flags.PropertyFlagsLO.NoExport) != 0) + if ((PropertyFlags & (ulong)Flags.PropertyFlagsLO.NoExport) != 0 +#if DNF + // 0x00800000 is CPF_Comment in DNF + && Package.Build != UnrealPackage.GameBuild.BuildName.DNF +#endif + ) { output += "noexport "; copyFlags &= ~(ulong)Flags.PropertyFlagsLO.NoExport; From add174ff15c497a347c46717b241a6f543de1027 Mon Sep 17 00:00:00 2001 From: Benjamin Moir Date: Sat, 6 Jul 2024 18:58:03 +1000 Subject: [PATCH 37/50] Support DNF edfindable specifier --- src/Core/Classes/Props/UPropertyDecompiler.cs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/Core/Classes/Props/UPropertyDecompiler.cs b/src/Core/Classes/Props/UPropertyDecompiler.cs index d659a79d..8a425c11 100644 --- a/src/Core/Classes/Props/UPropertyDecompiler.cs +++ b/src/Core/Classes/Props/UPropertyDecompiler.cs @@ -431,6 +431,9 @@ public string FormatFlags() if ((PropertyFlags & (ulong)Flags.PropertyFlagsLO.EdFindable) != 0 #if AHIT && Package.Build != UnrealPackage.GameBuild.BuildName.AHIT +#endif +#if DNF + && Package.Build != UnrealPackage.GameBuild.BuildName.DNF #endif ) { @@ -438,7 +441,11 @@ public string FormatFlags() output += "edfindable "; } - if ((PropertyFlags & (ulong)Flags.PropertyFlagsLO.Deprecated) != 0) + if ((PropertyFlags & (ulong)Flags.PropertyFlagsLO.Deprecated) != 0 +#if DNF + && Package.Build != UnrealPackage.GameBuild.BuildName.DNF +#endif + ) { output += "deprecated "; copyFlags &= ~(ulong)Flags.PropertyFlagsLO.Deprecated; @@ -504,6 +511,12 @@ public string FormatFlags() #if DNF if (Package.Build == UnrealPackage.GameBuild.BuildName.DNF) { + if (HasPropertyFlag(0x20000000)) + { + output += "edfindable "; + copyFlags &= ~(uint)0x20000000; + } + if (HasPropertyFlag(0x1000000)) { output += "nontrans "; From 4bf5447fb3f8a854e4d851a158a10c303a104abb Mon Sep 17 00:00:00 2001 From: Benjamin Moir Date: Sat, 6 Jul 2024 19:10:55 +1000 Subject: [PATCH 38/50] Support for DNF obsolete, editinlinenew, noteditinlinenew, placeable, notplaceable class flags --- src/Core/Classes/UClassDecompiler.cs | 65 ++++++++++++++++++++++++---- 1 file changed, 56 insertions(+), 9 deletions(-) diff --git a/src/Core/Classes/UClassDecompiler.cs b/src/Core/Classes/UClassDecompiler.cs index 0ce38567..ce1fe325 100644 --- a/src/Core/Classes/UClassDecompiler.cs +++ b/src/Core/Classes/UClassDecompiler.cs @@ -249,17 +249,29 @@ private string FormatFlags() } } - if ((ClassFlags & (uint)Flags.ClassFlags.EditInlineNew) != 0) +#if DNF + if (Package.Build == UnrealPackage.GameBuild.BuildName.DNF) { - output += "\r\n\teditinlinenew"; + if (HasClassFlag(0x00001000U)) + { + output += "\r\n\tobsolete"; + } } else +#endif { - // Only do if parent had EditInlineNew - var parentClass = (UClass)Super; - if (parentClass != null && (parentClass.ClassFlags & (uint)Flags.ClassFlags.EditInlineNew) != 0) + if ((ClassFlags & (uint)Flags.ClassFlags.EditInlineNew) != 0) + { + output += "\r\n\teditinlinenew"; + } + else { - output += "\r\n\tnoteditinlinenew"; + // Only do if parent had EditInlineNew + var parentClass = (UClass)Super; + if (parentClass != null && (parentClass.ClassFlags & (uint)Flags.ClassFlags.EditInlineNew) != 0) + { + output += "\r\n\tnoteditinlinenew"; + } } } @@ -268,6 +280,25 @@ private string FormatFlags() output += "\r\n\tcollapsecategories"; } +#if DNF + if (Package.Build == UnrealPackage.GameBuild.BuildName.DNF) + { + if (HasClassFlag(0x00004000)) + { + output += "\r\n\teditinlinenew"; + } + else + { + // Only do if parent had EditInlineNew + var parentClass = (UClass)Super; + if (parentClass != null && (parentClass.ClassFlags & 0x00004000) != 0) + { + output += "\r\n\tnoteditinlinenew"; + } + } + } + else +#endif // TODO: Might indicate "Interface" in later versions if (HasClassFlag(Flags.ClassFlags.ExportStructs) && Package.Version < 300) { @@ -281,13 +312,29 @@ private string FormatFlags() if (Extends("Actor")) { - if ((ClassFlags & (uint)Flags.ClassFlags.Placeable) != 0) +#if DNF + if (Package.Build == UnrealPackage.GameBuild.BuildName.DNF) { - output += Package.Version >= PlaceableVersion ? "\r\n\tplaceable" : "\r\n\tusercreate"; + if (HasClassFlag(0x02000)) + { + output += "\r\n\tplaceable"; + } + else + { + output += $"\r\n\tnotplaceable"; + } } else +#endif { - output += Package.Version >= PlaceableVersion ? "\r\n\tnotplaceable" : "\r\n\tnousercreate"; + if ((ClassFlags & (uint)Flags.ClassFlags.Placeable) != 0) + { + output += Package.Version >= PlaceableVersion ? "\r\n\tplaceable" : "\r\n\tusercreate"; + } + else + { + output += Package.Version >= PlaceableVersion ? "\r\n\tnotplaceable" : "\r\n\tnousercreate"; + } } } From 25c7644690a5b97809dc8a75da029ac6b40f4265 Mon Sep 17 00:00:00 2001 From: Eliot Date: Sat, 6 Jul 2024 17:44:53 +0200 Subject: [PATCH 39/50] Subtract the 'DNF' 'CommentString' flag for properties. --- src/Branch/EngineBranch.cs | 12 +++++++++++- src/Branch/UE2/DNF/EngineBranch.DNF.cs | 12 ++++++++++++ src/Core/Classes/Props/UPropertyDecompiler.cs | 3 +++ src/UnrealPackage.cs | 8 ++++++++ 4 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/Branch/EngineBranch.cs b/src/Branch/EngineBranch.cs index a4977e7c..b1c065d5 100644 --- a/src/Branch/EngineBranch.cs +++ b/src/Branch/EngineBranch.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Diagnostics; using UELib.Annotations; +using UELib.Branch.UE2.DNF; using UELib.Core.Tokens; using UELib.Decoding; using UELib.Tokens; @@ -44,6 +45,15 @@ public EngineBranch(BuildGeneration generation) Generation = generation; } + public void ConditionalBranchAction(Action action) + where T : EngineBranch + { + if (GetType() == typeof(T)) + { + action(); + } + } + protected void SetupSerializer() where T : IPackageSerializer { @@ -123,4 +133,4 @@ public virtual void PostDeserializePackage(UnrealPackage linker, IUnrealStream s { } } -} \ No newline at end of file +} diff --git a/src/Branch/UE2/DNF/EngineBranch.DNF.cs b/src/Branch/UE2/DNF/EngineBranch.DNF.cs index 1fc4fd6c..7b1dd1ea 100644 --- a/src/Branch/UE2/DNF/EngineBranch.DNF.cs +++ b/src/Branch/UE2/DNF/EngineBranch.DNF.cs @@ -3,6 +3,9 @@ using UELib.Branch.UE2.DNF.Tokens; using UELib.Core; using UELib.Tokens; +using System.Diagnostics; +using System; +using UELib.Annotations; namespace UELib.Branch.UE2.DNF { @@ -13,6 +16,15 @@ public EngineBranchDNF(BuildGeneration generation) : base(generation) { } + [Conditional("DNF")] + public static void ConditionalBranchAction([NotNull] EngineBranch branch, Action action) + { + if (branch.GetType() == typeof(EngineBranchDNF)) + { + action(); + } + } + protected TokenMap BuildTokenMap(UnrealPackage linker) { return new TokenMap(0x80) diff --git a/src/Core/Classes/Props/UPropertyDecompiler.cs b/src/Core/Classes/Props/UPropertyDecompiler.cs index 8a425c11..55bdab1c 100644 --- a/src/Core/Classes/Props/UPropertyDecompiler.cs +++ b/src/Core/Classes/Props/UPropertyDecompiler.cs @@ -511,6 +511,9 @@ public string FormatFlags() #if DNF if (Package.Build == UnrealPackage.GameBuild.BuildName.DNF) { + // Always erase 'CommentString' + copyFlags &= ~(uint)0x00800000; + if (HasPropertyFlag(0x20000000)) { output += "edfindable "; diff --git a/src/UnrealPackage.cs b/src/UnrealPackage.cs index 6351f964..547ac243 100644 --- a/src/UnrealPackage.cs +++ b/src/UnrealPackage.cs @@ -726,6 +726,14 @@ private FieldInfo FindBuildInfo(UnrealPackage linker, [CanBeNull] out BuildAttri return null; } + public void ConditionalBuildAction(BuildName build, Action action) + { + if (this == build) + { + action(); + } + } + public static bool operator ==(GameBuild b, BuildGeneration gen) { return b.Generation == gen; From b63c9b4725a1893d12baee577f5ea4f279794cef Mon Sep 17 00:00:00 2001 From: Benjamin Moir Date: Mon, 15 Jul 2024 22:04:33 +1000 Subject: [PATCH 40/50] Support DNF editinline and editinlineuse property specifiers --- src/Core/Classes/Props/UPropertyDecompiler.cs | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/src/Core/Classes/Props/UPropertyDecompiler.cs b/src/Core/Classes/Props/UPropertyDecompiler.cs index 55bdab1c..1fdc63dc 100644 --- a/src/Core/Classes/Props/UPropertyDecompiler.cs +++ b/src/Core/Classes/Props/UPropertyDecompiler.cs @@ -407,14 +407,29 @@ public string FormatFlags() copyFlags &= ~(ulong)Flags.PropertyFlagsLO.ExportObject; } - if ((PropertyFlags & (ulong)Flags.PropertyFlagsLO.EditInline) != 0) + ulong editInline = (ulong)Flags.PropertyFlagsLO.EditInline; + ulong editInlineUse = (ulong)Flags.PropertyFlagsLO.EditInlineUse; + +#if DNF + if (Package.Build == UnrealPackage.GameBuild.BuildName.DNF) + { + editInline = 0x10000000; + editInlineUse = 0x40000000; + } +#endif + + if ((PropertyFlags & editInline) != 0) { - if ((PropertyFlags & (ulong)Flags.PropertyFlagsLO.EditInlineUse) != 0) + if ((PropertyFlags & editInlineUse) != 0) { - copyFlags &= ~(ulong)Flags.PropertyFlagsLO.EditInlineUse; + copyFlags &= ~editInlineUse; output += "editinlineuse "; } - else if ((PropertyFlags & (ulong)Flags.PropertyFlagsLO.EditInlineNotify) != 0) + else if ((PropertyFlags & (ulong)Flags.PropertyFlagsLO.EditInlineNotify) != 0 +#if DNF + && Package.Build != UnrealPackage.GameBuild.BuildName.DNF +#endif + ) { copyFlags &= ~(ulong)Flags.PropertyFlagsLO.EditInlineNotify; output += "editinlinenotify "; @@ -424,7 +439,7 @@ public string FormatFlags() output += "editinline "; } - copyFlags &= ~(ulong)Flags.PropertyFlagsLO.EditInline; + copyFlags &= ~editInline; } } From 41d57d641f3ef15489b1bff5d02b5968544ddf2e Mon Sep 17 00:00:00 2001 From: Benjamin Moir Date: Mon, 15 Jul 2024 22:12:18 +1000 Subject: [PATCH 41/50] Support DNF editinlinenotify property specifier --- src/Core/Classes/Props/UPropertyDecompiler.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/Core/Classes/Props/UPropertyDecompiler.cs b/src/Core/Classes/Props/UPropertyDecompiler.cs index 1fdc63dc..df96fd11 100644 --- a/src/Core/Classes/Props/UPropertyDecompiler.cs +++ b/src/Core/Classes/Props/UPropertyDecompiler.cs @@ -409,12 +409,14 @@ public string FormatFlags() ulong editInline = (ulong)Flags.PropertyFlagsLO.EditInline; ulong editInlineUse = (ulong)Flags.PropertyFlagsLO.EditInlineUse; + ulong editInlineNotify = (ulong)Flags.PropertyFlagsLO.EditInlineNotify; #if DNF if (Package.Build == UnrealPackage.GameBuild.BuildName.DNF) { editInline = 0x10000000; editInlineUse = 0x40000000; + editInlineNotify = 0x80000000; } #endif @@ -425,13 +427,9 @@ public string FormatFlags() copyFlags &= ~editInlineUse; output += "editinlineuse "; } - else if ((PropertyFlags & (ulong)Flags.PropertyFlagsLO.EditInlineNotify) != 0 -#if DNF - && Package.Build != UnrealPackage.GameBuild.BuildName.DNF -#endif - ) + else if ((PropertyFlags & editInlineNotify) != 0) { - copyFlags &= ~(ulong)Flags.PropertyFlagsLO.EditInlineNotify; + copyFlags &= ~editInlineNotify; output += "editinlinenotify "; } else if (!HasPropertyFlag(Flags.PropertyFlagsLO.DuplicateTransient)) From 7de145f077595f7ad0ba41fd4595533a3e33108c Mon Sep 17 00:00:00 2001 From: Eliot Date: Sat, 20 Jul 2024 23:14:29 +0200 Subject: [PATCH 42/50] Re-factor "IsCompilerGenerated" --- src/Core/Classes/Props/UDelegateProperty.cs | 8 ------ src/Core/Classes/Props/UProperty.cs | 5 ---- src/Core/Classes/UStructDecompiler.cs | 25 +++++++++++++------ .../IsCompilerAutoGeneratedHelper.cs | 25 +++++++++++++++++++ 4 files changed, 42 insertions(+), 21 deletions(-) create mode 100644 src/Decompiler/IsCompilerAutoGeneratedHelper.cs diff --git a/src/Core/Classes/Props/UDelegateProperty.cs b/src/Core/Classes/Props/UDelegateProperty.cs index dc37b353..5b88aa85 100644 --- a/src/Core/Classes/Props/UDelegateProperty.cs +++ b/src/Core/Classes/Props/UDelegateProperty.cs @@ -50,14 +50,6 @@ protected override void Deserialize() } } -#if DNF - public override bool IsCompilerGenerated() - { - // delegate properties are compiler generated in DNF, not part of the source. - return Package.Build == UnrealPackage.GameBuild.BuildName.DNF; - } -#endif - /// public override string GetFriendlyType() { diff --git a/src/Core/Classes/Props/UProperty.cs b/src/Core/Classes/Props/UProperty.cs index 8a380d07..b416e317 100644 --- a/src/Core/Classes/Props/UProperty.cs +++ b/src/Core/Classes/Props/UProperty.cs @@ -288,11 +288,6 @@ public bool IsParm() return HasPropertyFlag(PropertyFlagsLO.Parm); } - public virtual bool IsCompilerGenerated() - { - return false; - } - public virtual string GetFriendlyInnerType() { return string.Empty; diff --git a/src/Core/Classes/UStructDecompiler.cs b/src/Core/Classes/UStructDecompiler.cs index e079648e..8f173816 100644 --- a/src/Core/Classes/UStructDecompiler.cs +++ b/src/Core/Classes/UStructDecompiler.cs @@ -1,6 +1,7 @@ #if DECOMPILE using System; using System.Linq; +using UELib.Decompiler; namespace UELib.Core { @@ -203,13 +204,20 @@ protected string FormatProperties() // Don't use foreach, screws up order. foreach (var property in Variables) { - if (property.IsCompilerGenerated()) - continue; + bool isCompilerGenerated = IsCompilerAutoGeneratedHelper.Visit((dynamic)property); // Fix for properties within structs - output += "\r\n" + - property.PreDecompile() + - $"{UDecompilingState.Tabs}var"; + output += "\r\n"; + // MetaData like comments. + string preOutput = property.PreDecompile(); + output += preOutput; + output += UDecompilingState.Tabs; + if (isCompilerGenerated) + { + output += "//"; + } + + output += "var"; if (property.CategoryName != null && !property.CategoryName.IsNone()) { output += property.CategoryName == Name @@ -217,10 +225,11 @@ protected string FormatProperties() : $"({property.CategoryName})"; } output += $" {property.Decompile()};"; - string s = property.PostDecompile(); - if (!string.IsNullOrEmpty(s)) + + string postOutput = property.PostDecompile(); + if (!string.IsNullOrEmpty(postOutput)) { - output += s; + output += postOutput; } } diff --git a/src/Decompiler/IsCompilerAutoGeneratedHelper.cs b/src/Decompiler/IsCompilerAutoGeneratedHelper.cs new file mode 100644 index 00000000..b19ba1f0 --- /dev/null +++ b/src/Decompiler/IsCompilerAutoGeneratedHelper.cs @@ -0,0 +1,25 @@ +using System; +using UELib.Core; + +namespace UELib.Decompiler +{ + // Static non-interface visitor class because we don't require any polymorphism here. + public static class IsCompilerAutoGeneratedHelper + { + public static bool Visit(UDelegateProperty obj) + { + return obj.Name.StartsWith("__", StringComparison.OrdinalIgnoreCase) && + obj.Name.EndsWith("__Delegate", StringComparison.OrdinalIgnoreCase); + } + + public static bool Visit(UField obj) + { + return false; + } + + public static bool Visit(UObject obj) + { + return obj.Name.StartsWith("Default__", StringComparison.OrdinalIgnoreCase); + } + } +} From 8e3053c7addf9a4e5d1e9118ff68100930cf6c6a Mon Sep 17 00:00:00 2001 From: Eliot Date: Tue, 13 Aug 2024 05:21:32 +0200 Subject: [PATCH 43/50] Fixes https://github.com/UE-Explorer/UE-Explorer/issues/63 --- src/UnrealPackage.cs | 80 +++++++++++++++++++------------------------- 1 file changed, 34 insertions(+), 46 deletions(-) diff --git a/src/UnrealPackage.cs b/src/UnrealPackage.cs index 78d07053..ee5ea10b 100644 --- a/src/UnrealPackage.cs +++ b/src/UnrealPackage.cs @@ -413,7 +413,7 @@ public enum BuildName /// 576/021 /// No Special support, but there's no harm in recognizing this build. /// - [Build(576, 21)] Batman1, + [Build(576, 21, BuildGeneration.RSS)] Batman1, /// /// 576/100 @@ -449,10 +449,9 @@ public enum BuildName /// Appears to be missing (v623:ExportGuids, v767:TextureAllocations, and v673:FPropertyTag's BoolValue change). /// Presume at least v832 from Borderlands 2 /// - [Build(594, 58, BuildGeneration.GB)] - [OverridePackageVersion(832)] + [Build(594, 58, BuildGeneration.GB)] [OverridePackageVersion(832)] Borderlands_GOTYE, - + /// /// 584/126 /// @@ -531,8 +530,7 @@ public enum BuildName /// /// 842-864/001 /// - [Build(842, 1, BuildFlags.ConsoleCooked)] - [Build(864, 1, BuildFlags.ConsoleCooked)] + [Build(842, 1, BuildFlags.ConsoleCooked)] [Build(864, 1, BuildFlags.ConsoleCooked)] InfinityBlade2, // Cannot auto-detect, ambiguous with UDK-2015-01-29 @@ -576,7 +574,7 @@ public enum BuildName /// /// 805/101 /// - [Build(805, 101)] [BuildEngineBranch(typeof(EngineBranchRSS))] + [Build(805, 101, BuildGeneration.RSS)] [BuildEngineBranch(typeof(EngineBranchRSS))] Batman2, /// @@ -585,13 +583,15 @@ public enum BuildName /// 806/103 /// 807/137-138 /// - [Build(806, 103)] [Build(807, 807, 137, 138)] [BuildEngineBranch(typeof(EngineBranchRSS))] + [Build(806, 103, BuildGeneration.RSS)] + [Build(807, 807, 137, 138, BuildGeneration.RSS)] + [BuildEngineBranch(typeof(EngineBranchRSS))] Batman3, /// /// 807/104 /// - [Build(807, 104)] [BuildEngineBranch(typeof(EngineBranchRSS))] + [Build(807, 104, BuildGeneration.RSS)] [BuildEngineBranch(typeof(EngineBranchRSS))] Batman3MP, /// @@ -599,7 +599,9 @@ public enum BuildName /// /// 863/32995(227 & ~8000) /// - [Build(863, 32995)] [OverridePackageVersion(863, 227)] [BuildEngineBranch(typeof(EngineBranchRSS))] + [Build(863, 32995, BuildGeneration.RSS)] + [OverridePackageVersion(863, 227)] + [BuildEngineBranch(typeof(EngineBranchRSS))] Batman4, /// @@ -607,7 +609,8 @@ public enum BuildName /// /// 867/008:010 /// - [Build(867, 867, 8u, 10u)] [BuildEngineBranch(typeof(EngineBranchGigantic))] Gigantic, + [Build(867, 867, 8u, 10u)] [BuildEngineBranch(typeof(EngineBranchGigantic))] + Gigantic, /// /// Rocket League @@ -615,7 +618,8 @@ public enum BuildName /// 867/009:032 /// Requires third-party decompression and decryption /// - [Build(867, 868, 9u, 32u)] [BuildEngineBranch(typeof(EngineBranchRL))] RocketLeague, + [Build(867, 868, 9u, 32u)] [BuildEngineBranch(typeof(EngineBranchRL))] + RocketLeague, /// /// Battleborn @@ -833,12 +837,10 @@ public struct PackageFileSummary : IUnrealSerializableClass public UnrealFlags PackageFlags; - [Obsolete] - private const int VHeaderSize = 249; + [Obsolete] private const int VHeaderSize = 249; public int HeaderSize; - [Obsolete] - private const int VFolderName = 269; + [Obsolete] private const int VFolderName = 269; /// /// UPK content category e.g. Weapons, Sounds or Meshes. @@ -857,8 +859,7 @@ public struct PackageFileSummary : IUnrealSerializableClass /// public UArray Heritages; - [Obsolete] - private const int VDependsOffset = 415; + [Obsolete] private const int VDependsOffset = 415; public int DependsOffset; public UGuid Guid; @@ -867,40 +868,32 @@ public struct PackageFileSummary : IUnrealSerializableClass private PackageFileEngineVersion PackageEngineVersion; private PackageFileEngineVersion PackageCompatibleEngineVersion; - [Obsolete] - private const int VEngineVersion = 245; + [Obsolete] private const int VEngineVersion = 245; + + [Obsolete] public const int VCookerVersion = 277; - [Obsolete] - public const int VCookerVersion = 277; - public int EngineVersion; public int CookerVersion; - [Obsolete] - private const int VCompression = 334; + [Obsolete] private const int VCompression = 334; public uint CompressionFlags; public UArray CompressedChunks; - [Obsolete] - private const int VPackageSource = 482; + [Obsolete] private const int VPackageSource = 482; public uint PackageSource; - [Obsolete] - private const int VAdditionalPackagesToCook = 516; + [Obsolete] private const int VAdditionalPackagesToCook = 516; public UArray AdditionalPackagesToCook; - [Obsolete] - private const int VImportExportGuidsOffset = 623; + [Obsolete] private const int VImportExportGuidsOffset = 623; public int ImportExportGuidsOffset; public int ImportGuidsCount; public int ExportGuidsCount; - [Obsolete] - private const int VThumbnailTableOffset = 584; + [Obsolete] private const int VThumbnailTableOffset = 584; public int ThumbnailTableOffset; - [Obsolete] - private const int VTextureAllocations = 767; + [Obsolete] private const int VTextureAllocations = 767; public int GatherableTextDataCount; public int GatherableTextDataOffset; @@ -1379,20 +1372,15 @@ public void Deserialize(IUnrealStream stream) /// public bool IsBigEndianEncoded { get; } - [Obsolete] - public const int VSIZEPREFIXDEPRECATED = 64; - - [Obsolete] - public const int VINDEXDEPRECATED = 178; + [Obsolete] public const int VSIZEPREFIXDEPRECATED = 64; - [Obsolete] - public const int VDLLBIND = 655; + [Obsolete] public const int VINDEXDEPRECATED = 178; - [Obsolete] - public const int VCLASSGROUP = 789; + [Obsolete] public const int VDLLBIND = 655; - [Obsolete] - public const int VCOOKEDPACKAGES = 277; + [Obsolete] public const int VCLASSGROUP = 789; + + [Obsolete] public const int VCOOKEDPACKAGES = 277; public uint Version => Summary.Version; From 1dc0cfd0bce16e71931e390353f96fb59fb01ef1 Mon Sep 17 00:00:00 2001 From: Eliot Date: Tue, 13 Aug 2024 08:35:21 +0200 Subject: [PATCH 44/50] More fixes for Batman. --- src/Core/Classes/Props/UProperty.cs | 16 ++++++++++++---- src/Core/Classes/UClass.cs | 8 +++----- src/UnrealPackage.cs | 2 +- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/Core/Classes/Props/UProperty.cs b/src/Core/Classes/Props/UProperty.cs index 2984d185..71005661 100644 --- a/src/Core/Classes/Props/UProperty.cs +++ b/src/Core/Classes/Props/UProperty.cs @@ -122,11 +122,19 @@ protected override void Deserialize() : _Buffer.ReadUInt32(); Record(nameof(PropertyFlags), PropertyFlags); #if BATMAN - if (Package.Build == BuildGeneration.RSS && - _Buffer.LicenseeVersion >= 101) + if (Package.Build == BuildGeneration.RSS) { - PropertyFlags = (PropertyFlags & 0xFFFF0000) >> 24; - Record(nameof(PropertyFlags), (PropertyFlagsLO)PropertyFlags); + if (_Buffer.LicenseeVersion >= 101) + { + PropertyFlags = (PropertyFlags & 0xFFFF0000) >> 24; + Record(nameof(PropertyFlags), (PropertyFlagsLO)PropertyFlags); + } + + if (Package.Build == UnrealPackage.GameBuild.BuildName.Batman4) + { + PropertyFlags = (PropertyFlags & ~(PropertyFlags >> 2 & 1)) | + ((ulong)PropertyFlagsLO.Net * (PropertyFlags >> 2 & 1)); + } } #endif #if XCOM2 diff --git a/src/Core/Classes/UClass.cs b/src/Core/Classes/UClass.cs index 19bcc624..084c8513 100644 --- a/src/Core/Classes/UClass.cs +++ b/src/Core/Classes/UClass.cs @@ -410,13 +410,11 @@ protected override void Deserialize() #if BATMAN if (Package.Build == BuildGeneration.RSS) { - _Buffer.Skip(sizeof(int)); - if (Package.Build == UnrealPackage.GameBuild.BuildName.Batman4) + if (_Buffer.LicenseeVersion >= 95) { - _Buffer.Skip(sizeof(int)); + int bm_v174 = _Buffer.ReadInt32(); + Record(nameof(bm_v174), bm_v174); } - - _Buffer.ConformRecordPosition(); } #endif #if ROCKETLEAGUE diff --git a/src/UnrealPackage.cs b/src/UnrealPackage.cs index ee5ea10b..eb5250b7 100644 --- a/src/UnrealPackage.cs +++ b/src/UnrealPackage.cs @@ -413,7 +413,7 @@ public enum BuildName /// 576/021 /// No Special support, but there's no harm in recognizing this build. /// - [Build(576, 21, BuildGeneration.RSS)] Batman1, + [Build(576, 21)] Batman1, /// /// 576/100 From 1de6322ae42308a2a9bb0bb978f44c138295e5e0 Mon Sep 17 00:00:00 2001 From: Eliot Date: Tue, 13 Aug 2024 09:16:51 +0200 Subject: [PATCH 45/50] Fix UClass deserialization for Battleborn. --- src/Core/Classes/UClass.cs | 36 +++++++++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/src/Core/Classes/UClass.cs b/src/Core/Classes/UClass.cs index 084c8513..28c1054f 100644 --- a/src/Core/Classes/UClass.cs +++ b/src/Core/Classes/UClass.cs @@ -361,16 +361,33 @@ protected override void Deserialize() Record("Unknown:Dishonored", unknownName); } #endif - if (_Buffer.Version >= UnrealPackage.VCLASSGROUP) +#if BATTLEBORN + if (Package.Build == UnrealPackage.GameBuild.BuildName.Battleborn) { + // Usually 0x03 + byte unknownByte = _Buffer.ReadByte(); + Record("Unknown:Battleborn", unknownByte); + + NativeClassName = _Buffer.ReadText(); + Record(nameof(NativeClassName), NativeClassName); + + // not verified + ClassGroups = DeserializeGroup("ClassGroups"); + + goto skipClassGroups; + } +#endif #if DISHONORED - if (Package.Build == UnrealPackage.GameBuild.BuildName.Dishonored) - { - NativeClassName = _Buffer.ReadText(); - Record(nameof(NativeClassName), NativeClassName); - goto skipClassGroups; - } + if (Package.Build == UnrealPackage.GameBuild.BuildName.Dishonored) + { + NativeClassName = _Buffer.ReadText(); + Record(nameof(NativeClassName), NativeClassName); + + goto skipClassGroups; + } #endif + if (_Buffer.Version >= UnrealPackage.VCLASSGROUP) + { ClassGroups = DeserializeGroup("ClassGroups"); if (_Buffer.Version >= 813) { @@ -448,8 +465,9 @@ protected override void Deserialize() ClassGroups = DeserializeGroup("ClassGroups"); #endif #if BORDERLANDS2 - if (Package.Build == UnrealPackage.GameBuild.BuildName.Borderlands2 || - Package.Build == UnrealPackage.GameBuild.BuildName.Battleborn) + if (Package.Build == UnrealPackage.GameBuild.BuildName.Borderlands2 + || Package.Build == UnrealPackage.GameBuild.BuildName.Battleborn + ) { byte unknownByte = _Buffer.ReadByte(); Record("Unknown:Borderlands2", unknownByte); From 6b83a339908b71ff48b34fc3a7273e60f7a7fa81 Mon Sep 17 00:00:00 2001 From: Eliot Date: Tue, 13 Aug 2024 09:20:29 +0200 Subject: [PATCH 46/50] Fix directives. --- src/Core/Classes/UClass.cs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/Core/Classes/UClass.cs b/src/Core/Classes/UClass.cs index 28c1054f..4b76d6af 100644 --- a/src/Core/Classes/UClass.cs +++ b/src/Core/Classes/UClass.cs @@ -367,7 +367,7 @@ protected override void Deserialize() // Usually 0x03 byte unknownByte = _Buffer.ReadByte(); Record("Unknown:Battleborn", unknownByte); - + NativeClassName = _Buffer.ReadText(); Record(nameof(NativeClassName), NativeClassName); @@ -382,7 +382,7 @@ protected override void Deserialize() { NativeClassName = _Buffer.ReadText(); Record(nameof(NativeClassName), NativeClassName); - + goto skipClassGroups; } #endif @@ -395,9 +395,8 @@ protected override void Deserialize() Record(nameof(NativeClassName), NativeClassName); } } -#if DISHONORED + skipClassGroups: ; -#endif // FIXME: Found first in(V:655, DLLBind?), Definitely not in APB and GoW 2 // TODO: Corrigate Version @@ -443,7 +442,7 @@ protected override void Deserialize() int v2a8 = _Buffer.ReadInt32(); Record(nameof(v2a8), v2a8); - + _Buffer.Read(out UArray v2b0); Record(nameof(v2b0), v2b0); } @@ -464,10 +463,10 @@ protected override void Deserialize() if (Package.Build == UnrealPackage.GameBuild.BuildName.Dishonored) ClassGroups = DeserializeGroup("ClassGroups"); #endif -#if BORDERLANDS2 - if (Package.Build == UnrealPackage.GameBuild.BuildName.Borderlands2 - || Package.Build == UnrealPackage.GameBuild.BuildName.Battleborn - ) +#if BORDERLANDS2 || BATTLEBORN + if (Package.Build == UnrealPackage.GameBuild.BuildName.Borderlands2 || + Package.Build == UnrealPackage.GameBuild.BuildName.Battleborn + ) { byte unknownByte = _Buffer.ReadByte(); Record("Unknown:Borderlands2", unknownByte); From b3dfd593c68592b4e8d50c6c385ac82c485eed1a Mon Sep 17 00:00:00 2001 From: Eliot Date: Tue, 13 Aug 2024 21:31:25 +0200 Subject: [PATCH 47/50] Minor fixes that effected EndWar. --- src/Core/Classes/UClass.cs | 16 +++++++++++++--- src/Core/Classes/UDefaultProperty.cs | 4 +++- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/Core/Classes/UClass.cs b/src/Core/Classes/UClass.cs index b43fffec..1fda4489 100644 --- a/src/Core/Classes/UClass.cs +++ b/src/Core/Classes/UClass.cs @@ -72,7 +72,16 @@ public void Deserialize(IUnrealStream stream) [Obsolete("Use ComponentDefaultObjectMap")] public IList Components = null; - public UMap ComponentDefaultObjectMap; + + /// + /// A map of default objects for the components that are instantiated by this class. + /// + /// The component objects are expected to be derivatives of class , + /// however not all UComponent objects are known to UELib, so manual safe casting is required. + /// + /// Will be null if not deserialized. + /// + public UMap ComponentDefaultObjectMap; /// /// Index of unsorted categories names into the NameTableList. @@ -188,6 +197,7 @@ protected override void Deserialize() _Buffer.ReadStruct(out ClassGuid); Record(nameof(ClassGuid), ClassGuid); } + skipClassGuid: if (_Buffer.Version < (uint)PackageObjectLegacyVersion.ClassDependenciesDeprecated) @@ -195,13 +205,13 @@ protected override void Deserialize() _Buffer.ReadArray(out ClassDependencies); Record(nameof(ClassDependencies), ClassDependencies); } - + // FIXME: version if (_Buffer.Version < 220) { PackageImports = DeserializeGroup(nameof(PackageImports)); } - + serializeWithin: if (_Buffer.Version >= 62) { diff --git a/src/Core/Classes/UDefaultProperty.cs b/src/Core/Classes/UDefaultProperty.cs index 7da75d69..53a2d05f 100644 --- a/src/Core/Classes/UDefaultProperty.cs +++ b/src/Core/Classes/UDefaultProperty.cs @@ -132,8 +132,10 @@ private T FindProperty(out UStruct outer) case PropertyType.ArrayProperty: var arrayField = (UArrayProperty)property; Debug.Assert(arrayField != null, "arrayField != null"); + + // May be null if deserialization failed var arrayInnerField = arrayField.InnerProperty; - if (arrayInnerField.Type == PropertyType.StructProperty) + if (arrayInnerField?.Type == PropertyType.StructProperty) { outer = ((UStructProperty)arrayInnerField).Struct; } From a46d0ced81d26aab5cfd80c36fae04757c231dbd Mon Sep 17 00:00:00 2001 From: Eliot Date: Tue, 13 Aug 2024 23:47:55 +0200 Subject: [PATCH 48/50] Correct package versions that have an affect on EndWar and older packages. --- README.md | 1 + src/Branch/PackageObjectLegacyVersion.cs | 30 ++++- src/Core/Classes/UClass.cs | 153 ++++++++++++----------- src/Core/Classes/UFunctionDecompiler.cs | 5 +- src/Core/Classes/UScriptStruct.cs | 2 +- src/Core/Classes/UStruct.cs | 1 + src/UnrealPackage.cs | 8 +- 7 files changed, 115 insertions(+), 85 deletions(-) diff --git a/README.md b/README.md index b64da22f..4dedf780 100644 --- a/README.md +++ b/README.md @@ -130,6 +130,7 @@ This is a table of games that are confirmed to be compatible with the current st | Duke Nukem Forever (2011) | Unknown | 156/036 | [Extraction is required](https://github.com/DaZombieKiller/MegaPackageExtractor) | | | | | | | | | | | +| Tom Clancy's EndWar | Unknown | 329/000 | | | Roboblitz | 2306 | 369/006 | | | Medal of Honor: Airborne | 2859 | 421/011 | | | Frontlines: Fuel of War | 2917 | 433/052 | Poor output of functions | diff --git a/src/Branch/PackageObjectLegacyVersion.cs b/src/Branch/PackageObjectLegacyVersion.cs index 91723d92..d385d705 100644 --- a/src/Branch/PackageObjectLegacyVersion.cs +++ b/src/Branch/PackageObjectLegacyVersion.cs @@ -40,6 +40,8 @@ public enum PackageObjectLegacyVersion /// PrimitiveCastTokenAdded = 95, + AddedHideCategoriesToUClass = 99, + LightMapScaleAddedToPoly = 106, KerningAddedToUFont = 119, @@ -54,9 +56,7 @@ public enum PackageObjectLegacyVersion // FIXME: Version, def not <= 178, found in GoW but no version check, so this approximation should do :) TemplateDataAddedToUComponent = 200, - - DisplacedScriptPropertiesWithClassDefaultObject = 200, - + /// /// Present in all released UE3 games (starting with RoboBlitz). /// @@ -64,6 +64,7 @@ public enum PackageObjectLegacyVersion /// IsLocalAddedToDelegateFunctionToken = 181, + AddedAutoExpandCategoriesToUClass = 185, ClassDependenciesDeprecated = 186, // FIXME: Version @@ -76,13 +77,25 @@ public enum PackageObjectLegacyVersion EnumTagNameAddedToBytePropertyTag = UE3, ObjectFlagsSizeChangedToULong = 195, + + // 208 according to EndWar + PackageImportsDeprecated = 208, + + // 210 according to EndWar + AddedComponentTemplatesToUClass = 210, + + // 219 according to EndWar + DisplacedScriptPropertiesWithClassDefaultObject = 219, + ArchetypeAddedToExports = 220, - ComponentClassBridgeMapDeprecated = 248, + // 222 according to EndWar + AddedInterfacesFeature = 222, // 227 according to the GoW client FixedVerticesToArrayFromPoly = 227, + ComponentClassBridgeMapDeprecated = 248, SerialSizeConditionRemoved = 249, // Thanks to @https://www.gildor.org/ for reverse-engineering the lazy-loader version changes. @@ -137,6 +150,9 @@ public enum PackageObjectLegacyVersion AddedTextureFileCacheGuidToTexture2D = 567, LightmassAdded = 600, + + AddedDontSortCategoriesToUClass = 603, + UProcBuildingReferenceAddedToPoly = 606, EnumNameAddedToBytePropertyTag = 633, @@ -146,6 +162,9 @@ public enum PackageObjectLegacyVersion // FIXME: Version EndTokenAppendedToArrayTokenIntrinsics = 649, LightmassShadowIndirectOnlyOptionAdded = 652, + + AddedDLLBindFeature = 655, + PolyRulesetVariationTypeChangedToName = 670, BoolValueToByteForBoolPropertyTag = 673, @@ -156,6 +175,9 @@ public enum PackageObjectLegacyVersion ForceScriptOrderAddedToUClass = 749, SuperReferenceMovedToUStruct = 756, + AddedClassGroupsToUClass = 789, + AddedNativeClassNameToUClass = 813, + AddedATITCToUTexture2D = 857, AddedETCToUTexture2D = 864, } diff --git a/src/Core/Classes/UClass.cs b/src/Core/Classes/UClass.cs index 1fda4489..675549eb 100644 --- a/src/Core/Classes/UClass.cs +++ b/src/Core/Classes/UClass.cs @@ -66,12 +66,13 @@ public void Deserialize(IUnrealStream stream) public UArray ClassDependencies; /// - /// A list of objects imported from a package. + /// A list of package names imported by this class. + /// + /// Will be null if not deserialized. /// - public IList PackageImports; + public UArray PackageImportNames; - [Obsolete("Use ComponentDefaultObjectMap")] - public IList Components = null; + [Obsolete("Use PackageImportNames")] public IList PackageImports; /// /// A map of default objects for the components that are instantiated by this class. @@ -83,6 +84,9 @@ public void Deserialize(IUnrealStream stream) /// public UMap ComponentDefaultObjectMap; + [Obsolete("Use ComponentDefaultObjectMap")] + public IList Components = null; + /// /// Index of unsorted categories names into the NameTableList. /// UE3 @@ -206,19 +210,18 @@ protected override void Deserialize() Record(nameof(ClassDependencies), ClassDependencies); } - // FIXME: version - if (_Buffer.Version < 220) + if (_Buffer.Version < (uint)PackageObjectLegacyVersion.PackageImportsDeprecated) { - PackageImports = DeserializeGroup(nameof(PackageImports)); + _Buffer.ReadArray(out PackageImportNames); + Record(nameof(PackageImportNames), PackageImportNames); } serializeWithin: if (_Buffer.Version >= 62) { - // Class Name Extends Super.Name Within _WithinIndex - // Config(_ConfigIndex); Within = _Buffer.ReadObject(); Record(nameof(Within), Within); + ConfigName = _Buffer.ReadNameReference(); Record(nameof(ConfigName), ConfigName); #if DNF @@ -257,8 +260,7 @@ protected override void Deserialize() #endif ; - // +HideCategories - if (_Buffer.Version >= 99) + if (_Buffer.Version >= (uint)PackageObjectLegacyVersion.AddedHideCategoriesToUClass) { // FIXME: >= version if (_Buffer.Version >= 178 @@ -271,15 +273,14 @@ protected override void Deserialize() } // FIXME: >= version - if (_Buffer.Version >= 178 && _Buffer.Version < - (uint)PackageObjectLegacyVersion.ComponentClassBridgeMapDeprecated) + if (_Buffer.Version >= 178 && + _Buffer.Version < (uint)PackageObjectLegacyVersion.ComponentClassBridgeMapDeprecated) { _Buffer.ReadMap(out UMap componentClassBridgeMap); Record(nameof(componentClassBridgeMap), componentClassBridgeMap); } - // FIXME: >= version (187-223) - if (_Buffer.Version >= 220 && + if (_Buffer.Version >= (uint)PackageObjectLegacyVersion.AddedComponentTemplatesToUClass && _Buffer.Version < (uint)PackageObjectLegacyVersion.ComponentTemplatesDeprecated) { _Buffer.ReadArray(out UArray componentTemplates); @@ -293,40 +294,38 @@ protected override void Deserialize() Record(nameof(ComponentDefaultObjectMap), ComponentDefaultObjectMap); } - // FIXME: >= version (187-223) - if (_Buffer.Version >= 220) + if (_Buffer.Version >= (uint)PackageObjectLegacyVersion.AddedInterfacesFeature && + _Buffer.Version < (uint)PackageObjectLegacyVersion.InterfaceClassesDeprecated) { - if (_Buffer.Version < (uint)PackageObjectLegacyVersion.InterfaceClassesDeprecated) - { - _Buffer.ReadArray(out UArray interfaceClasses); - Record(nameof(interfaceClasses), interfaceClasses); - } - else + _Buffer.ReadArray(out UArray interfaceClasses); + Record(nameof(interfaceClasses), interfaceClasses); + } + + if (_Buffer.Version >= (uint)PackageObjectLegacyVersion.InterfaceClassesDeprecated) + { + // See http://udn.epicgames.com/Three/UnrealScriptInterfaces.html + int interfacesCount = _Buffer.ReadInt32(); + Record("Implements.Count", interfacesCount); + if (interfacesCount > 0) { - // See http://udn.epicgames.com/Three/UnrealScriptInterfaces.html - int interfacesCount = _Buffer.ReadInt32(); - Record("Implements.Count", interfacesCount); - if (interfacesCount > 0) + AssertEOS(interfacesCount * 8, "Implemented"); + ImplementedInterfaces = new List(interfacesCount); + for (int i = 0; i < interfacesCount; ++i) { - AssertEOS(interfacesCount * 8, "Implemented"); - ImplementedInterfaces = new List(interfacesCount); - for (int i = 0; i < interfacesCount; ++i) - { - int interfaceIndex = _Buffer.ReadInt32(); - Record("Implemented.InterfaceIndex", interfaceIndex); - int typeIndex = _Buffer.ReadInt32(); - Record("Implemented.TypeIndex", typeIndex); - ImplementedInterfaces.Add(interfaceIndex); + int interfaceIndex = _Buffer.ReadInt32(); + Record("Implemented.InterfaceIndex", interfaceIndex); + int typeIndex = _Buffer.ReadInt32(); + Record("Implemented.TypeIndex", typeIndex); + ImplementedInterfaces.Add(interfaceIndex); #if UE4 - if (_Buffer.UE4Version <= 0) - { - continue; - } + if (_Buffer.UE4Version <= 0) + { + continue; + } - bool isImplementedByK2 = _Buffer.ReadInt32() > 0; - Record("Implemented.isImplementedByK2", isImplementedByK2); + bool isImplementedByK2 = _Buffer.ReadInt32() > 0; + Record("Implemented.isImplementedByK2", isImplementedByK2); #endif - } } } } @@ -348,42 +347,42 @@ protected override void Deserialize() if (!Package.IsConsoleCooked() && !Package.Build.Flags.HasFlag(BuildFlags.XenonCooked)) { - if (_Buffer.Version >= 603 && _Buffer.UE4Version < 113 + if (_Buffer.Version >= (uint)PackageObjectLegacyVersion.AddedDontSortCategoriesToUClass && + _Buffer.UE4Version < 113 #if TERA - && Package.Build != UnrealPackage.GameBuild.BuildName.Tera + && Package.Build != UnrealPackage.GameBuild.BuildName.Tera #endif ) + { DontSortCategories = DeserializeGroup("DontSortCategories"); + } // FIXME: Added in v99, removed in ~220? if (_Buffer.Version < 220 || !isHideCategoriesOldOrder) { HideCategories = DeserializeGroup("HideCategories"); + } #if SPELLBORN - if (Package.Build == UnrealPackage.GameBuild.BuildName.Spellborn) - { - uint replicationFlags = _Buffer.ReadUInt32(); - Record(nameof(replicationFlags), replicationFlags); - } -#endif + if (Package.Build == UnrealPackage.GameBuild.BuildName.Spellborn) + { + uint replicationFlags = _Buffer.ReadUInt32(); + Record(nameof(replicationFlags), replicationFlags); } - - // +AutoExpandCategories - if (_Buffer.Version >= 185) +#endif + if (_Buffer.Version >= (uint)PackageObjectLegacyVersion.AddedAutoExpandCategoriesToUClass) { // 490:GoW1, 576:CrimeCraft if (!HasClassFlag(Flags.ClassFlags.CollapseCategories) || _Buffer.Version <= vHideCategoriesOldOrder || _Buffer.Version >= 576) AutoExpandCategories = DeserializeGroup("AutoExpandCategories"); + } #if TRANSFORMERS - if (Package.Build == BuildGeneration.HMS) - { - _Buffer.ReadArray(out UArray hmsConstructors); - Record(nameof(hmsConstructors), hmsConstructors); - } -#endif + if (Package.Build == BuildGeneration.HMS) + { + _Buffer.ReadArray(out UArray hmsConstructors); + Record(nameof(hmsConstructors), hmsConstructors); } - +#endif // FIXME: Wrong version, no version checks found in games that DO have checks for version 600+ if (_Buffer.Version > 670 #if BORDERLANDS @@ -453,14 +452,15 @@ protected override void Deserialize() goto skipClassGroups; } #endif - if (_Buffer.Version >= UnrealPackage.VCLASSGROUP) + if (_Buffer.Version >= (uint)PackageObjectLegacyVersion.AddedClassGroupsToUClass) { ClassGroups = DeserializeGroup("ClassGroups"); - if (_Buffer.Version >= 813) - { - NativeClassName = _Buffer.ReadText(); - Record(nameof(NativeClassName), NativeClassName); - } + } + + if (_Buffer.Version >= (uint)PackageObjectLegacyVersion.AddedNativeClassNameToUClass) + { + NativeClassName = _Buffer.ReadText(); + Record(nameof(NativeClassName), NativeClassName); } skipClassGroups: ; @@ -481,14 +481,14 @@ protected override void Deserialize() { int unknownInt32 = _Buffer.ReadInt32(); Record("Unknown", unknownInt32); + } #if SINGULARITY - if (Package.Build == UnrealPackage.GameBuild.BuildName.Singularity) - { - _Buffer.Skip(8); - _Buffer.ConformRecordPosition(); - } -#endif + if (Package.Build == UnrealPackage.GameBuild.BuildName.Singularity) + { + _Buffer.Skip(8); + _Buffer.ConformRecordPosition(); } +#endif } #if BATMAN if (Package.Build == BuildGeneration.RSS) @@ -514,7 +514,8 @@ protected override void Deserialize() Record(nameof(v2b0), v2b0); } #endif - if (_Buffer.Version >= UnrealPackage.VDLLBIND && _Buffer.UE4Version < 117) + if (_Buffer.Version >= (uint)PackageObjectLegacyVersion.AddedDLLBindFeature && + _Buffer.UE4Version < 117) { DLLBindName = _Buffer.ReadNameReference(); Record(nameof(DLLBindName), DLLBindName); @@ -528,7 +529,9 @@ protected override void Deserialize() #endif #if DISHONORED if (Package.Build == UnrealPackage.GameBuild.BuildName.Dishonored) + { ClassGroups = DeserializeGroup("ClassGroups"); + } #endif #if BORDERLANDS2 || BATTLEBORN if (Package.Build == UnrealPackage.GameBuild.BuildName.Borderlands2 || @@ -654,7 +657,7 @@ protected override void Deserialize() scriptProperties: if (_Buffer.Version >= (uint)PackageObjectLegacyVersion.DisplacedScriptPropertiesWithClassDefaultObject) { - // DEFAULT_ClassName + // Default__ClassName Default = _Buffer.ReadObject(); Record(nameof(Default), Default); } diff --git a/src/Core/Classes/UFunctionDecompiler.cs b/src/Core/Classes/UFunctionDecompiler.cs index e01d318a..9295eb73 100644 --- a/src/Core/Classes/UFunctionDecompiler.cs +++ b/src/Core/Classes/UFunctionDecompiler.cs @@ -1,6 +1,7 @@ #if DECOMPILE using System; using System.Linq; +using UELib.Branch; using UELib.Flags; namespace UELib.Core @@ -42,7 +43,7 @@ private string FormatFlags() output += "protected "; } - if (Package.Version >= UnrealPackage.VDLLBIND && HasFunctionFlag(Flags.FunctionFlags.DLLImport)) + if (Package.Version >= (uint)PackageObjectLegacyVersion.AddedDLLBindFeature && HasFunctionFlag(Flags.FunctionFlags.DLLImport)) { output += "dllimport "; } @@ -355,4 +356,4 @@ private string FormatCode() } } } -#endif \ No newline at end of file +#endif diff --git a/src/Core/Classes/UScriptStruct.cs b/src/Core/Classes/UScriptStruct.cs index 9cbea421..f3a08ec2 100644 --- a/src/Core/Classes/UScriptStruct.cs +++ b/src/Core/Classes/UScriptStruct.cs @@ -12,7 +12,7 @@ protected override void Deserialize() base.Deserialize(); // FIXME: Version - if (_Buffer.Version >= 221) + if (_Buffer.Version >= 223) { StructFlags = _Buffer.ReadUInt32(); Record(nameof(StructFlags), (StructFlags)StructFlags); diff --git a/src/Core/Classes/UStruct.cs b/src/Core/Classes/UStruct.cs index 9e50d59b..bf46cb3c 100644 --- a/src/Core/Classes/UStruct.cs +++ b/src/Core/Classes/UStruct.cs @@ -200,6 +200,7 @@ protected override void Deserialize() Record(nameof(Line), Line); TextPos = _Buffer.ReadInt32(); Record(nameof(TextPos), TextPos); + // Version < 200 (EndWar) //var MinAlignment = _Buffer.ReadInt32(); //Record(nameof(MinAlignment), MinAlignment); } diff --git a/src/UnrealPackage.cs b/src/UnrealPackage.cs index c385e685..0b201b73 100644 --- a/src/UnrealPackage.cs +++ b/src/UnrealPackage.cs @@ -331,14 +331,16 @@ public enum BuildName /// [Build(159, 29u, BuildGeneration.UE2_5)] Spellborn, - + /// /// EndWar /// /// 369/006 /// - [Build(329, 0)] [OverridePackageVersion(220)] EndWar, - + [Build(329, 0)] + [OverridePackageVersion((uint)PackageObjectLegacyVersion.AddedInterfacesFeature)] + EndWar, + /// /// Standard /// From 71ea1b48c60218f7e42e3b7f82cd37559c3255d5 Mon Sep 17 00:00:00 2001 From: Eliot Date: Sun, 1 Sep 2024 10:02:23 +0200 Subject: [PATCH 49/50] Cleanup... --- Test/Eliot.UELib.Test.csproj | 6 +- Test/Packages.Designer.cs | 10 +-- Test/Packages.resx | 10 +-- Test/upk/Builds/PackageTests.AA2.cs | 2 +- Test/upk/Builds/PackageTests.All.cs | 2 +- src/Branch/PackageObjectLegacyVersion.cs | 72 +++++++++++++++---- src/Branch/UE3/RL/EngineBranch.RL.cs | 10 +-- src/Branch/UE4/PackageSerializer.UE4.cs | 2 +- src/Core/Classes/Props/UDelegateProperty.cs | 9 ++- src/Core/Classes/Props/UProperty.cs | 14 ++-- src/Core/Classes/UClass.cs | 24 +++---- src/Core/Classes/UConst.cs | 5 +- src/Core/Classes/UDefaultProperty.cs | 2 +- src/Core/Classes/UEnum.cs | 18 ++--- src/Core/Classes/UFunction.cs | 30 ++++---- src/Core/Classes/UMetaData.cs | 8 ++- src/Core/Classes/UState.cs | 31 ++++---- src/Core/Classes/UStruct.cs | 54 ++++++-------- src/Core/Classes/UStructDecompiler.cs | 12 ++-- src/Core/Classes/UTextBuffer.cs | 2 +- src/Core/Tables/UExportTableItem.cs | 80 +++++++++++---------- src/Core/Tables/UNameTableItem.cs | 11 +-- src/Core/Tokens/CastTokens.cs | 3 +- src/Core/UStateFrame.cs | 22 +++--- src/UnrealMod.cs | 2 +- src/UnrealPackage.cs | 10 +-- src/UnrealStream.cs | 6 +- 27 files changed, 249 insertions(+), 208 deletions(-) diff --git a/Test/Eliot.UELib.Test.csproj b/Test/Eliot.UELib.Test.csproj index 90034400..1836be7b 100644 --- a/Test/Eliot.UELib.Test.csproj +++ b/Test/Eliot.UELib.Test.csproj @@ -70,5 +70,7 @@ PreserveNewest - - + + + + \ No newline at end of file diff --git a/Test/Packages.Designer.cs b/Test/Packages.Designer.cs index 22670303..12aba38b 100644 --- a/Test/Packages.Designer.cs +++ b/Test/Packages.Designer.cs @@ -61,11 +61,11 @@ internal Packages() { } /// - /// Looks up a localized string similar to D:\Projecten\UE Explorer Tests\Supported. + /// Looks up a localized string similar to /Samples/. /// - public static string Packages_Path { + public static string PackageFilesPath { get { - return ResourceManager.GetString("Packages_Path", resourceCulture); + return ResourceManager.GetString("PackageFilesPath", resourceCulture); } } @@ -90,7 +90,7 @@ public static byte[] TestUC3 { } /// - /// Looks up a localized string similar to C:\UT2004\Maps\Stock. + /// Looks up a localized string similar to C:\UT2004\Maps. /// public static string UE2MapFilesPath { get { @@ -99,7 +99,7 @@ public static string UE2MapFilesPath { } /// - /// Looks up a localized string similar to C:\UT2004\Textures\Stock. + /// Looks up a localized string similar to C:\UT2004\Textures. /// public static string UE2MaterialFilesPath { get { diff --git a/Test/Packages.resx b/Test/Packages.resx index 9c516019..def5078c 100644 --- a/Test/Packages.resx +++ b/Test/Packages.resx @@ -117,8 +117,8 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - D:\Projecten\UE Explorer Tests\Supported + + /Samples/ Path to directory with all known .upk files that are compatible (no errors on load). @@ -129,9 +129,9 @@ upk\testuc3\testuc3.u;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - C:\UT2004\Maps\Stock + C:\UT2004\Maps - C:\UT2004\Textures\Stock + C:\UT2004\Textures - \ No newline at end of file + diff --git a/Test/upk/Builds/PackageTests.AA2.cs b/Test/upk/Builds/PackageTests.AA2.cs index f1932efe..56038639 100644 --- a/Test/upk/Builds/PackageTests.AA2.cs +++ b/Test/upk/Builds/PackageTests.AA2.cs @@ -14,7 +14,7 @@ namespace Eliot.UELib.Test.upk.Builds public class PackageTestsAA2 { // Testing the "Arcade" packages only - private static readonly string PackagesPath = Packages.Packages_Path; + private static readonly string PackagesPath = Packages.PackageFilesPath; private static readonly string NoEncryptionCorePackagePath = Path.Join(PackagesPath, "(V128_032,Encrypted)AAA_2_6_Core.u"); private static readonly string EncryptedCorePackagePath = Path.Join(PackagesPath, "(V128_032,Encrypted)AAA_2_6_Core.u"); diff --git a/Test/upk/Builds/PackageTests.All.cs b/Test/upk/Builds/PackageTests.All.cs index d8dc0b83..a30f4c10 100644 --- a/Test/upk/Builds/PackageTests.All.cs +++ b/Test/upk/Builds/PackageTests.All.cs @@ -12,7 +12,7 @@ namespace Eliot.UELib.Test.upk.Builds [TestClass] public class PackageTestsAll { - private static readonly string s_packagesPath = Packages.Packages_Path; + private static readonly string s_packagesPath = Packages.PackageFilesPath; /// /// FIXME: Beware, we are experiencing a memory leak in this chain of events. diff --git a/src/Branch/PackageObjectLegacyVersion.cs b/src/Branch/PackageObjectLegacyVersion.cs index d385d705..4e9f029d 100644 --- a/src/Branch/PackageObjectLegacyVersion.cs +++ b/src/Branch/PackageObjectLegacyVersion.cs @@ -1,18 +1,13 @@ -using System.Runtime.CompilerServices; - -namespace UELib.Branch +namespace UELib.Branch { public enum PackageObjectLegacyVersion { Undefined = 0, /// - /// This is one particular update with A LOT of general package changes. + /// FIXME: Version 61 is the lowest package version I know that supports StateFlags. /// - ReturnExpressionAddedToReturnToken = 62, - - SphereExtendsPlane = 62, - LazyArraySkipCountChangedToSkipOffset = 62, + AddedStateFlagsToUState = 61, /// /// This should mark the first approximated version with dynamic arrays that are accessible using UnrealScript. @@ -21,6 +16,19 @@ public enum PackageObjectLegacyVersion /// DynamicArrayTokensAdded = 62, + /// + /// Mixed changes. + /// + Release62 = 62, + ReturnExpressionAddedToReturnToken = 62, + SphereExtendsPlane = 62, + LazyArraySkipCountChangedToSkipOffset = 62, + + /// + /// Mixed changes. + /// + Release64 = 64, + CharRemapAddedToUFont = 69, /// @@ -45,8 +53,12 @@ public enum PackageObjectLegacyVersion LightMapScaleAddedToPoly = 106, KerningAddedToUFont = 119, + AddedCppTextToUStruct = 120, FontPagesDisplaced = 122, + // FIXME: Version + MovedFriendlyNameToUFunction = 160, + // The estimated version changes that came after the latest known UE2 build. TextureDeprecatedFromPoly = 170, MaterialAddedToPoly = 170, @@ -54,9 +66,6 @@ public enum PackageObjectLegacyVersion UE3 = 178, CompactIndexDeprecated = 178, - // FIXME: Version, def not <= 178, found in GoW but no version check, so this approximation should do :) - TemplateDataAddedToUComponent = 200, - /// /// Present in all released UE3 games (starting with RoboBlitz). /// @@ -64,6 +73,12 @@ public enum PackageObjectLegacyVersion /// IsLocalAddedToDelegateFunctionToken = 181, + // FIXME: Version, added somewhere between 186 ... 230 + AddedStateStackToUStateFrame = 187, + + // FIXME: Version 128-178 + AddedDelegateSourceToUDelegateProperty = 185, + AddedAutoExpandCategoriesToUClass = 185, ClassDependenciesDeprecated = 186, @@ -76,25 +91,37 @@ public enum PackageObjectLegacyVersion // FIXME: Version EnumTagNameAddedToBytePropertyTag = UE3, - ObjectFlagsSizeChangedToULong = 195, + ObjectFlagsSizeExpandedTo64Bits = 195, + + // FIXME: Version, def not <= 178, found in GoW but no version check, so this approximation should do :) + TemplateDataAddedToUComponent = 200, // 208 according to EndWar PackageImportsDeprecated = 208, - + // 210 according to EndWar AddedComponentTemplatesToUClass = 210, // 219 according to EndWar DisplacedScriptPropertiesWithClassDefaultObject = 219, + // FIXME: Version + AddedFuncMapToUState = 220, + + /// + /// And ComponentMap + /// ArchetypeAddedToExports = 220, + PropertyFlagsSizeExpandedTo64Bits = 220, + // 222 according to EndWar AddedInterfacesFeature = 222, // 227 according to the GoW client FixedVerticesToArrayFromPoly = 227, + ExportFlagsAddedToExports = 247, ComponentClassBridgeMapDeprecated = 248, SerialSizeConditionRemoved = 249, @@ -112,6 +139,7 @@ public enum PackageObjectLegacyVersion ClassDefaultCheckAddedToTemplateName = 267, ComponentGuidDeprecated = 273, + ClassGuidDeprecated = 276, InterfaceClassesDeprecated = 288, @@ -133,12 +161,17 @@ public enum PackageObjectLegacyVersion NumberAddedToName = 343, + // FIXME: Version 374-491; Delegate source type changed from Name to Object + ChangedDelegateSourceFromNameToObject = 376, + // 417 according to the GoW client LightingChannelsAddedToPoly = 417, // FIXME: Version, not attested in (RoboBlitz v369, but attested in GoW v490). SkipSizeAddedToArrayFindTokenIntrinsics = 400, + AddedArrayEnumToUProperty = 401, + // FIXME: Version, not attested in (GoW v490) SkipSizeAddedToArrayTokenIntrinsics = 491, @@ -147,6 +180,13 @@ public enum PackageObjectLegacyVersion ComponentMapDeprecated = 543, + /// + /// Added with + /// + ClassPlatformFlagsDeprecated = 547, + + StateFrameLatentActionReduced = 566, + AddedTextureFileCacheGuidToTexture2D = 567, LightmassAdded = 600, @@ -159,19 +199,21 @@ public enum PackageObjectLegacyVersion LightmassExplicitEmissiveLightRadiusAdded = 636, + AddedDataScriptSizeToUStruct = 639, + // FIXME: Version EndTokenAppendedToArrayTokenIntrinsics = 649, LightmassShadowIndirectOnlyOptionAdded = 652, AddedDLLBindFeature = 655, - + PolyRulesetVariationTypeChangedToName = 670, BoolValueToByteForBoolPropertyTag = 673, AddedPVRTCToUTexture2D = 674, - ProbeMaskReducedAndIgnoreMaskRemoved = 692, + ProbeMaskReducedAndIgnoreMaskRemoved = 691, ForceScriptOrderAddedToUClass = 749, SuperReferenceMovedToUStruct = 756, diff --git a/src/Branch/UE3/RL/EngineBranch.RL.cs b/src/Branch/UE3/RL/EngineBranch.RL.cs index f032d3f7..02fbc6ee 100644 --- a/src/Branch/UE3/RL/EngineBranch.RL.cs +++ b/src/Branch/UE3/RL/EngineBranch.RL.cs @@ -31,14 +31,14 @@ protected override TokenMap BuildTokenMap(UnrealPackage linker) { 0x02, typeof(BadToken) }, { 0x03, typeof(BadToken) }, { 0x04, typeof(UnresolvedToken) }, - { 0x05, typeof(UnresolvedToken) }, + { 0x05, typeof(IntZeroToken) }, { 0x06, typeof(UnresolvedToken) }, { 0x07, typeof(BadToken) }, { 0x08, typeof(UnresolvedToken) }, { 0x09, typeof(UnresolvedToken) }, { 0x0A, typeof(FloatConstToken) }, { 0x0B, typeof(OutVariableToken) }, - { 0x0C, typeof(UnresolvedToken) }, + { 0x0C, typeof(VirtualFunctionToken) }, { 0x0D, typeof(BadToken) }, { 0x0E, typeof(UnresolvedToken) }, { 0x0F, typeof(UnresolvedToken) }, @@ -48,7 +48,7 @@ protected override TokenMap BuildTokenMap(UnrealPackage linker) { 0x13, typeof(BadToken) }, { 0x14, typeof(BadToken) }, { 0x15, typeof(UnresolvedToken) }, - { 0x16, typeof(UnresolvedToken) }, + { 0x16, typeof(NewToken) }, { 0x17, typeof(UnresolvedToken) }, { 0x18, typeof(UnresolvedToken) }, { 0x19, typeof(UnresolvedToken) }, @@ -74,7 +74,7 @@ protected override TokenMap BuildTokenMap(UnrealPackage linker) { 0x2D, typeof(UnresolvedToken) }, { 0x2E, typeof(UnresolvedToken) }, { 0x2F, typeof(BadToken) }, - { 0x30, typeof(UnresolvedToken) }, + { 0x30, typeof(ClassContextToken) }, { 0x31, typeof(BadToken) }, { 0x32, typeof(UnresolvedToken) }, { 0x33, typeof(BadToken) }, @@ -88,7 +88,7 @@ protected override TokenMap BuildTokenMap(UnrealPackage linker) { 0x3B, typeof(FinalFunctionToken) }, { 0x3C, typeof(UnresolvedToken) }, { 0x3D, typeof(UnresolvedToken) }, - { 0x3E, typeof(StructMemberToken) }, + { 0x3E, typeof(ContextToken) }, { 0x3F, typeof(UnresolvedToken) }, { 0x40, typeof(UnresolvedToken) }, { 0x41, typeof(PrimitiveCastToken) }, diff --git a/src/Branch/UE4/PackageSerializer.UE4.cs b/src/Branch/UE4/PackageSerializer.UE4.cs index eb0b6693..dc456c15 100644 --- a/src/Branch/UE4/PackageSerializer.UE4.cs +++ b/src/Branch/UE4/PackageSerializer.UE4.cs @@ -29,7 +29,7 @@ public void Serialize(IUnrealStream stream, UNameTableItem item) public void Deserialize(IUnrealStream stream, UNameTableItem item) { - item.Name = stream.ReadText(); + item.Name = stream.ReadString(); Debug.Assert(item.Name.Length <= MaxNameLengthUE4, "Maximum name length exceeded! Possible corrupt or unsupported package."); if (stream.UE4Version < 504) return; diff --git a/src/Core/Classes/Props/UDelegateProperty.cs b/src/Core/Classes/Props/UDelegateProperty.cs index 5b88aa85..7f0df2fb 100644 --- a/src/Core/Classes/Props/UDelegateProperty.cs +++ b/src/Core/Classes/Props/UDelegateProperty.cs @@ -1,4 +1,5 @@ -using UELib.Types; +using UELib.Branch; +using UELib.Types; namespace UELib.Core { @@ -31,14 +32,12 @@ protected override void Deserialize() Function = _Buffer.ReadObject(); Record(nameof(Function), Function); - // FIXME: Version 128-178 - if (_Buffer.Version <= 184) + if (_Buffer.Version < (uint)PackageObjectLegacyVersion.AddedDelegateSourceToUDelegateProperty) { return; } - // FIXME: Version 374-491; Delegate source type changed from Name to Object - if (_Buffer.Version <= 375) + if (_Buffer.Version < (uint)PackageObjectLegacyVersion.ChangedDelegateSourceFromNameToObject) { var source = _Buffer.ReadNameReference(); Record(nameof(source), source); diff --git a/src/Core/Classes/Props/UProperty.cs b/src/Core/Classes/Props/UProperty.cs index 71005661..a3bfe0a5 100644 --- a/src/Core/Classes/Props/UProperty.cs +++ b/src/Core/Classes/Props/UProperty.cs @@ -1,6 +1,6 @@ using System; -using System.Diagnostics; using UELib.Annotations; +using UELib.Branch; using UELib.Flags; using UELib.Types; @@ -80,7 +80,7 @@ protected override void Deserialize() Record(nameof(ArrayDim), ArrayDim); PropertyFlags = _Buffer.ReadUInt32(); - Record(nameof(PropertyFlags), PropertyFlags); + Record(nameof(PropertyFlags), (PropertyFlagsLO)PropertyFlags); _Buffer.Read(out CategoryName); Record(nameof(CategoryName), CategoryName); @@ -117,10 +117,10 @@ protected override void Deserialize() // (ArrayDim & 0x0000FFFFU) > 0 && (ArrayDim & 0x0000FFFFU) <= 2048, // $"Bad array dimension {ArrayDim & 0x0000FFFFU} for property ${GetReferencePath()}"); - PropertyFlags = Package.Version >= 220 + PropertyFlags = Package.Version >= (uint)PackageObjectLegacyVersion.PropertyFlagsSizeExpandedTo64Bits ? _Buffer.ReadUInt64() : _Buffer.ReadUInt32(); - Record(nameof(PropertyFlags), PropertyFlags); + Record(nameof(PropertyFlags), (PropertyFlagsLO)PropertyFlags); #if BATMAN if (Package.Build == BuildGeneration.RSS) { @@ -161,7 +161,7 @@ protected override void Deserialize() Record(nameof(CategoryName), CategoryName); } - if (_Buffer.Version > 400) + if (_Buffer.Version >= (uint)PackageObjectLegacyVersion.AddedArrayEnumToUProperty) { ArrayEnum = _Buffer.ReadObject(); Record(nameof(ArrayEnum), ArrayEnum); @@ -229,7 +229,7 @@ protected override void Deserialize() { if (HasPropertyFlag(0x800000)) { - EditorDataText = _Buffer.ReadText(); + EditorDataText = _Buffer.ReadString(); Record(nameof(EditorDataText), EditorDataText); } @@ -262,7 +262,7 @@ protected override void Deserialize() ) { // May represent a tooltip/comment in some games. Usually in the form of a quoted string, sometimes as a double-flash comment or both. - EditorDataText = _Buffer.ReadText(); + EditorDataText = _Buffer.ReadString(); Record(nameof(EditorDataText), EditorDataText); } #if SPELLBORN diff --git a/src/Core/Classes/UClass.cs b/src/Core/Classes/UClass.cs index 675549eb..8d75083f 100644 --- a/src/Core/Classes/UClass.cs +++ b/src/Core/Classes/UClass.cs @@ -153,7 +153,7 @@ protected override void Deserialize() VengeanceDeserializeHeader(_Buffer, ref header); } #endif - if (_Buffer.Version < 62) + if (_Buffer.Version < (uint)PackageObjectLegacyVersion.Release62) { int classRecordSize = _Buffer.ReadInt32(); Record(nameof(classRecordSize), classRecordSize); @@ -188,12 +188,12 @@ protected override void Deserialize() goto skipClassGuid; } #endif - if (_Buffer.Version >= 276) + if (_Buffer.Version >= (uint)PackageObjectLegacyVersion.ClassGuidDeprecated) { - if (_Buffer.Version < 547) + if (_Buffer.Version < (uint)PackageObjectLegacyVersion.ClassPlatformFlagsDeprecated) { - byte unknownByte = _Buffer.ReadByte(); - Record("ClassGuidReplacement???", unknownByte); + byte classPlatformFlags = _Buffer.ReadByte(); + Record(nameof(classPlatformFlags), classPlatformFlags); } } else @@ -217,7 +217,7 @@ protected override void Deserialize() } serializeWithin: - if (_Buffer.Version >= 62) + if (_Buffer.Version >= (uint)PackageObjectLegacyVersion.Release62) { Within = _Buffer.ReadObject(); Record(nameof(Within), Within); @@ -434,7 +434,7 @@ protected override void Deserialize() byte unknownByte = _Buffer.ReadByte(); Record("Unknown:Battleborn", unknownByte); - NativeClassName = _Buffer.ReadText(); + NativeClassName = _Buffer.ReadString(); Record(nameof(NativeClassName), NativeClassName); // not verified @@ -446,7 +446,7 @@ protected override void Deserialize() #if DISHONORED if (Package.Build == UnrealPackage.GameBuild.BuildName.Dishonored) { - NativeClassName = _Buffer.ReadText(); + NativeClassName = _Buffer.ReadString(); Record(nameof(NativeClassName), NativeClassName); goto skipClassGroups; @@ -459,7 +459,7 @@ protected override void Deserialize() if (_Buffer.Version >= (uint)PackageObjectLegacyVersion.AddedNativeClassNameToUClass) { - NativeClassName = _Buffer.ReadText(); + NativeClassName = _Buffer.ReadString(); Record(nameof(NativeClassName), NativeClassName); } @@ -555,7 +555,7 @@ protected override void Deserialize() #if THIEF_DS || DeusEx_IW if (Package.Build == BuildGeneration.Flesh) { - string thiefClassVisibleName = _Buffer.ReadText(); + string thiefClassVisibleName = _Buffer.ReadString(); Record(nameof(thiefClassVisibleName), thiefClassVisibleName); // Restore the human-readable name if possible @@ -584,13 +584,13 @@ protected override void Deserialize() if (_Buffer.LicenseeVersion >= 2) { - string vengeanceDefaultPropertiesText = _Buffer.ReadText(); + string vengeanceDefaultPropertiesText = _Buffer.ReadString(); Record(nameof(vengeanceDefaultPropertiesText), vengeanceDefaultPropertiesText); } if (_Buffer.LicenseeVersion >= 6) { - string vengeanceClassFilePath = _Buffer.ReadText(); + string vengeanceClassFilePath = _Buffer.ReadString(); Record(nameof(vengeanceClassFilePath), vengeanceClassFilePath); } diff --git a/src/Core/Classes/UConst.cs b/src/Core/Classes/UConst.cs index efec699a..833bc476 100644 --- a/src/Core/Classes/UConst.cs +++ b/src/Core/Classes/UConst.cs @@ -20,10 +20,11 @@ public partial class UConst : UField protected override void Deserialize() { base.Deserialize(); - Value = _Buffer.ReadText(); + + Value = _Buffer.ReadString(); Record(nameof(Value), Value); } #endregion } -} \ No newline at end of file +} diff --git a/src/Core/Classes/UDefaultProperty.cs b/src/Core/Classes/UDefaultProperty.cs index 53a2d05f..a649c07c 100644 --- a/src/Core/Classes/UDefaultProperty.cs +++ b/src/Core/Classes/UDefaultProperty.cs @@ -615,7 +615,7 @@ private string DeserializeDefaultPropertyValue(PropertyType type, ref Deserializ case PropertyType.StrProperty: { - string value = _Buffer.ReadText(); + string value = _Buffer.ReadString(); Record(nameof(value), value); propertyValue = PropertyDisplay.FormatLiteral(value); break; diff --git a/src/Core/Classes/UEnum.cs b/src/Core/Classes/UEnum.cs index 2527e7fa..8ff7295d 100644 --- a/src/Core/Classes/UEnum.cs +++ b/src/Core/Classes/UEnum.cs @@ -1,6 +1,4 @@ -using System.Collections.Generic; - -namespace UELib.Core +namespace UELib.Core { /// /// Represents a unreal enum. @@ -13,7 +11,7 @@ public partial class UEnum : UField /// /// Names of each element in the UEnum. /// - public IList Names; + public UArray Names; #endregion @@ -23,12 +21,8 @@ protected override void Deserialize() { base.Deserialize(); - int count = _Buffer.ReadLength(); - Names = new List(count); - for (var i = 0; i < count; ++i) - { - Names.Add(_Buffer.ReadNameReference()); - } + _Buffer.ReadArray(out Names); + Record(nameof(Names), Names); #if SPELLBORN if (_Buffer.Package.Build == UnrealPackage.GameBuild.BuildName.Spellborn && 145 < _Buffer.Version) @@ -36,9 +30,9 @@ protected override void Deserialize() uint unknownEnumFlags = _Buffer.ReadUInt32(); Record(nameof(unknownEnumFlags), unknownEnumFlags); } - } #endif + } #endregion } -} \ No newline at end of file +} diff --git a/src/Core/Classes/UFunction.cs b/src/Core/Classes/UFunction.cs index afde8618..98d727d5 100644 --- a/src/Core/Classes/UFunction.cs +++ b/src/Core/Classes/UFunction.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using UELib.Branch; using UELib.Flags; namespace UELib.Core @@ -11,8 +12,6 @@ namespace UELib.Core [UnrealRegisterClass] public partial class UFunction : UStruct, IUnrealNetObject { - private const uint VFriendlyName = 189; - #region Serialized Members public ushort NativeToken { get; private set; } @@ -51,7 +50,6 @@ protected override void Deserialize() ushort size = _Buffer.ReadUShort(); _Buffer.Skip(size * 2); Record("Unknown:Borderlands2", size); - } #endif base.Deserialize(); @@ -65,11 +63,12 @@ protected override void Deserialize() RepOffset = _Buffer.ReadUShort(); Record(nameof(RepOffset), RepOffset); } + FriendlyName = ExportTable.ObjectName; return; } #endif - if (_Buffer.Version < 64) + if (_Buffer.Version < (uint)PackageObjectLegacyVersion.Release64) { ushort paramsSize = _Buffer.ReadUShort(); Record(nameof(paramsSize), paramsSize); @@ -78,7 +77,7 @@ protected override void Deserialize() NativeToken = _Buffer.ReadUShort(); Record(nameof(NativeToken), NativeToken); - if (_Buffer.Version < 64) + if (_Buffer.Version < (uint)PackageObjectLegacyVersion.Release64) { byte paramsCount = _Buffer.ReadByte(); Record(nameof(paramsCount), paramsCount); @@ -87,7 +86,7 @@ protected override void Deserialize() OperPrecedence = _Buffer.ReadByte(); Record(nameof(OperPrecedence), OperPrecedence); - if (_Buffer.Version < 64) + if (_Buffer.Version < (uint)PackageObjectLegacyVersion.Release64) { ushort returnValueOffset = _Buffer.ReadUShort(); Record(nameof(returnValueOffset), returnValueOffset); @@ -113,7 +112,7 @@ protected override void Deserialize() // HO:0x04 = Constructor uint v134 = _Buffer.ReadUInt32(); Record(nameof(v134), v134); - + FunctionFlags |= ((ulong)v134 << 32); } #endif @@ -135,11 +134,11 @@ protected override void Deserialize() } #endif - // TODO: Data-strip version? - if (_Buffer.Version >= VFriendlyName && !Package.IsConsoleCooked() + if (_Buffer.Version >= (uint)PackageObjectLegacyVersion.MovedFriendlyNameToUFunction && + !Package.IsConsoleCooked() #if TRANSFORMERS - // Cooked, but not stripped, However FriendlyName got stripped or deprecated. - && Package.Build != BuildGeneration.HMS + // Cooked, but not stripped, However FriendlyName got stripped or deprecated. + && Package.Build != BuildGeneration.HMS #endif #if MKKE // Cooked and stripped, but FriendlyName still remains @@ -170,14 +169,15 @@ protected override void FindChildren() } } -#endregion + #endregion #region Methods + public bool HasFunctionFlag(uint flag) { return ((uint)FunctionFlags & flag) != 0; } - + public bool HasFunctionFlag(FunctionFlags flag) { return ((uint)FunctionFlags & (uint)flag) != 0; @@ -210,12 +210,12 @@ public bool IsDelegate() public bool HasOptionalParamData() { // FIXME: Deprecate version check, and re-map the function flags using the EngineBranch class approach. - return Package.Version > 300 + return Package.Version > 300 && ByteCodeManager != null && Params?.Any() == true // Not available for older packages. // && HasFunctionFlag(Flags.FunctionFlags.OptionalParameters); - ; + ; } #endregion diff --git a/src/Core/Classes/UMetaData.cs b/src/Core/Classes/UMetaData.cs index aebcc698..536c0144 100644 --- a/src/Core/Classes/UMetaData.cs +++ b/src/Core/Classes/UMetaData.cs @@ -25,10 +25,11 @@ public void Serialize(IUnrealStream stream) public void Deserialize(IUnrealStream stream) { + // FIXME: Unversioned if (stream.Version <= 540) { // e.g. Core.Object.X - _FieldName = stream.ReadText(); + _FieldName = stream.ReadString(); } else { @@ -42,7 +43,7 @@ public void Deserialize(IUnrealStream stream) for (var i = 0; i < length; ++i) { var key = stream.ReadNameReference(); - string value = stream.ReadText(); + string value = stream.ReadString(); Tags.Add(key.Name, value); } } @@ -65,7 +66,7 @@ public string Decompile() public override string ToString() { - return _Field != null ? _Field.GetOuterGroup() : _FieldName; + return _Field != null ? _Field.GetPath() : _FieldName; } } @@ -86,6 +87,7 @@ public UArray Fields protected override void Deserialize() { base.Deserialize(); + _Buffer.ReadArray(out _Fields); Record(nameof(_Fields), _Fields); } diff --git a/src/Core/Classes/UState.cs b/src/Core/Classes/UState.cs index 9abb49b8..0220b9e4 100644 --- a/src/Core/Classes/UState.cs +++ b/src/Core/Classes/UState.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using UELib.Flags; using UELib.Branch; @@ -16,12 +17,7 @@ public struct ULabelEntry [UnrealRegisterClass] public partial class UState : UStruct { - // FIXME: Version 61 is the lowest package version I know that supports StateFlags. - private const int VStateFlags = 61; - - // FIXME: Version - private const int VFuncMap = 220; - public const int VProbeMaskReducedAndIgnoreMaskRemoved = 691; + [Obsolete] public const int VProbeMaskReducedAndIgnoreMaskRemoved = 691; #region Serialized Members @@ -46,6 +42,9 @@ public partial class UState : UStruct /// private uint _StateFlags; + /// + /// Always null if version is lower than + /// public UMap FuncMap; #endregion @@ -92,7 +91,7 @@ protected override void Deserialize() LabelTableOffset = _Buffer.ReadUInt16(); Record(nameof(LabelTableOffset), LabelTableOffset); - if (_Buffer.Version >= VStateFlags) + if (_Buffer.Version >= (uint)PackageObjectLegacyVersion.AddedStateFlagsToUState) { #if BORDERLANDS2 || TRANSFORMERS || BATMAN // FIXME:Temp fix @@ -117,7 +116,11 @@ protected override void Deserialize() return; } #endif - if (_Buffer.Version < VFuncMap) return; + if (_Buffer.Version < (uint)PackageObjectLegacyVersion.AddedFuncMapToUState) + { + return; + } + _Buffer.ReadMap(out FuncMap); Record(nameof(FuncMap), FuncMap); } @@ -139,15 +142,9 @@ protected override void FindChildren() #region Methods - public bool HasStateFlag(StateFlags flag) - { - return (_StateFlags & (uint)flag) != 0; - } + public bool HasStateFlag(StateFlags flag) => (_StateFlags & (uint)flag) != 0; - public bool HasStateFlag(uint flag) - { - return (_StateFlags & flag) != 0; - } + public bool HasStateFlag(uint flag) => (_StateFlags & flag) != 0; #endregion } diff --git a/src/Core/Classes/UStruct.cs b/src/Core/Classes/UStruct.cs index bf46cb3c..753e402e 100644 --- a/src/Core/Classes/UStruct.cs +++ b/src/Core/Classes/UStruct.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Runtime.CompilerServices; using UELib.Annotations; using UELib.Branch; @@ -15,18 +14,7 @@ namespace UELib.Core [UnrealRegisterClass] public partial class UStruct : UField { - private const int VCppText = 120; - - // FIXME: Version - private const int VFriendlyNameMoved = 160; - - /// - /// Used to determine if UClass has an interfaces UArray, and the ObjectToInterface CastToken (among others). - /// FIXME: Version - /// - public const int VInterfaceClass = 220; - - private const int VStorageScriptSize = 639; + [Obsolete] public const int VInterfaceClass = 222; #region Serialized Members @@ -71,7 +59,7 @@ public partial class UStruct : UField public long ScriptOffset { get; private set; } public int ScriptSize { get; private set; } - public UByteCodeDecompiler ByteCodeManager; + [CanBeNull] public UByteCodeDecompiler ByteCodeManager; #endregion @@ -103,7 +91,7 @@ protected override void Deserialize() Record(nameof(ScriptText), ScriptText); // FIXME: another 2x32 uints here (IsConsoleCooked) - + goto skipChildren; } #endif @@ -112,6 +100,7 @@ protected override void Deserialize() ScriptText = _Buffer.ReadObject(); Record(nameof(ScriptText), ScriptText); } + skipScriptText: Children = _Buffer.ReadObject(); Record(nameof(Children), Children); @@ -123,7 +112,7 @@ protected override void Deserialize() } #endif // Moved to UFunction in UE3 - if (_Buffer.Version < VFriendlyNameMoved) + if (_Buffer.Version < (uint)PackageObjectLegacyVersion.MovedFriendlyNameToUFunction) { FriendlyName = _Buffer.ReadNameReference(); Record(nameof(FriendlyName), FriendlyName); @@ -136,10 +125,10 @@ protected override void Deserialize() // Back-ported CppText CppText = _Buffer.ReadObject(); Record(nameof(CppText), CppText); - + var dnfTextObj2 = _Buffer.ReadObject(); Record(nameof(dnfTextObj2), dnfTextObj2); - + _Buffer.ReadArray(out UArray dnfIncludeTexts); Record(nameof(dnfIncludeTexts), dnfIncludeTexts); } @@ -149,18 +138,19 @@ protected override void Deserialize() // Bool? byte dnfByte = _Buffer.ReadByte(); Record(nameof(dnfByte), dnfByte); - + var dnfName = _Buffer.ReadNameReference(); Record(nameof(dnfName), dnfName); } - + goto lineData; } #endif // Standard, but UT2004' derived games do not include this despite reporting version 128+ - if (_Buffer.Version >= VCppText && _Buffer.UE4Version < 117 - && !Package.IsConsoleCooked() && - (Package.Build != BuildGeneration.UE2_5 && + if (_Buffer.Version >= (uint)PackageObjectLegacyVersion.AddedCppTextToUStruct && + _Buffer.UE4Version < 117 && + !Package.IsConsoleCooked() && + (Package.Build != BuildGeneration.UE2_5 && Package.Build != BuildGeneration.AGP)) { CppText = _Buffer.ReadObject(); @@ -192,7 +182,7 @@ protected override void Deserialize() Record(nameof(ProcessedText), ProcessedText); } #endif - lineData: + lineData: if (!Package.IsConsoleCooked() && _Buffer.UE4Version < 117) { @@ -220,11 +210,11 @@ protected override void Deserialize() Record(nameof(transformersEndLine), transformersEndLine); } #endif - serializeByteCode: + serializeByteCode: ByteScriptSize = _Buffer.ReadInt32(); Record(nameof(ByteScriptSize), ByteScriptSize); - bool hasFixedScriptSize = _Buffer.Version >= VStorageScriptSize; - if (hasFixedScriptSize) + + if (_Buffer.Version >= (uint)PackageObjectLegacyVersion.AddedDataScriptSizeToUStruct) { DataScriptSize = _Buffer.ReadInt32(); Record(nameof(DataScriptSize), DataScriptSize); @@ -241,7 +231,7 @@ protected override void Deserialize() return; ByteCodeManager = new UByteCodeDecompiler(this); - if (hasFixedScriptSize) + if (_Buffer.Version >= (uint)PackageObjectLegacyVersion.AddedDataScriptSizeToUStruct) { _Buffer.Skip(DataScriptSize); } @@ -280,7 +270,7 @@ public override void PostInitialize() Console.WriteLine(ice.Message); } } - + [Obsolete("Pending deprecation")] protected virtual void FindChildren() { @@ -324,7 +314,7 @@ protected virtual void FindChildren() } #endregion - + public IEnumerable EnumerateFields() { for (var field = Children; field != null; field = field.NextField) @@ -333,13 +323,13 @@ public IEnumerable EnumerateFields() } } - + [MethodImpl(MethodImplOptions.AggressiveInlining)] public TokenFactory GetTokenFactory() { return Package.Branch.GetTokenFactory(Package); } - + [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool HasStructFlag(StructFlags flag) { diff --git a/src/Core/Classes/UStructDecompiler.cs b/src/Core/Classes/UStructDecompiler.cs index 8f173816..8565352c 100644 --- a/src/Core/Classes/UStructDecompiler.cs +++ b/src/Core/Classes/UStructDecompiler.cs @@ -1,6 +1,7 @@ #if DECOMPILE using System; using System.Linq; +using UELib.Branch; using UELib.Decompiler; namespace UELib.Core @@ -52,7 +53,8 @@ public override string Decompile() public override string FormatHeader() { - var output = $"struct {FormatFlags()}{Name}{(Super != null ? $" {FormatExtends()} {Super.Name}" : string.Empty)}"; + var output = + $"struct {FormatFlags()}{Name}{(Super != null ? $" {FormatExtends()} {Super.Name}" : string.Empty)}"; string metaData = DecompileMeta(); if (metaData != string.Empty) { @@ -126,7 +128,8 @@ private string FormatFlags() return output; } - protected virtual string CPPTextKeyword => Package.Version < VCppText ? "cppstruct" : "structcpptext"; + protected virtual string CPPTextKeyword => + Package.Version < (uint)PackageObjectLegacyVersion.AddedCppTextToUStruct ? "cppstruct" : "structcpptext"; protected string FormatCPPText() { @@ -216,7 +219,7 @@ protected string FormatProperties() { output += "//"; } - + output += "var"; if (property.CategoryName != null && !property.CategoryName.IsNone()) { @@ -224,8 +227,9 @@ protected string FormatProperties() ? "()" : $"({property.CategoryName})"; } + output += $" {property.Decompile()};"; - + string postOutput = property.PostDecompile(); if (!string.IsNullOrEmpty(postOutput)) { diff --git a/src/Core/Classes/UTextBuffer.cs b/src/Core/Classes/UTextBuffer.cs index 332885a8..8c972be4 100644 --- a/src/Core/Classes/UTextBuffer.cs +++ b/src/Core/Classes/UTextBuffer.cs @@ -47,7 +47,7 @@ protected override void Deserialize() return; } #endif - ScriptText = _Buffer.ReadText(); + ScriptText = _Buffer.ReadString(); Record(nameof(ScriptText), "..."); } diff --git a/src/Core/Tables/UExportTableItem.cs b/src/Core/Tables/UExportTableItem.cs index 65c2ad85..2a383d9a 100644 --- a/src/Core/Tables/UExportTableItem.cs +++ b/src/Core/Tables/UExportTableItem.cs @@ -12,58 +12,60 @@ namespace UELib /// public sealed class UExportTableItem : UObjectTableItem, IUnrealSerializableClass { - [Obsolete] - public const int VObjectFlagsToULONG = 195; + [Obsolete] public const int VObjectFlagsToULONG = 195; #region Serialized Members - + private int _ClassIndex; + public int ClassIndex { get => _ClassIndex; set => _ClassIndex = value; } - [CanBeNull] - public UObjectTableItem Class => Owner.GetIndexTable(ClassIndex); + [CanBeNull] public UObjectTableItem Class => Owner.GetIndexTable(ClassIndex); + + private int _SuperIndex; - public int _SuperIndex; public int SuperIndex { get => _SuperIndex; set => _SuperIndex = value; } - [CanBeNull] - public UObjectTableItem Super => Owner.GetIndexTable(_SuperIndex); - public int _TemplateIndex; + [CanBeNull] public UObjectTableItem Super => Owner.GetIndexTable(_SuperIndex); + + private int _TemplateIndex; + public int TemplateIndex { get => _TemplateIndex; set => _TemplateIndex = value; } - [CanBeNull] - public UObjectTableItem Template => Owner.GetIndexTable(_TemplateIndex); - public int _ArchetypeIndex; + [CanBeNull] public UObjectTableItem Template => Owner.GetIndexTable(_TemplateIndex); + + private int _ArchetypeIndex; + public int ArchetypeIndex { get => _ArchetypeIndex; set => _ArchetypeIndex = value; } - [CanBeNull] - public UObjectTableItem Archetype => Owner.GetIndexTable(_ArchetypeIndex); - - [Obsolete("Use Class"), Browsable(false)] public UObjectTableItem ClassTable => Owner.GetIndexTable(_ClassIndex); - [Obsolete] - protected override int __ClassIndex => _ClassIndex; + [CanBeNull] public UObjectTableItem Archetype => Owner.GetIndexTable(_ArchetypeIndex); - [Obsolete] - [NotNull] - protected override string __ClassName => Class?.ObjectName ?? "Class"; + [Obsolete("Use Class"), Browsable(false)] + public UObjectTableItem ClassTable => Owner.GetIndexTable(_ClassIndex); + + [Obsolete] protected override int __ClassIndex => _ClassIndex; + + [Obsolete] [NotNull] protected override string __ClassName => Class?.ObjectName ?? "Class"; + + [Obsolete("Use Super"), Browsable(false)] + public UObjectTableItem SuperTable => Owner.GetIndexTable(_SuperIndex); - [Obsolete("Use Super"), Browsable(false)] public UObjectTableItem SuperTable => Owner.GetIndexTable(_SuperIndex); [Obsolete("Use Super?.ObjectName"), Browsable(false)] public string SuperName { @@ -74,7 +76,9 @@ public string SuperName } } - [Obsolete("Use Archetype"), Browsable(false)] public UObjectTableItem ArchetypeTable => Owner.GetIndexTable(_ArchetypeIndex); + [Obsolete("Use Archetype"), Browsable(false)] + public UObjectTableItem ArchetypeTable => Owner.GetIndexTable(_ArchetypeIndex); + [Obsolete("Use Archetype?.ObjectName"), Browsable(false)] public string ArchetypeName { @@ -102,7 +106,11 @@ public string ArchetypeName public int SerialOffset; public uint ExportFlags; - + + /// + /// Always null if version is lower than + /// or higher than + /// public UMap ComponentMap; //public List NetObjects; @@ -123,13 +131,14 @@ public void Serialize(IUnrealStream stream) { stream.Write(_ClassIndex); stream.Write(_SuperIndex); - stream.Write(OuterIndex); - stream.Write(ObjectName); + stream.Write(_OuterIndex); + stream.Write(_ObjectName); if (stream.Version >= (uint)PackageObjectLegacyVersion.ArchetypeAddedToExports) { _ArchetypeIndex = stream.ReadInt32(); } - stream.Write(stream.Version >= (uint)PackageObjectLegacyVersion.ObjectFlagsSizeChangedToULong + + stream.Write(stream.Version >= (uint)PackageObjectLegacyVersion.ObjectFlagsSizeExpandedTo64Bits ? ObjectFlags : (uint)ObjectFlags); stream.WriteIndex(SerialSize); // Assumes SerialSize has been updated to @Object's buffer size. @@ -140,7 +149,7 @@ public void Serialize(IUnrealStream stream) } // TODO: Continue. - if (stream.Version >= 220) + if (stream.Version >= (uint)PackageObjectLegacyVersion.ArchetypeAddedToExports) { throw new NotSupportedException(); } @@ -150,7 +159,7 @@ public void Deserialize(IUnrealStream stream) { _ClassIndex = stream.ReadObjectIndex(); _SuperIndex = stream.ReadObjectIndex(); - OuterIndex = stream.ReadInt32(); // ObjectIndex, though always written as 32bits regardless of build. + _OuterIndex = stream.ReadInt32(); // ObjectIndex, though always written as 32bits regardless of build. #if BIOSHOCK if (stream.Package.Build == UnrealPackage.GameBuild.BuildName.BioShock && stream.Version >= 132) @@ -158,7 +167,7 @@ public void Deserialize(IUnrealStream stream) stream.Skip(sizeof(int)); } #endif - ObjectName = stream.ReadNameReference(); + _ObjectName = stream.ReadNameReference(); if (stream.Version >= (uint)PackageObjectLegacyVersion.ArchetypeAddedToExports) { _ArchetypeIndex = stream.ReadInt32(); @@ -180,7 +189,7 @@ public void Deserialize(IUnrealStream stream) } #endif ObjectFlags = stream.ReadUInt32(); - if (stream.Version >= (uint)PackageObjectLegacyVersion.ObjectFlagsSizeChangedToULong) + if (stream.Version >= (uint)PackageObjectLegacyVersion.ObjectFlagsSizeExpandedTo64Bits) { ObjectFlags = (ObjectFlags << 32) | stream.ReadUInt32(); } @@ -209,7 +218,7 @@ public void Deserialize(IUnrealStream stream) stream.Skip(sizeof(int)); } #endif - if (stream.Version < 220) + if (stream.Version < (uint)PackageObjectLegacyVersion.ArchetypeAddedToExports) return; if (stream.Version < (uint)PackageObjectLegacyVersion.ComponentMapDeprecated @@ -232,7 +241,7 @@ public void Deserialize(IUnrealStream stream) } } - if (stream.Version < 247) + if (stream.Version < (uint)PackageObjectLegacyVersion.ExportFlagsAddedToExports) return; streamExportFlags: @@ -284,9 +293,8 @@ public void Deserialize(IUnrealStream stream) stream.Skip(4); // Package flags } } - - [Obsolete] - private long _ObjectFlagsOffset; + + [Obsolete] private long _ObjectFlagsOffset; /// /// Updates the ObjectFlags inside the Stream to the current set ObjectFlags of this Table diff --git a/src/Core/Tables/UNameTableItem.cs b/src/Core/Tables/UNameTableItem.cs index c0daf71c..12ca7009 100644 --- a/src/Core/Tables/UNameTableItem.cs +++ b/src/Core/Tables/UNameTableItem.cs @@ -1,5 +1,6 @@ using System.Diagnostics; using System.Runtime.CompilerServices; +using UELib.Branch; namespace UELib { @@ -40,7 +41,7 @@ public void Deserialize(IUnrealStream stream) return; } #endif - _Flags = stream.Version >= UExportTableItem.VObjectFlagsToULONG + _Flags = stream.Version >= (uint)PackageObjectLegacyVersion.ObjectFlagsSizeExpandedTo64Bits ? stream.ReadUInt64() : stream.ReadUInt32(); } @@ -50,15 +51,15 @@ private string DeserializeName(IUnrealStream stream) { #if UE1 // Very old packages use a simple Ansi encoding. - if (stream.Version < UnrealPackage.VSIZEPREFIXDEPRECATED) return stream.ReadAnsiNullString(); + if (stream.Version < (uint)PackageObjectLegacyVersion.Release64) return stream.ReadAnsiNullString(); #endif - return stream.ReadText(); + return stream.ReadString(); } public void Serialize(IUnrealStream stream) { stream.Write(_Name); - if (stream.Version < UExportTableItem.VObjectFlagsToULONG) + if (stream.Version < (uint)PackageObjectLegacyVersion.ObjectFlagsSizeExpandedTo64Bits) // Writing UINT stream.Write((uint)_Flags); else @@ -81,4 +82,4 @@ public static implicit operator int(UNameTableItem a) return a.Index; } } -} \ No newline at end of file +} diff --git a/src/Core/Tokens/CastTokens.cs b/src/Core/Tokens/CastTokens.cs index c624ab8c..214f4613 100644 --- a/src/Core/Tokens/CastTokens.cs +++ b/src/Core/Tokens/CastTokens.cs @@ -2,6 +2,7 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; +using UELib.Branch; using UELib.ObjectModel.Annotations; using UELib.Tokens; @@ -73,7 +74,7 @@ protected virtual void DeserializeCastToken(IUnrealStream stream) private void RemapCastToken(IUnrealArchive stream) { - if (stream.Version >= VInterfaceClass) return; + if (stream.Version >= (uint)PackageObjectLegacyVersion.AddedInterfacesFeature) return; // TODO: Could there be more? switch (CastOpCode) diff --git a/src/Core/UStateFrame.cs b/src/Core/UStateFrame.cs index 7c8678a6..88aebb6f 100644 --- a/src/Core/UStateFrame.cs +++ b/src/Core/UStateFrame.cs @@ -1,4 +1,5 @@ using System.Diagnostics.CodeAnalysis; +using UELib.Branch; namespace UELib.Core { @@ -8,10 +9,6 @@ namespace UELib.Core [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] public class UStateFrame : IUnrealSerializableClass { - // FIXME: Version, added somewhere between 186 ... 230 - private const int VStateStack = 187; - private const int VLatentActionReduced = 566; - public UStruct Node; public UState StateNode; public ulong ProbeMask; @@ -24,10 +21,10 @@ public void Deserialize(IUnrealStream stream) { Node = stream.ReadObject(); StateNode = stream.ReadObject(); - ProbeMask = stream.Version < UState.VProbeMaskReducedAndIgnoreMaskRemoved + ProbeMask = stream.Version < (uint)PackageObjectLegacyVersion.ProbeMaskReducedAndIgnoreMaskRemoved ? stream.ReadUInt64() : stream.ReadUInt32(); - LatentAction = stream.Version < VLatentActionReduced + LatentAction = stream.Version < (uint)PackageObjectLegacyVersion.StateFrameLatentActionReduced ? stream.ReadUInt32() : stream.ReadUInt16(); if (stream.Package.Build == UnrealPackage.GameBuild.BuildName.DNF && @@ -35,7 +32,9 @@ public void Deserialize(IUnrealStream stream) { uint dnfUInt32 = stream.ReadUInt32(); } - if (stream.Version >= VStateStack) stream.ReadArray(out StateStack); + + if (stream.Version >= (uint)PackageObjectLegacyVersion.AddedStateStackToUStateFrame) + stream.ReadArray(out StateStack); if (Node != null) Offset = stream.ReadIndex(); } @@ -43,15 +42,16 @@ public void Serialize(IUnrealStream stream) { stream.Write(Node); stream.Write(StateNode); - stream.Write(stream.Version < UState.VProbeMaskReducedAndIgnoreMaskRemoved + stream.Write(stream.Version < (uint)PackageObjectLegacyVersion.ProbeMaskReducedAndIgnoreMaskRemoved ? ProbeMask : (uint)ProbeMask ); - stream.Write(stream.Version < VLatentActionReduced + stream.Write(stream.Version < (uint)PackageObjectLegacyVersion.StateFrameLatentActionReduced ? LatentAction : (ushort)LatentAction ); - if (stream.Version >= VStateStack) stream.Write(ref StateStack); + if (stream.Version >= (uint)PackageObjectLegacyVersion.AddedStateStackToUStateFrame) + stream.Write(ref StateStack); if (Node != null) stream.Write(Offset); } @@ -76,4 +76,4 @@ public void Serialize(IUnrealStream stream) } } } -} \ No newline at end of file +} diff --git a/src/UnrealMod.cs b/src/UnrealMod.cs index 12338f8a..207822de 100644 --- a/src/UnrealMod.cs +++ b/src/UnrealMod.cs @@ -50,7 +50,7 @@ public void Serialize(IUnrealStream stream) public void Deserialize(IUnrealStream stream) { - FileName = stream.ReadText(); + FileName = stream.ReadString(); SerialOffset = stream.ReadIndex(); SerialSize = stream.ReadIndex(); FileFlags = stream.ReadUInt32(); diff --git a/src/UnrealPackage.cs b/src/UnrealPackage.cs index 0b201b73..81e43185 100644 --- a/src/UnrealPackage.cs +++ b/src/UnrealPackage.cs @@ -827,7 +827,7 @@ public void Deserialize(IUnrealStream stream) Minor = stream.ReadUInt16(); Patch = stream.ReadUInt16(); Changelist = stream.ReadUInt32(); - Branch = stream.ReadText(); + Branch = stream.ReadString(); } public override string ToString() @@ -1062,7 +1062,7 @@ public void Deserialize(IUnrealStream stream) if (stream.Version >= VFolderName) { - FolderName = stream.ReadText(); + FolderName = stream.ReadString(); } PackageFlags = stream.ReadFlags32(); @@ -1080,7 +1080,7 @@ public void Deserialize(IUnrealStream stream) #if UE4 if (stream.UE4Version >= 516 && stream.Package.ContainsEditorData()) { - LocalizationId = stream.ReadText(); + LocalizationId = stream.ReadString(); } if (stream.UE4Version >= 459) @@ -1232,7 +1232,7 @@ public void Deserialize(IUnrealStream stream) int buildSeconds = stream.ReadInt32(); } - string dnfString = stream.ReadText(); + string dnfString = stream.ReadString(); // DLC package if (PackageFlags.HasFlags(0x80U)) @@ -1694,7 +1694,7 @@ public void Deserialize(UPackageStream stream) { for (var i = 0; i < Summary.ImportGuidsCount; ++i) { - string levelName = stream.ReadText(); + string levelName = stream.ReadString(); int guidCount = stream.ReadInt32(); stream.Skip(guidCount * 16); } diff --git a/src/UnrealStream.cs b/src/UnrealStream.cs index 62eb611e..6394eab3 100644 --- a/src/UnrealStream.cs +++ b/src/UnrealStream.cs @@ -327,7 +327,7 @@ public int ReadNameIndex(out int num) [Obsolete("UE Explorer - Hex Viewer")] public static int ReadIndexFromBuffer(byte[] value, IUnrealStream stream) { - if (stream.Version >= UnrealPackage.VINDEXDEPRECATED) + if (stream.Version >= (uint)PackageObjectLegacyVersion.CompactIndexDeprecated) { return BitConverter.ToInt32(value, 0); } @@ -904,7 +904,7 @@ public static void ReadArray(this IUnrealStream stream, out UArray array array = new UArray(c); for (int i = 0; i < c; ++i) { - string element = stream.ReadText(); + string element = stream.ReadString(); array.Add(element); } } @@ -1030,7 +1030,7 @@ public static void Read(this IUnrealStream stream, out UBulkData value) public static void Read(this IUnrealStream stream, out UObject value) => value = ReadObject(stream); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Read(this IUnrealStream stream, out string value) => value = stream.ReadText(); + public static void Read(this IUnrealStream stream, out string value) => value = stream.ReadString(); [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Read(this IUnrealStream stream, out UName value) => value = ReadNameReference(stream); From 27cb23bb29e7acb81d47e1b0647b35eb27622f46 Mon Sep 17 00:00:00 2001 From: Eliot Date: Sun, 1 Sep 2024 10:47:29 +0200 Subject: [PATCH 50/50] > 1.6.0 --- CHANGELOG.md | 27 ++++++++++++++++++++++----- README.md | 40 ++++++++++++++++++++++------------------ src/Eliot.UELib.csproj | 2 +- 3 files changed, 45 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 56fbd488..bfc3d41f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,13 +1,30 @@ -# [1.5.1](https://github.com/EliotVU/Unreal-Library/releases/tag/1.5.1) +# + +## [1.6.0](https://github.com/EliotVU/Unreal-Library/releases/tag/1.6.0) + +Notable changes: + +* 5ef6d04a Support for Tom Clancy's EndWar +* cbbfff3e Support for Gigantic: Rampage Edition (thanks to @HyenaCoding) +* 139254a9 Support for Borderlands: Game of the Year Enhanced; and fixed regression #55 of Borderlands and Battleborn. +* 98dbf0bf Improved support for Duke Nukem Forever (thanks to @DaZombieKiller) +* 88a5b619 Improved support for Rocket League #54 +* 8e3053c7 Fixed regression [Batman series support](https://github.com/UE-Explorer/UE-Explorer/issues/63) + +* d04d8b13 Fixed fallback for deprecated ClassName so that "UE Explorer" can pickup content again. +* 5ac20221 Fixed #36; various T3D archetype fixes. +* 84b46eed Fixed T3D syntax ouput from "object end" to "end object" + +## [1.5.1](https://github.com/EliotVU/Unreal-Library/releases/tag/1.5.1) * Fixed regression #74; The deprecated `UnrealConfig.CookedPlatform` field was ignored, which is still relevant for legacy-code. * Updated auto-detected builds for Infinity Blade's series -# [1.5.0](https://github.com/EliotVU/Unreal-Library/releases/tag/1.5.0) +## [1.5.0](https://github.com/EliotVU/Unreal-Library/releases/tag/1.5.0) * 1ef135d Improved support for A Hat in Time (UE3), contributed by @Un-Drew -# [1.4.0](https://github.com/EliotVU/Unreal-Library/releases/tag/1.4.0) +## [1.4.0](https://github.com/EliotVU/Unreal-Library/releases/tag/1.4.0) Notable changes that affect UnrealScript output: @@ -48,7 +65,7 @@ Notable changes that affect various data structures: **Support for the data types listed above have only been implemented for the standard structure that Epic Games uses** -# [1.3.1](https://github.com/EliotVU/Unreal-Library/releases/tag/1.3.1) +## [1.3.1](https://github.com/EliotVU/Unreal-Library/releases/tag/1.3.1) Notable changes back-ported from 'develop' version 1.4.0: @@ -62,7 +79,7 @@ Notable changes back-ported from 'develop' version 1.4.0: * 42783b16 Added the capability to override the interpreted version for packages of builds that are auto-detected. -# [1.3.0](https://github.com/EliotVU/Unreal-Library/releases/tag/1.3.0.0) +## [1.3.0](https://github.com/EliotVU/Unreal-Library/releases/tag/1.3.0.0) Notable changes: diff --git a/README.md b/README.md index 4dedf780..8927cc70 100644 --- a/README.md +++ b/README.md @@ -8,15 +8,15 @@ Its main purpose is to decompile the UnrealScript byte-code to its original sour It accomplishes this by reading the necessary Unreal data classes such as: - UObject, UField, UConst, UEnum, UProperty, UStruct, UFunction, UState, UClass, + UObject, UField, UConst, UEnum, UProperty, UStruct, UFunction, UState, UClass, UTextBuffer, UMetaData, UPackage Classes such as UStruct, UState, UClass, and UFunction contain the UnrealScript byte-code which we can deserialize in order to re-construct the byte-codes to its original UnrealScript source. Additionally UELib is also capable of deserializing of many more data classes such as: - UFont, USound, UPalette, UTexture, - UTexture2D, UTexture2DDynamic, UTexture2DComposite, UTexture3D, + UFont, USound, UPalette, UTexture, + UTexture2D, UTexture2DDynamic, UTexture2DComposite, UTexture3D, UTextureCube, UTextureFlipBook, UTextureMovie UPrimitive, UPolys @@ -28,9 +28,10 @@ Install using either: * Package Manager: -``` cmd +```cmd Install-Package Eliot.UELib ``` + * NuGet: * Usage: See the [documentation](https://github.com/EliotVU/Unreal-Library/wiki/Usage) for more examples. @@ -42,9 +43,9 @@ Install using either: var package = UnrealLoader.LoadPackage(@"C:\Path\Package.upk", System.IO.FileAccess.Read); Console.WriteLine($"Version: {package.Summary.Version}"); - // Necessary if working with packages that have been cooked for a console platform, and IF the build was not properly auto-detected. + // Necessary if working with packages that have been cooked for a console platform, and IF the build was not properly auto-detected. // package.CookerPlatform = BuildPlatform.Console; - + // Initializes the registered classes, constructs and deserializes(loads) the package objects. package.InitializePackage(); @@ -76,7 +77,7 @@ If you're looking to modify the library for the sole purpose of modding [UE Expl This is a table of games that are confirmed to be compatible with the current state of UELib, the table is sorted by Package-Version. | Name | Engine:Branch | Package/Licensee | Support State | -| --------------------- | ---------------- | ------------------- | ----------------- +| --------------------- | ---------------- | ------------------- | ----------------- | | Unreal | 100-226 | 61/000 | | | [Star Trek: The Next Generation: Klingon Honor Guard](Star%20Trek:%20The%20Next%20Generation:%20Klingon%20Honor%20Guard) | 219 | 61/000 | | | X-COM: Alliance | 200-220 | 61/000 | Bad output at the start of functions (BeginFunctionToken) | @@ -90,7 +91,7 @@ This is a table of games that are confirmed to be compatible with the current st | Duke Nukem Forever (2001) | 613 | 68/002 | UStruct offsets are off leading to bad output code | | Rune | 400 | 69/000 | | | Unrealty | 405 | 69/000 | | -| X-COM: Enforcer | 420 | 69/000 | | +| X-COM: Enforcer | 420 | 69/000 | | | Tactical Ops: Assault on Terror | 436 | 69/000 | | | Star Trek: Deep Space Nine: The Fallen | 338 | 73/000 | | | Harry Potter and the Sorcerer's Stone | 436 | 76/000 | | @@ -167,9 +168,9 @@ This is a table of games that are confirmed to be compatible with the current st | Infinity Blade 1 | 7595 | 788/001 | Console | | [Dishonored](http://www.dishonored.com/) | 9099 | 801/030 | | | Tribes: Ascend | 7748 | 805/Unknown | | -| Tony Hawk's Pro Skater HD | | +| Tony Hawk's Pro Skater HD | | | | | Rock of Ages | 7748 | 805/000 | | -| Batman: Arkham City | 7748 | 805/101 | | +| Batman: Arkham City | 7748 | 805/101 | | | Batman: Arkham Origins | 7748 | 807/138 | Not verified | | Sanctum | 7876 | 810/000 | | | AntiChamber | 7977 | 812/000 | | @@ -178,7 +179,8 @@ This is a table of games that are confirmed to be compatible with the current st | Gears of War 3 | 8653 | 828/000 | | | Quantum Conundrum | 8623 | 832/32870 | | | Borderlands | 4871 | Unknown | | -| Borderlands 2 | 8623/023 | 832/056 | | +| Borderlands 2 | 8623/0023 | 832/056 | | +| Borderlands: Game of the Year Enhanced | 5001/0001 | 832/058 | | | Remember Me | 8623 | 832/021 | | | The Haunted: Hells Reach | 8788 | 841/000 | | | Asura's Wrath | 8788 | 841/000 | -zlib; platform needs to be set to console. | @@ -198,14 +200,16 @@ This is a table of games that are confirmed to be compatible with the current st | Chivalry: Medieval Warfare | 10246 | 860/000 | | | Hawken | 10681 | 860/004 | | | Rocket League | 10897 | 867/009 (868/032 has not been tested) | [Decryption required](https://github.com/AltimorTASDK/RLUPKTool) | -| Styx: Master of Shadows | 10499 | 860/004 | | +| Styx: Master of Shadows | 10499 | 860/004 | | | Batman: Arkham Knight | | 863/32995 | Not verified | +| Gigantic: Rampage Edition | 19100 | 867/010 | | | Infinity Blade 3 | | 868/000 | Console | | Guilty Gear Xrd | 10246 | 868/003 | [Decryption required](https://github.com/gdkchan/GGXrdRevelatorDec) | +| [Might & Magic Heroes VII](https://en.wikipedia.org/wiki/Might_%26_Magic_Heroes_VII) | 12161 | 868/004 | (Signature and custom features are not supported) | | Bombshell | 11767 | 870/000 | | | Orcs Must Die! Unchained | 20430 | 870/000 | | | Gal\*Gun: Double Peace | 10897 | 871/000 | | -| [Might & Magic Heroes VII](https://en.wikipedia.org/wiki/Might_%26_Magic_Heroes_VII) | 12161 | 868/004 | (Signature and custom features are not supported) +| Battleborn | 8623/1055 | 874/078 | | | A Hat in Time | 12097 | 877-893/005 | | | Shadow Complex Remastered | 10897 | 893/001 | | | Soldier Front 2 | 6712 | 904/009 | | @@ -230,8 +234,8 @@ Do you know a game that is compatible but is not listed here? Click on the top r ## Special thanks to - * Epic Games for [UDN: Packages](http://www.hypercoop.tk/infobase/archive/unrealtech/Packages.htm) (general package format) - * [Antonio Cordero Balcazar](https://github.com/acorderob) for [UTPT](https://www.acordero.org/projects/unreal-tournament-package-tool) (game support) and documentation (format) - * [Dmitry Jemerov](https://github.com/yole) for [unhood](https://github.com/yole/unhood) (early UE3 format) - * [Konstantin Nosov](https://github.com/gildor2) for providing help and [UE Viewer](http://www.gildor.org/en/projects/umodel) (game support) - * [Contributors](https://github.com/EliotVU/Unreal-Library/graphs/contributors) +* Epic Games for [UDN: Packages](http://www.hypercoop.tk/infobase/archive/unrealtech/Packages.htm) (general package format) +* [Antonio Cordero Balcazar](https://github.com/acorderob) for [UTPT](https://www.acordero.org/projects/unreal-tournament-package-tool) (game support) and documentation (format) +* [Dmitry Jemerov](https://github.com/yole) for [unhood](https://github.com/yole/unhood) (early UE3 format) +* [Konstantin Nosov](https://github.com/gildor2) for providing help and [UE Viewer](http://www.gildor.org/en/projects/umodel) (game support) +* [Contributors](https://github.com/EliotVU/Unreal-Library/graphs/contributors) diff --git a/src/Eliot.UELib.csproj b/src/Eliot.UELib.csproj index 78fd52b3..a0d965e1 100644 --- a/src/Eliot.UELib.csproj +++ b/src/Eliot.UELib.csproj @@ -73,7 +73,7 @@ Eliot.UELib $(AssemblyName) - $(VersionPrefix)1.5.1 + $(VersionPrefix)1.6.0 EliotVU $(AssemblyName) UnrealScript decompiler library for Unreal package files (.upk, .u, .uasset; etc), with support for Unreal Engine 1, 2, and 3.