diff --git a/src/Branch/UE3/RSS/Tokens/Bm4ContextToken.cs b/src/Branch/UE3/RSS/Tokens/Bm4ContextToken.cs new file mode 100644 index 00000000..86d19950 --- /dev/null +++ b/src/Branch/UE3/RSS/Tokens/Bm4ContextToken.cs @@ -0,0 +1,9 @@ +using UELib.Core; + +namespace UELib.Branch.UE3.RSS.Tokens +{ + public class Bm4ContextToken : UStruct.UByteCodeDecompiler.ContextToken + { + + } +} \ No newline at end of file diff --git a/src/Branch/UE3/RSS/Tokens/NameConstNoNumberToken.cs b/src/Branch/UE3/RSS/Tokens/NameConstNoNumberToken.cs new file mode 100644 index 00000000..80c06e4a --- /dev/null +++ b/src/Branch/UE3/RSS/Tokens/NameConstNoNumberToken.cs @@ -0,0 +1,12 @@ +using UELib.Core; + +namespace UELib.Branch.UE3.RSS.Tokens +{ + public class NameConstNoNumberToken : UStruct.UByteCodeDecompiler.NameConstToken + { + public override void Deserialize(IUnrealStream stream) + { + Name = ReadNameNoNumber(stream); + } + } +} \ No newline at end of file diff --git a/src/ByteCodeDecompiler.cs b/src/ByteCodeDecompiler.cs index 7ff9cbf1..9e18dc27 100644 --- a/src/ByteCodeDecompiler.cs +++ b/src/ByteCodeDecompiler.cs @@ -6,12 +6,14 @@ using System.IO; using System.Runtime.CompilerServices; using UELib.Annotations; +using UELib.Flags; using UELib.Tokens; namespace UELib.Core { using System.Linq; using System.Text; + using UELib.Branch.UE3.RSS.Tokens; public partial class UStruct { @@ -306,6 +308,18 @@ private void AlignObjectSize() { 0x59, (byte)ExprToken.Unused } }; #endif +#if BATMAN + private static readonly Dictionary ByteCodeMap_BuildBatman4 = new Dictionary + { + { 0x2B, (byte)ExprToken.Unused }, // NameConst but without the Int32 number at the end + // 0x4E, unknown but it has the same pattern as a DynamicCast + { 0x4F, (byte)ExprToken.DynamicCast }, + // 0x4F, unknown but it has the same pattern as a DynamicCast + { 0x50, (byte)ExprToken.DynamicCast }, + // 0x50 + { 0x51, (byte)ExprToken.Unused }, + }; +#endif #if APB private static readonly Dictionary ByteCodeMap_BuildApb = new Dictionary { @@ -366,6 +380,13 @@ private void SetupByteCodeMap() return; } #endif +#if BIOSHOCK + if (Package.Build == UnrealPackage.GameBuild.BuildName.Batman4) + { + _ByteCodeMap = ByteCodeMap_BuildBatman4; + return; + } +#endif #if APB if (Package.Build == UnrealPackage.GameBuild.BuildName.APB && Package.LicenseeVersion >= 32) @@ -504,7 +525,12 @@ private void DeserializeDebugToken() private NativeFunctionToken CreateNativeToken(ushort nativeIndex) { - var nativeTableItem = _Container.Package.NTLPackage?.FindTableItem(nativeIndex); + var nativeTableItem = _Container.Package.NTLPackage?.FindTableItem(nativeIndex) ?? new NativeTableItem + { + Type = FunctionType.Function, + Name = $"__NFUN_{nativeIndex}__", + ByteToken = nativeIndex + }; return new NativeFunctionToken { NativeItem = nativeTableItem @@ -1113,8 +1139,26 @@ private Token DeserializeNext(byte tokenCode = byte.MaxValue) } } } +#if BATMAN + // HACK: temporary for the hotfix (:D) + if (token == null && Package.Build == UnrealPackage.GameBuild.BuildName.Batman4) + { + switch (serializedByte) + { + case 0x2B: + token = new NameConstNoNumberToken(); + break; - if (token == null) token = new UnresolvedToken(); + case 0x50: + token = new Bm4ContextToken(); + break; + } + } +#endif + if (token == null) + { + token = new UnresolvedToken(); + } AddToken(token, serializedByte, tokenPosition); return token; } @@ -1300,11 +1344,11 @@ private Token DeserializeCastToken(byte castToken) return token; } - #endregion +#endregion #if DECOMPILE - #region Decompile +#region Decompile public class NestManager { @@ -1571,6 +1615,10 @@ public string Decompile() #endif } } + catch (EndOfStreamException) + { + break; + } catch (Exception e) { output.Append($"// ({e.GetType().Name})"); @@ -1591,6 +1639,10 @@ public string Decompile() } } } + catch (EndOfStreamException) + { + break; + } catch (Exception e) { output.AppendLine("\r\n" @@ -1921,9 +1973,9 @@ private string DecompileNests(bool outputAllRemainingNests = false) return output; } - #endregion +#endregion - #region Disassemble +#region Disassemble [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")] public string Disassemble() @@ -1931,7 +1983,7 @@ public string Disassemble() return string.Empty; } - #endregion +#endregion #endif } diff --git a/src/Core/Classes/Props/UProperty.cs b/src/Core/Classes/Props/UProperty.cs index 56ecb667..d693582f 100644 --- a/src/Core/Classes/Props/UProperty.cs +++ b/src/Core/Classes/Props/UProperty.cs @@ -88,18 +88,24 @@ protected override void Deserialize() } #endif int info = _Buffer.ReadInt32(); + Record("ArrayDim&ElementSize", info); ArrayDim = (ushort)(info & 0x0000FFFFU); - Record("ArrayDim", ArrayDim); Debug.Assert(ArrayDim <= 2048, "Bad array dim"); ElementSize = (ushort)(info >> 16); - Record("ElementSize", ElementSize); skipInfo: PropertyFlags = Package.Version >= 220 ? _Buffer.ReadUInt64() : _Buffer.ReadUInt32(); Record("PropertyFlags", PropertyFlags); - +#if BATMAN + if (Package.Build == BuildGeneration.RSS && + Package.LicenseeVersion >= 101) + { + PropertyFlags = (PropertyFlags & 0xFFFF0000) >> 24; + Record("PropertyFlags", (PropertyFlagsLO)PropertyFlags); + } +#endif #if XCOM2 if (Package.Build == UnrealPackage.GameBuild.BuildName.XCOM2WotC) { @@ -108,7 +114,7 @@ protected override void Deserialize() } #endif #if THIEF_DS || DEUSEX_IW - if (Package.Build.Generation == BuildGeneration.Thief) + if (Package.Build == BuildGeneration.Thief) { // Property flags like CustomEditor, CustomViewer, ThiefProp, DeusExProp, NoTextExport, NoTravel uint deusFlags = _Buffer.ReadUInt32(); @@ -128,7 +134,7 @@ protected override void Deserialize() else { #if THIEF_DS || DEUSEX_IW - if (Package.Build.Generation == BuildGeneration.Thief) + if (Package.Build == BuildGeneration.Thief) { short deusInheritedOrRuntimeInstiantiated = _Buffer.ReadInt16(); Record(nameof(deusInheritedOrRuntimeInstiantiated), deusInheritedOrRuntimeInstiantiated); @@ -145,7 +151,7 @@ protected override void Deserialize() Record("RepOffset", RepOffset); } #if VENGEANCE - if (Package.Build.Generation == BuildGeneration.Vengeance) + if (Package.Build == BuildGeneration.Vengeance) { var vengeanceEditComboType = _Buffer.ReadNameReference(); Record(nameof(vengeanceEditComboType), vengeanceEditComboType); @@ -155,9 +161,9 @@ protected override void Deserialize() #endif // Appears to be a UE2X feature, it is not present in UE2 builds with no custom LicenseeVersion // Albeit DeusEx indicates otherwise? - if ((HasPropertyFlag(PropertyFlagsLO.EditorData) && (Package.Build.Generation == BuildGeneration.UE2_5 || Package.Build.Generation == BuildGeneration.Thief)) + if ((HasPropertyFlag(PropertyFlagsLO.EditorData) && (Package.Build == BuildGeneration.UE2_5 || Package.Build == BuildGeneration.Thief)) // No property flag - || Package.Build.Generation == BuildGeneration.Vengeance) + || Package.Build == BuildGeneration.Vengeance) { // May represent a tooltip/comment in some games. EditorDataText = _Buffer.ReadText(); diff --git a/src/Core/Classes/UClass.cs b/src/Core/Classes/UClass.cs index e5526fb2..40538a02 100644 --- a/src/Core/Classes/UClass.cs +++ b/src/Core/Classes/UClass.cs @@ -120,7 +120,7 @@ protected override void Deserialize() #endif base.Deserialize(); #if VENGEANCE - if (Package.Build.Generation == BuildGeneration.Vengeance && + if (Package.Build == BuildGeneration.Vengeance && Package.LicenseeVersion >= 36) { var header = (2, 0); @@ -171,7 +171,7 @@ protected override void Deserialize() PackageImports = DeserializeGroup(nameof(PackageImports)); } - skipTo61Stuff: + skipTo61Stuff: if (Package.Version >= 62) { // Class Name Extends Super.Name Within _WithinIndex @@ -203,6 +203,7 @@ protected override void Deserialize() ) DeserializeHideCategories(); + // Seems to have been removed in transformer packages DeserializeComponentsMap(); // RoboBlitz(369) @@ -240,10 +241,32 @@ protected override void Deserialize() || Package.Version <= vHideCategoriesOldOrder || Package.Version >= 576) AutoExpandCategories = DeserializeGroup("AutoExpandCategories"); +#if TRANSFORMERS + if (Package.Build == UnrealPackage.GameBuild.BuildName.Transformers) + { + var constructorsCount = _Buffer.ReadInt32(); + Record("Constructors.Count", constructorsCount); + if (constructorsCount >= 0) + { + int numBytes = constructorsCount * 4; + AssertEOS(numBytes, "Constructors"); + _Buffer.Skip(numBytes); + } + } +#endif + if (Package.Version > 670) { 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 (Package.Version >= 749 #if SPECIALFORCE2 && Package.Build != UnrealPackage.GameBuild.BuildName.SpecialForce2 @@ -258,32 +281,35 @@ protected override void Deserialize() { var unknownName = _Buffer.ReadNameReference(); Record("Unknown:Dishonored", unknownName); + + NativeClassName = _Buffer.ReadText(); + Record(nameof(NativeClassName), NativeClassName); + goto skipEditorContent; } #endif - if (Package.Version >= UnrealPackage.VCLASSGROUP) +#if BATMAN + if (_Buffer.Package.Build == BuildGeneration.RSS && + _Buffer.Package.LicenseeVersion >= 95) { -#if DISHONORED - if (Package.Build == UnrealPackage.GameBuild.BuildName.Dishonored) - { - NativeClassName = _Buffer.ReadText(); - Record(nameof(NativeClassName), NativeClassName); - goto skipClassGroups; - } + uint bm4_v174 = _Buffer.ReadUInt32(); + Record(nameof(bm4_v174), bm4_v174); + } #endif + if (Package.Version >= UnrealPackage.VCLASSGROUP) + { ClassGroups = DeserializeGroup("ClassGroups"); - if (Package.Version >= 813) - { - NativeClassName = _Buffer.ReadText(); - Record(nameof(NativeClassName), NativeClassName); - } } -#if DISHONORED - skipClassGroups: ; -#endif + + // No version check in batman??? + if (Package.Version >= 813) + { + NativeClassName = _Buffer.ReadText(); + Record(nameof(NativeClassName), NativeClassName); + } } } - // FIXME: Found first in(V:655), Definitely not in APB and GoW 2 + // FIXME: Found first in(V:655, DLLBind?), Definitely not in APB and GoW 2 // TODO: Corrigate Version if (Package.Version > 575 && Package.Version < 673 #if TERA @@ -299,12 +325,8 @@ protected override void Deserialize() } } } -#if BATMAN - if (_Buffer.Package.Build == UnrealPackage.GameBuild.BuildName.BatmanUDK) - { - _Buffer.Skip(sizeof(int)); - } -#endif + + skipEditorContent: if (Package.Version >= UnrealPackage.VDLLBIND) { if (!Package.Build.Flags.HasFlag(BuildFlags.NoDLLBind)) @@ -334,13 +356,13 @@ protected override void Deserialize() } } #if THIEF_DS || DeusEx_IW - if (Package.Build.Generation == BuildGeneration.Thief) + if (Package.Build == BuildGeneration.Thief) { string thiefClassVisibleName = _Buffer.ReadText(); Record(nameof(thiefClassVisibleName), thiefClassVisibleName); // Restore the human-readable name if possible - if (!string.IsNullOrEmpty(thiefClassVisibleName) + if (!string.IsNullOrEmpty(thiefClassVisibleName) && Package.Build == UnrealPackage.GameBuild.BuildName.Thief_DS) { var nameEntry = new UNameTableItem() @@ -352,7 +374,7 @@ protected override void Deserialize() } #endif #if VENGEANCE - if (Package.Build.Generation == BuildGeneration.Vengeance) + if (Package.Build == BuildGeneration.Vengeance) { if (Package.LicenseeVersion >= 2) { diff --git a/src/Core/Classes/UClassDecompiler.cs b/src/Core/Classes/UClassDecompiler.cs index db6d3c5e..d0335451 100644 --- a/src/Core/Classes/UClassDecompiler.cs +++ b/src/Core/Classes/UClassDecompiler.cs @@ -189,7 +189,7 @@ protected override string FormatHeader() output += $" within {Within.Name}"; } #if VENGEANCE - if (Package.Build.Generation == BuildGeneration.Vengeance) + if (Package.Build == BuildGeneration.Vengeance) { if (Vengeance_Implements != null && Vengeance_Implements.Any()) output += $" implements {string.Join(", ", Vengeance_Implements.Select(i => i.Name))}"; diff --git a/src/Core/Classes/UDefaultProperty.cs b/src/Core/Classes/UDefaultProperty.cs index 37e22b0a..c38fe6cb 100644 --- a/src/Core/Classes/UDefaultProperty.cs +++ b/src/Core/Classes/UDefaultProperty.cs @@ -188,7 +188,7 @@ private bool DeserializeNextTag() { if (_Buffer.Version < V3) return DeserializeTagUE1(); #if BATMAN - if (_Buffer.Package.Build == UnrealPackage.GameBuild.BuildName.BatmanUDK) + if (_Buffer.Package.Build == BuildGeneration.RSS) return DeserializeTagByOffset(); #endif return DeserializeTagUE3(); @@ -271,7 +271,7 @@ private bool DeserializeTagByOffset() Record(nameof(Type), Type.ToString()); if (Type == PropertyType.None) return true; - if (_Buffer.Package.Build.Generation != BuildGeneration.Batman3MP) + if (_Buffer.Package.Build != UnrealPackage.GameBuild.BuildName.Batman3MP) { ushort offset = _Buffer.ReadUInt16(); Record(nameof(offset), offset); @@ -291,7 +291,7 @@ private bool DeserializeTagByOffset() Type == PropertyType.StructProperty || Type == PropertyType.Vector || Type == PropertyType.Rotator || - (Type == PropertyType.BoolProperty && _Buffer.Package.Build.Generation == BuildGeneration.Batman4)) + (Type == PropertyType.BoolProperty && _Buffer.Package.Build == UnrealPackage.GameBuild.BuildName.Batman4)) { switch(Type) { diff --git a/src/Core/Classes/UFunctionDecompiler.cs b/src/Core/Classes/UFunctionDecompiler.cs index 5bc33d3a..cb371d81 100644 --- a/src/Core/Classes/UFunctionDecompiler.cs +++ b/src/Core/Classes/UFunctionDecompiler.cs @@ -88,7 +88,7 @@ private string FormatFlags() output += "final "; } #if VENGEANCE - if (Package.Build.Generation == BuildGeneration.Vengeance) + if (Package.Build == BuildGeneration.Vengeance) { if (HasFunctionFlag(Flags.FunctionFlags.VG_Overloaded)) { diff --git a/src/Core/Classes/UObject.cs b/src/Core/Classes/UObject.cs index a4b52326..9a9e161a 100644 --- a/src/Core/Classes/UObject.cs +++ b/src/Core/Classes/UObject.cs @@ -225,7 +225,7 @@ protected void VengeanceDeserializeHeader(IUnrealStream stream, ref (int a, int protected virtual void Deserialize() { #if VENGEANCE - if (Package.Build.Generation == BuildGeneration.Vengeance) + if (Package.Build == BuildGeneration.Vengeance) { if (Package.LicenseeVersion >= 25) { @@ -244,16 +244,19 @@ protected virtual void Deserialize() StateFrame = new UStateFrame(); StateFrame.Deserialize(_Buffer); } - - if (_Buffer.Version >= UExportTableItem.VNetObjects -#if MKKE - && Package.Build != UnrealPackage.GameBuild.BuildName.MKKE +#if MKKE || BATMAN + if (Package.Build == UnrealPackage.GameBuild.BuildName.MKKE || + Package.Build == UnrealPackage.GameBuild.BuildName.Batman4) + { + goto skipNetIndex; + } #endif - ) + if (_Buffer.Version >= UExportTableItem.VNetObjects) { int netIndex = _Buffer.ReadInt32(); Record(nameof(netIndex), netIndex); } + skipNetIndex: // TODO: Serialize component data here //if( _Buffer.Version > 400 @@ -265,7 +268,7 @@ protected virtual void Deserialize() //} #if THIEF_DS || DEUSEX_IW // FIXME: Not present in all objects, even some classes? - if (Package.Build.Generation == BuildGeneration.Thief && GetType() != typeof(UnknownObject)) + if (Package.Build == BuildGeneration.Thief && GetType() != typeof(UnknownObject)) { // var native private const int ObjectInternalPropertyHash[1]; int thiefLinkDataObjectCount = _Buffer.ReadInt32(); @@ -432,7 +435,9 @@ public string GetOuterName() [Pure] public string GetClassName() { - return Table.ClassName; + return ImportTable != null + ? ImportTable.ClassName + : Class?.Name ?? "Class"; } /// diff --git a/src/Core/Classes/UState.cs b/src/Core/Classes/UState.cs index 3cb96f65..20b74c65 100644 --- a/src/Core/Classes/UState.cs +++ b/src/Core/Classes/UState.cs @@ -67,7 +67,7 @@ protected override void Deserialize() } #endif - if (Package.Version < VProbeMaskReducedAndIgnoreMaskRemoved) + if (_Buffer.Version < VProbeMaskReducedAndIgnoreMaskRemoved) { ProbeMask = _Buffer.ReadUInt64(); Record(nameof(ProbeMask), ProbeMask); @@ -85,12 +85,13 @@ protected override void Deserialize() LabelTableOffset = _Buffer.ReadUInt16(); Record(nameof(LabelTableOffset), LabelTableOffset); - if (Package.Version >= VStateFlags) + if (_Buffer.Version >= VStateFlags) { -#if BORDERLANDS2 || TRANSFORMERS +#if BORDERLANDS2 || TRANSFORMERS || BATMAN // FIXME:Temp fix if (Package.Build == UnrealPackage.GameBuild.BuildName.Borderlands2 || - Package.Build == UnrealPackage.GameBuild.BuildName.Transformers) + Package.Build == UnrealPackage.GameBuild.BuildName.Transformers || + Package.Build == UnrealPackage.GameBuild.BuildName.Batman4) { _StateFlags = _Buffer.ReadUShort(); goto skipStateFlags; @@ -110,7 +111,7 @@ protected override void Deserialize() } #endif - if (Package.Version < VFuncMap) return; + if (_Buffer.Version < VFuncMap) return; _Buffer.ReadMap(out FuncMap); Record(nameof(FuncMap), FuncMap); } diff --git a/src/Core/Classes/UStruct.cs b/src/Core/Classes/UStruct.cs index ce84c8da..ca1b784f 100644 --- a/src/Core/Classes/UStruct.cs +++ b/src/Core/Classes/UStruct.cs @@ -76,17 +76,27 @@ public partial class UStruct : UField protected override void Deserialize() { base.Deserialize(); - +#if BATMAN + if (Package.Build == UnrealPackage.GameBuild.BuildName.Batman4) + { + goto skipScriptText; + } +#endif // --SuperField if (!Package.IsConsoleCooked()) { ScriptText = _Buffer.ReadObject(); Record(nameof(ScriptText), ScriptText); } - + skipScriptText: Children = _Buffer.ReadObject(); Record(nameof(Children), Children); - +#if BATMAN + if (Package.Build == UnrealPackage.GameBuild.BuildName.Batman4) + { + goto serializeByteCode; + } +#endif // Moved to UFunction in UE3 if (Package.Version < VFriendlyNameMoved) { @@ -97,14 +107,14 @@ protected override void Deserialize() // Standard, but UT2004' derived games do not include this despite reporting version 128+ if (Package.Version >= VCppText && !Package.IsConsoleCooked() - && Package.Build.Generation != BuildGeneration.UE2_5) + && Package.Build != BuildGeneration.UE2_5) { CppText = _Buffer.ReadObject(); Record(nameof(CppText), CppText); } #if VENGEANCE // Introduced with BioShock - if (Package.Build.Generation == BuildGeneration.Vengeance && + if (Package.Build == BuildGeneration.Vengeance && Package.LicenseeVersion >= 29) { int vengeanceUnknownObject = _Buffer.ReadObjectIndex(); @@ -113,14 +123,14 @@ protected override void Deserialize() #endif // UE3 or UE2.5 build, it appears that StructFlags may have been merged from an early UE3 build. // UT2004 reports version 26, and BioShock version 2 - if ((Package.Build.Generation == BuildGeneration.UE2_5 && Package.LicenseeVersion >= 26) || - (Package.Build.Generation == BuildGeneration.Vengeance && Package.LicenseeVersion >= 2)) + if ((Package.Build == BuildGeneration.UE2_5 && Package.LicenseeVersion >= 26) || + (Package.Build == BuildGeneration.Vengeance && Package.LicenseeVersion >= 2)) { StructFlags = _Buffer.ReadUInt32(); Record(nameof(StructFlags), (StructFlags)StructFlags); } #if VENGEANCE - if (Package.Build.Generation == BuildGeneration.Vengeance && + if (Package.Build == BuildGeneration.Vengeance && Package.LicenseeVersion >= 14) { ProcessedText = _Buffer.ReadObject(); @@ -151,6 +161,7 @@ protected override void Deserialize() _Buffer.Skip(4); } #endif + serializeByteCode: ByteScriptSize = _Buffer.ReadInt32(); Record(nameof(ByteScriptSize), ByteScriptSize); const int vDataScriptSize = 639; diff --git a/src/Core/Tables/UExportTableItem.cs b/src/Core/Tables/UExportTableItem.cs index 2168dd3b..b65d31a8 100644 --- a/src/Core/Tables/UExportTableItem.cs +++ b/src/Core/Tables/UExportTableItem.cs @@ -143,9 +143,9 @@ public void Deserialize(IUnrealStream stream) } #if BATMAN - if (stream.Package.Build == UnrealPackage.GameBuild.BuildName.BatmanUDK) + if (stream.Package.Build == BuildGeneration.RSS) { - stream.Skip( sizeof(int) ); + stream.Skip(sizeof(int)); } #endif diff --git a/src/Core/Tokens/CastTokens.cs b/src/Core/Tokens/CastTokens.cs index 0502d37f..1b555939 100644 --- a/src/Core/Tokens/CastTokens.cs +++ b/src/Core/Tokens/CastTokens.cs @@ -61,7 +61,7 @@ public override string Decompile() } } - public class InterfaceCastToken : ObjectCastToken + public class InterfaceCastToken : DynamicCastToken { } diff --git a/src/Core/Tokens/FieldTokens.cs b/src/Core/Tokens/FieldTokens.cs index f881bf4f..8855a3a6 100644 --- a/src/Core/Tokens/FieldTokens.cs +++ b/src/Core/Tokens/FieldTokens.cs @@ -95,8 +95,7 @@ public override void Deserialize(IUnrealStream stream) Decompiler.AlignSize(sizeof(int)); } - PropertyName = stream.ReadNameReference(); - Decompiler.AlignNameSize(); + PropertyName = ReadName(stream); // TODO: Corrigate version. Seen in version ~648(The Ball) may have been introduced earlier, but not prior 610. if (stream.Version > 610) @@ -170,10 +169,11 @@ public override string Decompile() public class InstanceDelegateToken : Token { + public UName DelegateName; + public override void Deserialize(IUnrealStream stream) { - stream.ReadNameIndex(); - Decompiler.AlignNameSize(); + DelegateName = ReadName(stream); } } } diff --git a/src/Core/Tokens/FunctionTokens.cs b/src/Core/Tokens/FunctionTokens.cs index 11bd6987..a5e904c9 100644 --- a/src/Core/Tokens/FunctionTokens.cs +++ b/src/Core/Tokens/FunctionTokens.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Runtime.CompilerServices; using System.Text; namespace UELib.Core @@ -14,12 +15,19 @@ public class EndFunctionParmsToken : Token public abstract class FunctionToken : Token { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected UName DeserializeFunctionName(IUnrealStream stream) + { + return ReadName(stream); + } + protected void DeserializeCall() { DeserializeParms(); Decompiler.DeserializeDebugToken(); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private void DeserializeParms() { #pragma warning disable 642 @@ -27,23 +35,6 @@ private void DeserializeParms() #pragma warning restore 642 } - protected void DeserializeBinaryOperator() - { - DeserializeNext(); - DeserializeNext(); - - DeserializeNext(); // ) - Decompiler.DeserializeDebugToken(); - } - - protected void DeserializeUnaryOperator() - { - DeserializeNext(); - - DeserializeNext(); // ) - Decompiler.DeserializeDebugToken(); - } - private static string PrecedenceToken(Token t) { if (!(t is FunctionToken)) @@ -61,29 +52,46 @@ private static string PrecedenceToken(Token t) break; } - return addParenthesis ? $"({t.Decompile()})" : t.Decompile(); + return addParenthesis + ? $"({t.Decompile()})" + : t.Decompile(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool NeedsSpace(string operatorName) + { + return char.IsUpper(operatorName[0]) + || char.IsLower(operatorName[0]); } protected string DecompilePreOperator(string operatorName) { - string output = operatorName + (operatorName.Length > 1 ? " " : string.Empty) + DecompileNext(); - DecompileNext(); // ) - return output; + string operand = DecompileNext(); + AssertSkipCurrentToken(); + + // Only space out if we have a non-symbol operator name. + return NeedsSpace(operatorName) + ? $"{operatorName} {operand}" + : $"{operatorName}{operand}"; } protected string DecompileOperator(string operatorName) { var output = - $"{PrecedenceToken(GrabNextToken())} {operatorName} {PrecedenceToken(GrabNextToken())}"; - DecompileNext(); // ) + $"{PrecedenceToken(NextToken())} {operatorName} {PrecedenceToken(NextToken())}"; + AssertSkipCurrentToken(); return output; } protected string DecompilePostOperator(string operatorName) { - string output = $"{operatorName} {DecompileNext()}"; - DecompileNext(); // ) - return output; + string operand = DecompileNext(); + AssertSkipCurrentToken(); + + // Only space out if we have a non-symbol operator name. + return NeedsSpace(operatorName) + ? $"{operand} {operatorName}" + : $"{operand}{operatorName}"; } protected string DecompileCall(string functionName) @@ -108,7 +116,7 @@ private string DecompileParms() var tokens = new List>(); { next: - var t = GrabNextToken(); + var t = NextToken(); tokens.Add(Tuple.Create(t, t.Decompile())); if (!(t is EndFunctionParmsToken)) goto next; @@ -161,7 +169,7 @@ public override void Deserialize(IUnrealStream stream) Decompiler.AlignSize(sizeof(int)); } - Function = stream.ReadObject() as UFunction; + Function = stream.ReadObject(); Decompiler.AlignObjectSize(); DeserializeCall(); @@ -242,9 +250,7 @@ public override void Deserialize(IUnrealStream stream) Decompiler.AlignSize(sizeof(int)); } - FunctionName = stream.ReadNameReference(); - Decompiler.AlignNameSize(); - + FunctionName = DeserializeFunctionName(stream); DeserializeCall(); } @@ -261,9 +267,7 @@ public class GlobalFunctionToken : FunctionToken public override void Deserialize(IUnrealStream stream) { - FunctionName = stream.ReadNameReference(); - Decompiler.AlignNameSize(); - + FunctionName = DeserializeFunctionName(stream); DeserializeCall(); } @@ -276,24 +280,23 @@ public override string Decompile() public class DelegateFunctionToken : FunctionToken { + public byte? IsLocal; + public UProperty DelegateProperty; public UName FunctionName; public override void Deserialize(IUnrealStream stream) { // TODO: Corrigate Version - if (stream.Version > 180) + if (stream.Version >= 181) { - ++stream.Position; // ReadByte() + IsLocal = stream.ReadByte(); Decompiler.AlignSize(sizeof(byte)); } - // Delegate object index - stream.ReadObjectIndex(); + DelegateProperty = stream.ReadObject(); Decompiler.AlignObjectSize(); - FunctionName = stream.ReadNameReference(); - Decompiler.AlignNameSize(); - + FunctionName = DeserializeFunctionName(stream); DeserializeCall(); } @@ -310,35 +313,7 @@ public class NativeFunctionToken : FunctionToken public override void Deserialize(IUnrealStream stream) { - if (NativeItem == null) - { - NativeItem = new NativeTableItem - { - Type = FunctionType.Function, - Name = "UnresolvedNativeFunction_" + RepresentToken, - ByteToken = RepresentToken - }; - } - - switch (NativeItem.Type) - { - case FunctionType.Function: - DeserializeCall(); - break; - - case FunctionType.PreOperator: - case FunctionType.PostOperator: - DeserializeUnaryOperator(); - break; - - case FunctionType.Operator: - DeserializeBinaryOperator(); - break; - - default: - DeserializeCall(); - break; - } + DeserializeCall(); } public override string Decompile() diff --git a/src/Core/Tokens/JumpTokens.cs b/src/Core/Tokens/JumpTokens.cs index 8b67e4df..27de6962 100644 --- a/src/Core/Tokens/JumpTokens.cs +++ b/src/Core/Tokens/JumpTokens.cs @@ -574,18 +574,20 @@ public override string Decompile() public class ArrayIteratorToken : JumpToken { - protected bool HasSecondParm; + public byte WithIndexParam; public override void Deserialize(IUnrealStream stream) { // Expression DeserializeNext(); - // Param 1 + // Item param DeserializeNext(); - HasSecondParm = stream.ReadByte() > 0; + WithIndexParam = stream.ReadByte(); Decompiler.AlignSize(sizeof(byte)); + + // Index param DeserializeNext(); base.Deserialize(stream); @@ -598,10 +600,20 @@ public override string Decompile() Commentize(); // foreach ArrayVariable( Parameters ) - var output = $"foreach {DecompileNext()}({DecompileNext()}"; - output += (HasSecondParm ? ", " : string.Empty) + DecompileNext(); + string output; + if (WithIndexParam > 0) + { + output = $"foreach {DecompileNext()}({DecompileNext()}, {DecompileNext()})"; + } + else + { + output = $"foreach {DecompileNext()}({DecompileNext()})"; + // Skip Index param + NextToken(); + } + Decompiler._CanAddSemicolon = false; - return $"{output})"; + return output; } } @@ -691,4 +703,4 @@ public override string Decompile() } } } -} \ No newline at end of file +} diff --git a/src/Core/Tokens/Token.cs b/src/Core/Tokens/Token.cs index 62bab382..8c45397f 100644 --- a/src/Core/Tokens/Token.cs +++ b/src/Core/Tokens/Token.cs @@ -1,5 +1,6 @@ using System; using System.Diagnostics; +using System.Runtime.CompilerServices; namespace UELib.Core { @@ -51,34 +52,99 @@ public virtual string Disassemble() protected string DecompileNext() { - tryNext: - var t = Decompiler.NextToken; - if (t is DebugInfoToken) goto tryNext; + tryNext: + var token = Decompiler.NextToken; + if (token is DebugInfoToken) goto tryNext; - try - { - return t.Decompile(); - } - catch (Exception e) - { - return $"{t.GetType().Name}({e.GetType().Name})"; - } + return token.Decompile(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected T NextToken() + where T : Token + { + return (T)NextToken(); } - protected Token GrabNextToken() + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected Token NextToken() { - tryNext: + tryNext: var t = Decompiler.NextToken; if (t is DebugInfoToken) goto tryNext; return t; } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected void SkipCurrentToken() + { + ++Decompiler.CurrentTokenIndex; + } + + /// + /// Asserts that the token that we want to skip is indeed of the correct type, this also skips past any . + /// + /// The type of the token that we want to assert. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected void AssertSkipCurrentToken() + where T : Token + { + tryNext: + var token = Decompiler.NextToken; + if (token is DebugInfoToken) goto tryNext; + // This assertion will fail in most cases if the native indexes are a mismatch. +#if STRICT + Debug.Assert(token is T, $"Expected to skip a token of type '{typeof(T)}', but got '{token.GetType()}'"); +#endif + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] protected Token DeserializeNext() { return Decompiler.DeserializeNext(); } + /// + /// Wrapper for IUnrealStream.ReadNameReference to handle memory alignment as well as differences between builds. + /// + /// In Batman4 tokens with name references have been reduced to only serialize the index reference, except for NameConstToken among others. + /// + /// TODO: Maybe wrap the IUnrealStream underlying type instead, and implement all memory alignment logic in there. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected UName ReadName(IUnrealStream stream) + { +#if BATMAN + // (Only for byte-codes) No int32 numeric followed after a name index for Batman4 + if (stream.Package.Build == UnrealPackage.GameBuild.BuildName.Batman4) + { + return ReadNameNoNumber(stream); + } +#endif + var name = stream.ReadNameReference(); + Decompiler.AlignNameSize(); + return name; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected UName ReadNameNoNumber(IUnrealStream stream) + { + int nameIndex = stream.ReadInt32(); + Decompiler.AlignSize(sizeof(int)); + var name = new UName(stream.Package.Names[nameIndex], 0); + return name; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected T ReadObject(IUnrealStream stream) + where T : UObject + { + var obj = stream.ReadObject(); + Decompiler.AlignObjectSize(); + return obj; + } + public override string ToString() { return diff --git a/src/Core/UStateFrame.cs b/src/Core/UStateFrame.cs index 49f1d9c9..50ef56e0 100644 --- a/src/Core/UStateFrame.cs +++ b/src/Core/UStateFrame.cs @@ -8,6 +8,8 @@ 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; @@ -28,7 +30,7 @@ public void Deserialize(IUnrealStream stream) LatentAction = stream.Version < VLatentActionReduced ? stream.ReadUInt32() : stream.ReadUInt16(); - stream.ReadArray(out StateStack); + if (stream.Version >= VStateStack) stream.ReadArray(out StateStack); if (Node != null) Offset = stream.ReadIndex(); } @@ -44,7 +46,7 @@ public void Serialize(IUnrealStream stream) ? LatentAction : (ushort)LatentAction ); - stream.Write(ref StateStack); + if (stream.Version >= VStateStack) stream.Write(ref StateStack); if (Node != null) stream.Write(Offset); } diff --git a/src/Eliot.UELib.csproj b/src/Eliot.UELib.csproj index 813d88e3..5fdb3a3b 100644 --- a/src/Eliot.UELib.csproj +++ b/src/Eliot.UELib.csproj @@ -115,6 +115,8 @@ + + @@ -252,7 +254,7 @@ - +