From cc40832a503dcb6121e9d9a85ba1d95261375663 Mon Sep 17 00:00:00 2001 From: Eliot Date: Tue, 6 Dec 2022 18:30:56 +0100 Subject: [PATCH 01/13] Merge in UELib 1.4 fixes for Batman among other minor changes. --- src/ByteCodeDecompiler.cs | 16 +++++- src/Core/Classes/Props/UProperty.cs | 12 +++-- src/Core/Classes/UClass.cs | 6 ++- src/Core/Classes/UDefaultProperty.cs | 6 +-- src/Core/Classes/UObject.cs | 17 ++++--- src/Core/Classes/UState.cs | 11 +++-- src/Core/Classes/UStruct.cs | 17 +++++-- src/Core/Tables/UExportTableItem.cs | 4 +- src/Core/Tokens/FieldTokens.cs | 8 +-- src/Core/Tokens/FunctionTokens.cs | 31 ++++++------ src/Core/Tokens/JumpTokens.cs | 26 +++++++--- src/Core/Tokens/Token.cs | 35 +++++++++++++ src/UnrealBuild.cs | 73 +++++++++++++++++++++++++--- src/UnrealPackage.cs | 48 ++++++++++++++---- 14 files changed, 244 insertions(+), 66 deletions(-) diff --git a/src/ByteCodeDecompiler.cs b/src/ByteCodeDecompiler.cs index 7ff9cbf1..c0b8c7d3 100644 --- a/src/ByteCodeDecompiler.cs +++ b/src/ByteCodeDecompiler.cs @@ -6,6 +6,7 @@ using System.IO; using System.Runtime.CompilerServices; using UELib.Annotations; +using UELib.Flags; using UELib.Tokens; namespace UELib.Core @@ -504,7 +505,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 @@ -1571,6 +1577,10 @@ public string Decompile() #endif } } + catch (EndOfStreamException) + { + break; + } catch (Exception e) { output.Append($"// ({e.GetType().Name})"); @@ -1591,6 +1601,10 @@ public string Decompile() } } } + catch (EndOfStreamException) + { + break; + } catch (Exception e) { output.AppendLine("\r\n" diff --git a/src/Core/Classes/Props/UProperty.cs b/src/Core/Classes/Props/UProperty.cs index 56ecb667..442c2cdf 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) { diff --git a/src/Core/Classes/UClass.cs b/src/Core/Classes/UClass.cs index e5526fb2..7a418eef 100644 --- a/src/Core/Classes/UClass.cs +++ b/src/Core/Classes/UClass.cs @@ -300,9 +300,13 @@ protected override void Deserialize() } } #if BATMAN - if (_Buffer.Package.Build == UnrealPackage.GameBuild.BuildName.BatmanUDK) + if (_Buffer.Package.Build == BuildGeneration.RSS) { _Buffer.Skip(sizeof(int)); + if (Package.Build == UnrealPackage.GameBuild.BuildName.Batman4) + { + _Buffer.Skip(sizeof(int)); + } } #endif if (Package.Version >= UnrealPackage.VDLLBIND) 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/UObject.cs b/src/Core/Classes/UObject.cs index a4b52326..1d0b3606 100644 --- a/src/Core/Classes/UObject.cs +++ b/src/Core/Classes/UObject.cs @@ -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 @@ -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..51db67eb 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) { @@ -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/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..619d27ec 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 @@ -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(); } diff --git a/src/Core/Tokens/JumpTokens.cs b/src/Core/Tokens/JumpTokens.cs index 8b67e4df..c179df95 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 + GrabNextToken(); + } + 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..462db95c 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 { @@ -74,11 +75,45 @@ protected Token GrabNextToken() return t; } + [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) + { + UName name; +#if BATMAN + // (Only for byte-codes) No int32 numeric followed after a name index for Batman4 + if (stream.Package.Build == UnrealPackage.GameBuild.BuildName.Batman4) + { + Decompiler.AlignSize(sizeof(int)); + int nameIndex = stream.ReadInt32(); + name = new UName(stream.Package.Names[nameIndex]); + return name; + } +#endif + Decompiler.AlignNameSize(); + name = stream.ReadNameReference(); + return name; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected T ReadObject(IUnrealStream stream) where T : UObject + { + Decompiler.AlignObjectSize(); + return stream.ReadObject(); + } + public override string ToString() { return diff --git a/src/UnrealBuild.cs b/src/UnrealBuild.cs index a38284e8..263f08aa 100644 --- a/src/UnrealBuild.cs +++ b/src/UnrealBuild.cs @@ -5,18 +5,75 @@ namespace UELib public enum BuildGeneration { Undefined, - + + /// + /// Unreal Engine 1 + /// + /// Not in use yet. + /// + UE1, + + /// + /// Unreal Engine 2 + /// + /// Not in use yet. + /// + UE2, + + /// + /// Heavily modified Unreal Engine 2 by Ion Storm for Thief: Deadly Shadows + /// Thief, + + /// + /// Unreal Engine 2 with some early UE3 upgrades. + /// UE2_5, - UE2X, + + /// + /// Heavily modified Unreal Engine 2.5 for Vengeance: Tribes; also used by Swat4 and BioShock. + /// Vengeance, + + /// + /// Heavily modified Unreal Engine 2.5 for Splinter Cell + /// + /// Not yet supported. + /// Lead, - - // Batman2+ use the same Engine spinoff, but it's still necessary to distinguish the builds by name. - Batman2, - Batman3, - Batman3MP, - Batman4 + + /// + /// Modified Unreal Engine 2 for Xbox e.g. Unreal Championship 2: The Liandri Conflict + /// + UE2X, + + /// + /// Unreal Engine 3 + /// + /// Not in use yet. + /// + UE3, + + /// + /// Rocksteady Studios + /// + /// Heavily modified Unreal Engine 3 for the Arkham series + /// + RSS, + + /// + /// High Moon Studios + /// + /// Heavily modified Unreal Engine 3 for Transformers and Deadpool etc + /// + HMS, + + /// + /// Unreal Engine 4 + /// + /// Not in use yet. + /// + UE4 } [Flags] diff --git a/src/UnrealPackage.cs b/src/UnrealPackage.cs index 7b5e3ee0..0599d8e7 100644 --- a/src/UnrealPackage.cs +++ b/src/UnrealPackage.cs @@ -501,17 +501,37 @@ public enum BuildName [Build(860, 4)] Hawken, /// - /// 805-6/101-3 - /// 807/137-8 + /// Batman: Arkham City + /// + /// 805/101 + /// + [Build(805, 101, BuildGeneration.RSS)] + Batman2, + + /// + /// Batman: Arkham Origins + /// + /// 806/103 + /// 807/137-138 + /// + [Build(806, 103, BuildGeneration.RSS)] + [Build(807, 807, 137, 138, BuildGeneration.RSS)] + Batman3, + + /// /// 807/104 - /// 863/32995 /// - [Build(805, 101, BuildGeneration.Batman2)] - [Build(806, 103, BuildGeneration.Batman3)] - [Build(807, 807, 137, 138, BuildGeneration.Batman3)] - [Build(807, 104, BuildGeneration.Batman3MP)] - [Build(863, 32995, BuildGeneration.Batman4)] - BatmanUDK, + [Build(807, 104, BuildGeneration.RSS)] + Batman3MP, + + /// + /// Batman: Arkham Knight + /// + /// 863/32995(227 & ~8000) + /// + [Build(863, 32995, BuildGeneration.RSS)] + //[OverridePackageVersion(863, 227)] + Batman4, /// /// 867/009:032 @@ -582,7 +602,17 @@ public GameBuild(UnrealPackage package) if (Name == BuildName.Unset) Name = package.LicenseeVersion == 0 ? BuildName.Default : BuildName.Unknown; } + + 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 i) { return b != null && b.Name == i; From 69e827115e52aca827d243fb02130baeb7556ee2 Mon Sep 17 00:00:00 2001 From: Eliot Date: Sat, 28 May 2022 19:20:34 +0200 Subject: [PATCH 02/13] Fix: Swap InterfaceToBool with InterfaceToObject (cherry picked from commit ffaca763f2c83bf5fb312592db7c09fe367e66b1) --- src/UnrealTokens.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/UnrealTokens.cs b/src/UnrealTokens.cs index e678152f..adef027a 100644 --- a/src/UnrealTokens.cs +++ b/src/UnrealTokens.cs @@ -207,9 +207,9 @@ public enum CastToken : byte None = 0x00, #region UE3 - InterfaceToBool = 0x36, + InterfaceToObject = 0x36, InterfaceToString = 0x37, - InterfaceToObject = 0x38, + InterfaceToBool = 0x38, #endregion RotatorToVector = 0x39, // Redefined From 7988debd19457497a7e0923966417a57166a4224 Mon Sep 17 00:00:00 2001 From: Eliot Date: Tue, 6 Dec 2022 18:33:24 +0100 Subject: [PATCH 03/13] (Cherry pick) Put StateStack behind a package version (Fixes < UE3 map actors). --- src/Core/UStateFrame.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) 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); } From 1c10f35d0e41d5b93f6142004cbed26a2a4d64a5 Mon Sep 17 00:00:00 2001 From: Eliot Date: Wed, 8 Jun 2022 06:55:51 +0200 Subject: [PATCH 04/13] (Transformers) Support UClass constructors. (cherry picked from commit b564102b2af94e1f244397c6538095d6e429f28f) (cherry picked from commit 09ef07d5e23a896ba24f7311fa482e0b6367a831) --- src/Core/Classes/UClass.cs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/Core/Classes/UClass.cs b/src/Core/Classes/UClass.cs index 7a418eef..3f559f49 100644 --- a/src/Core/Classes/UClass.cs +++ b/src/Core/Classes/UClass.cs @@ -203,6 +203,7 @@ protected override void Deserialize() ) DeserializeHideCategories(); + // Seems to have been removed in transformer packages DeserializeComponentsMap(); // RoboBlitz(369) @@ -240,6 +241,20 @@ 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"); @@ -283,7 +298,7 @@ protected override void Deserialize() } } - // 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 From 7190a8084f62f6763aaa945377ba526b1d26c544 Mon Sep 17 00:00:00 2001 From: Eliot Date: Wed, 8 Jun 2022 09:36:06 +0200 Subject: [PATCH 05/13] Use shorthand Build == BuildGeneration (cherry picked from commit 354ff5ec720a87c593ab0900447a9824b9756e58) --- src/Core/Classes/Props/UProperty.cs | 10 +++++----- src/Core/Classes/UClass.cs | 6 +++--- src/Core/Classes/UClassDecompiler.cs | 2 +- src/Core/Classes/UFunctionDecompiler.cs | 2 +- src/Core/Classes/UObject.cs | 4 ++-- src/Core/Classes/UStruct.cs | 10 +++++----- src/UnrealPackage.cs | 16 ++++++++-------- src/UnrealStream.cs | 2 +- 8 files changed, 26 insertions(+), 26 deletions(-) diff --git a/src/Core/Classes/Props/UProperty.cs b/src/Core/Classes/Props/UProperty.cs index 442c2cdf..d693582f 100644 --- a/src/Core/Classes/Props/UProperty.cs +++ b/src/Core/Classes/Props/UProperty.cs @@ -114,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(); @@ -134,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); @@ -151,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); @@ -161,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 3f559f49..9d38f00c 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); @@ -353,7 +353,7 @@ 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); @@ -371,7 +371,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/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 1d0b3606..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) { @@ -268,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(); diff --git a/src/Core/Classes/UStruct.cs b/src/Core/Classes/UStruct.cs index 51db67eb..ca1b784f 100644 --- a/src/Core/Classes/UStruct.cs +++ b/src/Core/Classes/UStruct.cs @@ -107,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(); @@ -123,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(); diff --git a/src/UnrealPackage.cs b/src/UnrealPackage.cs index 0599d8e7..c8286b73 100644 --- a/src/UnrealPackage.cs +++ b/src/UnrealPackage.cs @@ -602,25 +602,25 @@ public GameBuild(UnrealPackage package) if (Name == BuildName.Unset) Name = package.LicenseeVersion == 0 ? BuildName.Default : BuildName.Unknown; } - + public static bool operator ==(GameBuild b, BuildGeneration gen) { - return b.Generation == gen; + return b?.Generation == gen; } public static bool operator !=(GameBuild b, BuildGeneration gen) { - return b.Generation != gen; + return b?.Generation != gen; } - - public static bool operator ==(GameBuild b, BuildName i) + + public static bool operator ==(GameBuild b, BuildName name) { - return b != null && b.Name == i; + return b?.Name == name; } - public static bool operator !=(GameBuild b, BuildName i) + public static bool operator !=(GameBuild b, BuildName name) { - return b != null && b.Name != i; + return b?.Name != name; } /// diff --git a/src/UnrealStream.cs b/src/UnrealStream.cs index e02bb6cf..5426b401 100644 --- a/src/UnrealStream.cs +++ b/src/UnrealStream.cs @@ -148,7 +148,7 @@ public string ReadText() #endif int unfixedSize = ReadIndex(); #if BIOSHOCK - if (_Archive.Package.Build.Generation == BuildGeneration.Vengeance && + if (_Archive.Package.Build == BuildGeneration.Vengeance && _Archive.Version >= 135) { unfixedSize = -unfixedSize; From a1969bb6f450b726d6aa7dce3fd6f0da327304e8 Mon Sep 17 00:00:00 2001 From: Eliot Date: Wed, 8 Jun 2022 09:39:02 +0200 Subject: [PATCH 06/13] Add a new attribute to set an overridden package version for particular builds. (cherry picked from commit 42783b1602edd590b942e02435ba392e10434b69) --- src/UnrealBuild.cs | 18 ++++++++++++ src/UnrealPackage.cs | 69 ++++++++++++++++++++++---------------------- 2 files changed, 53 insertions(+), 34 deletions(-) diff --git a/src/UnrealBuild.cs b/src/UnrealBuild.cs index 263f08aa..c5733e11 100644 --- a/src/UnrealBuild.cs +++ b/src/UnrealBuild.cs @@ -94,4 +94,22 @@ public enum BuildFlags : byte /// NoDLLBind = 0x04 } + + [AttributeUsage(AttributeTargets.Field)] + public sealed class OverridePackageVersionAttribute : Attribute + { + public readonly uint FixedVersion; + public readonly ushort? FixedLicenseeVersion; + + public OverridePackageVersionAttribute(uint fixedVersion) + { + FixedVersion = fixedVersion; + } + + public OverridePackageVersionAttribute(uint fixedVersion, ushort? fixedLicenseeVersion) + { + FixedVersion = fixedVersion; + FixedLicenseeVersion = fixedLicenseeVersion; + } + } } diff --git a/src/UnrealPackage.cs b/src/UnrealPackage.cs index c8286b73..b5833471 100644 --- a/src/UnrealPackage.cs +++ b/src/UnrealPackage.cs @@ -80,13 +80,7 @@ public sealed class UnrealPackage : IDisposable, IBuffered #region Serialized Members - private uint _Version; - - public uint Version - { - get => OverrideVersion > 0 ? OverrideVersion : _Version; - set => _Version = value; - } + public uint Version { get; set; } /// /// For debugging purposes. Change this to override the present Version deserialized from the package. @@ -119,13 +113,7 @@ public uint Version #endregion - private ushort _LicenseeVersion; - - public ushort LicenseeVersion - { - get => OverrideLicenseeVersion > 0 ? OverrideLicenseeVersion : _LicenseeVersion; - private set => _LicenseeVersion = value; - } + public ushort LicenseeVersion { get; set; } /// /// For debugging purposes. Change this to override the present Version deserialized from the package. @@ -548,7 +536,9 @@ public enum BuildName public BuildName Name { get; } public uint Version { get; } + public uint? OverrideVersion { get; } public uint LicenseeVersion { get; } + public ushort? OverrideLicenseeVersion { get; } /// /// Is cooked for consoles. @@ -570,32 +560,34 @@ public GameBuild(UnrealPackage package) { if (UnrealConfig.Platform == UnrealConfig.CookedPlatform.Console) Flags |= BuildFlags.ConsoleCooked; - var gameBuilds = (BuildName[])Enum.GetValues(typeof(BuildName)); - foreach (var gameBuild in gameBuilds) + var builds = typeof(BuildName).GetFields(); + foreach (var build in builds) { - var gameBuildMember = typeof(BuildName).GetMember(gameBuild.ToString()); - if (gameBuildMember.Length == 0) - continue; - - object[] attribs = gameBuildMember[0].GetCustomAttributes(false); - var game = attribs.OfType().SingleOrDefault(attr => attr.Verify(this, package)); - if (game == null) + var buildAttributes = build.GetCustomAttributes(false); + var buildAttribute = buildAttributes.FirstOrDefault(attr => attr.Verify(this, package)); + if (buildAttribute == null) continue; Version = package.Version; LicenseeVersion = package.LicenseeVersion; - Flags = game.Flags; - Generation = game.Generation; + Flags = buildAttribute.Flags; + Generation = buildAttribute.Generation; + + var overrideAttribute = build.GetCustomAttribute(false); + if (overrideAttribute != null) + { + OverrideVersion = overrideAttribute.FixedVersion; + OverrideLicenseeVersion = overrideAttribute.FixedLicenseeVersion; + } - Name = (BuildName)Enum.Parse(typeof(BuildName), Enum.GetName(typeof(BuildName), gameBuild)); + Name = (BuildName)Enum.Parse(typeof(BuildName), build.Name); if (package.Decoder != null) break; - var buildDecoderAttr = - attribs.SingleOrDefault(attr => attr is BuildDecoderAttribute) as BuildDecoderAttribute; - if (buildDecoderAttr == null) + var buildDecoderAttribute = build.GetCustomAttribute(false); + if (buildDecoderAttribute == null) break; - package.Decoder = buildDecoderAttr.CreateDecoder(); + package.Decoder = buildDecoderAttribute.CreateDecoder(); break; } @@ -1001,11 +993,9 @@ public void Deserialize(UPackageStream stream) LicenseeVersion = (ushort)(Version >> 16); Version &= 0xFFFFU; Console.WriteLine("Package Version:" + Version + "/" + LicenseeVersion); - - Build = new GameBuild(this); - Console.WriteLine("Build:" + Build.Name); - + SetupBuild(stream); stream.BuildDetected(Build); + Console.WriteLine("Build:" + Build.Name); if (Version >= VHeaderSize) { @@ -1341,6 +1331,17 @@ public void Deserialize(UPackageStream stream) HeaderSize = (int)stream.Position; } + private void SetupBuild(UPackageStream stream) + { + Build = new GameBuild(this); + + if (Build.OverrideVersion.HasValue) Version = Build.OverrideVersion.Value; + if (Build.OverrideLicenseeVersion.HasValue) LicenseeVersion = Build.OverrideLicenseeVersion.Value; + + if (OverrideVersion != 0) Version = OverrideVersion; + if (OverrideLicenseeVersion != 0) LicenseeVersion = OverrideLicenseeVersion; + } + /// /// Constructs all export objects. /// From 0047a95af58510d63bb62a983b469695ed65587c Mon Sep 17 00:00:00 2001 From: Eliot Date: Tue, 6 Dec 2022 18:44:42 +0100 Subject: [PATCH 07/13] More cherry picks to patch up Batman. --- src/UnrealBuild.cs | 2 +- src/UnrealPackage.cs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/UnrealBuild.cs b/src/UnrealBuild.cs index c5733e11..095d8d4d 100644 --- a/src/UnrealBuild.cs +++ b/src/UnrealBuild.cs @@ -106,7 +106,7 @@ public OverridePackageVersionAttribute(uint fixedVersion) FixedVersion = fixedVersion; } - public OverridePackageVersionAttribute(uint fixedVersion, ushort? fixedLicenseeVersion) + public OverridePackageVersionAttribute(uint fixedVersion, ushort fixedLicenseeVersion) { FixedVersion = fixedVersion; FixedLicenseeVersion = fixedLicenseeVersion; diff --git a/src/UnrealPackage.cs b/src/UnrealPackage.cs index b5833471..5ea27a03 100644 --- a/src/UnrealPackage.cs +++ b/src/UnrealPackage.cs @@ -512,13 +512,14 @@ public enum BuildName [Build(807, 104, BuildGeneration.RSS)] Batman3MP, + /// /// Batman: Arkham Knight /// /// 863/32995(227 & ~8000) /// [Build(863, 32995, BuildGeneration.RSS)] - //[OverridePackageVersion(863, 227)] + [OverridePackageVersion(863, 227)] Batman4, /// From d0a87562103d584000baca16ffab6ec4ec40331a Mon Sep 17 00:00:00 2001 From: Eliot Date: Tue, 6 Dec 2022 18:48:27 +0100 Subject: [PATCH 08/13] Change version 1.3.0 -> 1.3.1 --- src/Properties/AssemblyInfo.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Properties/AssemblyInfo.cs b/src/Properties/AssemblyInfo.cs index b39fca42..0761bf59 100644 --- a/src/Properties/AssemblyInfo.cs +++ b/src/Properties/AssemblyInfo.cs @@ -31,5 +31,5 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.3.0")] -[assembly: AssemblyFileVersion("1.3.0")] \ No newline at end of file +[assembly: AssemblyVersion("1.3.1")] +[assembly: AssemblyFileVersion("1.3.1")] \ No newline at end of file From 0ffe978be8cdb255eb88ce6b281b3dcb7c9b452c Mon Sep 17 00:00:00 2001 From: Eliot Date: Tue, 6 Dec 2022 19:50:18 +0100 Subject: [PATCH 09/13] Complete UClass support for Batman series #51. --- src/Core/Classes/UClass.cs | 61 ++++++++++++++++++++------------------ 1 file changed, 32 insertions(+), 29 deletions(-) diff --git a/src/Core/Classes/UClass.cs b/src/Core/Classes/UClass.cs index 9d38f00c..40538a02 100644 --- a/src/Core/Classes/UClass.cs +++ b/src/Core/Classes/UClass.cs @@ -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 @@ -258,7 +258,15 @@ protected override void Deserialize() 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 @@ -273,28 +281,31 @@ 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); + } } } @@ -314,16 +325,8 @@ protected override void Deserialize() } } } -#if BATMAN - if (_Buffer.Package.Build == BuildGeneration.RSS) - { - _Buffer.Skip(sizeof(int)); - if (Package.Build == UnrealPackage.GameBuild.BuildName.Batman4) - { - _Buffer.Skip(sizeof(int)); - } - } -#endif + + skipEditorContent: if (Package.Version >= UnrealPackage.VDLLBIND) { if (!Package.Build.Flags.HasFlag(BuildFlags.NoDLLBind)) @@ -359,7 +362,7 @@ protected override void Deserialize() 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() From ff0646cea4249cba1e99838ef11e012eb3a7d3f7 Mon Sep 17 00:00:00 2001 From: Eliot Date: Tue, 6 Dec 2022 21:28:08 +0100 Subject: [PATCH 10/13] Migrate some improvements from the develop branch. More (hacky) byte-code fixes for Batman4 #51. --- src/Branch/UE3/RSS/Tokens/Bm4ContextToken.cs | 9 ++ .../UE3/RSS/Tokens/NameConstNoNumberToken.cs | 12 +++ src/ByteCodeDecompiler.cs | 50 +++++++++-- src/Core/Tokens/FunctionTokens.cs | 84 +++++++------------ src/Core/Tokens/JumpTokens.cs | 2 +- src/Core/Tokens/Token.cs | 73 +++++++++++----- src/Eliot.UELib.csproj | 4 +- 7 files changed, 149 insertions(+), 85 deletions(-) create mode 100644 src/Branch/UE3/RSS/Tokens/Bm4ContextToken.cs create mode 100644 src/Branch/UE3/RSS/Tokens/NameConstNoNumberToken.cs 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 c0b8c7d3..9e18dc27 100644 --- a/src/ByteCodeDecompiler.cs +++ b/src/ByteCodeDecompiler.cs @@ -13,6 +13,7 @@ namespace UELib.Core { using System.Linq; using System.Text; + using UELib.Branch.UE3.RSS.Tokens; public partial class UStruct { @@ -307,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 { @@ -367,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) @@ -1119,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; } @@ -1306,11 +1344,11 @@ private Token DeserializeCastToken(byte castToken) return token; } - #endregion +#endregion #if DECOMPILE - #region Decompile +#region Decompile public class NestManager { @@ -1935,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() @@ -1945,7 +1983,7 @@ public string Disassemble() return string.Empty; } - #endregion +#endregion #endif } diff --git a/src/Core/Tokens/FunctionTokens.cs b/src/Core/Tokens/FunctionTokens.cs index 619d27ec..a5e904c9 100644 --- a/src/Core/Tokens/FunctionTokens.cs +++ b/src/Core/Tokens/FunctionTokens.cs @@ -35,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)) @@ -69,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) @@ -116,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; @@ -313,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 c179df95..27de6962 100644 --- a/src/Core/Tokens/JumpTokens.cs +++ b/src/Core/Tokens/JumpTokens.cs @@ -609,7 +609,7 @@ public override string Decompile() { output = $"foreach {DecompileNext()}({DecompileNext()})"; // Skip Index param - GrabNextToken(); + NextToken(); } Decompiler._CanAddSemicolon = false; diff --git a/src/Core/Tokens/Token.cs b/src/Core/Tokens/Token.cs index 462db95c..8c45397f 100644 --- a/src/Core/Tokens/Token.cs +++ b/src/Core/Tokens/Token.cs @@ -52,28 +52,52 @@ 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() @@ -91,27 +115,34 @@ protected Token DeserializeNext() [MethodImpl(MethodImplOptions.AggressiveInlining)] protected UName ReadName(IUnrealStream stream) { - UName name; #if BATMAN // (Only for byte-codes) No int32 numeric followed after a name index for Batman4 if (stream.Package.Build == UnrealPackage.GameBuild.BuildName.Batman4) { - Decompiler.AlignSize(sizeof(int)); - int nameIndex = stream.ReadInt32(); - name = new UName(stream.Package.Names[nameIndex]); - return name; + return ReadNameNoNumber(stream); } #endif + var name = stream.ReadNameReference(); Decompiler.AlignNameSize(); - name = stream.ReadNameReference(); return name; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected T ReadObject(IUnrealStream stream) where T : UObject + 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 stream.ReadObject(); + return obj; } public override string ToString() 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 @@ - +