diff --git a/CHANGELOG.md b/CHANGELOG.md index d9c14428..2e9d6259 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # +## [1.7.0](https://github.com/EliotVU/Unreal-Library/releases/tag/1.7.0) + +* 0ff0ed96 Added .NET Standard 2.1 and .NET 8.0 as framework targets. +* 48b427f1 Support for Mass Effect: Legendary Edition +* 83c7ecfe Support for Men of Valor +* a226a6c7 Support for Tom Clancy's Splinter Cell: Double Agent (Offline Mode) +* 660c0c28 Support for Stargate SG-1: The Alliance +* 1dd24dc5 Improved support for Frontlines: Fuel of War + ## [1.6.1](https://github.com/EliotVU/Unreal-Library/releases/tag/1.6.1) * Added .NET Standard 2.0 as a target framework, when targeting this standard any code releated to WinForms will be excluded (This will be deprecated in its entirety with UELib 2.0) diff --git a/README.md b/README.md index 8a4853ce..eebce869 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ -[![Nuget](https://img.shields.io/nuget/dt/Eliot.UELib?style=for-the-badge)](https://www.nuget.org/packages/Eliot.UELib/) [![Nuget](https://img.shields.io/nuget/v/Eliot.UELib?style=for-the-badge)](https://www.nuget.org/packages/Eliot.UELib/) +![Platform](https://img.shields.io/badge/platform-.NET-blue.svg?style=for-the-badge) +![License](https://img.shields.io/badge/license-MIT-blue.svg?style=for-the-badge) # UELib @@ -20,19 +21,15 @@ Additionally UELib is also capable of deserializing of many more data classes su UTextureCube, UTextureFlipBook, UTextureMovie UPrimitive, UPolys -## How to use - -To use this library you will need [.NET Framework 4.8](https://dotnet.microsoft.com/en-us/download/dotnet-framework/net48) (The library will move to .NET 6 or higher at version 2.0) - -Install using either: - -* Package Manager: +## Install ```cmd Install-Package Eliot.UELib ``` -* NuGet: +| Module | Version | License | +|---|---|---| +| | [![Nuget](https://img.shields.io/nuget/v/Eliot.UELib?label=Version&logo=nuget&style=for-the-badge)](https://www.nuget.org/packages/Eliot.UELib/) | ![GitHub](https://img.shields.io/github/license/EliotVU/Unreal-Library.svg?style=for-the-badge) | * Usage: See the [documentation](https://github.com/EliotVU/Unreal-Library/wiki/Usage) for more examples. @@ -106,6 +103,7 @@ This is a table of games that are confirmed to be compatible with the current st | XIII | 829 | 100/058 | | | Postal 2: Paradise Lost | 1417 | 118/002 | | | Tom Clancy's Splinter Cell | 829 | 100/017 | | +| Tom Clancy's Splinter Cell: Double Agent | 829 | 100/167:SCX | (PC) Offline mode version | | Tom Clancy's Rainbow Six 3: Raven Shield | 600-927 | 118/012-014 | | | Unreal Tournament 2003 | 1077-2225 | 119/025 | | | Devastation | 600-? | 118-120/004-008 | | @@ -124,8 +122,10 @@ This is a table of games that are confirmed to be compatible with the current st | Shark Tale | 2226 | 129/003 | | | Lemony Snicket's A Series of Unfortunate Events | 2226 | 129/003 | | | Swat 4 | 2226:Vengeance | 129/027 | | +| Stargate SG1: The Alliance | 4180 | 130/004 | | | Tribes: Vengeance | 2226:Vengeance | 130/027 | | | Bioshock | 2226:Vengeance | 130-141/056 | | +| Men of Valor | 926 | 137/000 | PC | | Bioshock 2 | 2226:Vengeance | 143/059 | | | Unreal Championship 2: Liandri Conflict | 3323 | 151/002 | [Third-party](https://forums.beyondunreal.com/threads/unreal-championship-2-script-decompiler-release.206036/) | | The Chronicles of Spellborn | 3323 | 159/029 | | @@ -134,12 +134,16 @@ This is a table of games that are confirmed to be compatible with the current st | | | | | | Tom Clancy's EndWar | Unknown | 329/000 | | | Roboblitz | 2306 | 369/006 | | +| Mass Effect (Xbox 360) | 2674 | 391/092 | Xenon | | Medal of Honor: Airborne | 2859 | 421/011 | | -| Frontlines: Fuel of War | 2917 | 433/052 | Poor output of functions | +| Frontlines: Fuel of War | 2917 | 433/052 | | | Army of Two | 3004 | 445/079 | Overall quality has not been verified | | Mortal Kombat Komplete Edition | 2605 | 472/046 | | | Stargate Worlds | 3004 | 486/007 | | | Gears of War | 3329 | 490/009 | | +| Mass Effect (Windows) | 3240 | 491/1008(~112) | PC | +| Robert Ludlum's The Bourne Conspiracy | | 511/039 | | +| Transformers: War for Cybertron | | 511/144,145 | PC, PS3, Xenon | | Unreal Tournament 3 | 3809 | 512/000 | | | Mirrors Edge | 3716 | 536/043 | | | Alpha Protocol | 3857 | 539/091 | | @@ -159,6 +163,8 @@ This is a table of games that are confirmed to be compatible with the current st | Blacklight: Tango Down | 6165 | 673/002 | | | Dungeons & Dragons: Daggerdale | 6165 | 674/000 | | | Dungeon Defenders | 6262 | 678/002 | | +| Mass Effect 3 (Windows) | 6383 | 684/194 | PC (platform must be set to `Console`) | +| Mass Effect: Legendary Edition (Windows) | 6383 | 684/171 | PC (platform must be set to `Console`) | | Alice Madness Returns | 6760 | 690/000 | | | The Ball | 6699 | 706/000 | | | Bioshock Infinite | 6829 | 727/075 | | @@ -191,6 +197,8 @@ This is a table of games that are confirmed to be compatible with the current st | DmC: Devil May Cry | 8916 | 845/004 | | | XCOM: Enemy Unknown | 8916 | 845/059 | | | Gears of War: Judgement | 10566 | 846/000 | | +| Transformers: Fall of Cybertron | 846(~587)/181 | | +| Deadpool | 846(~587)/181 | | | InMomentum | 8980 | 848/000 | | | [Unmechanical](http://unmechanical.net/) | 9249 | 852/000 | | | Deadlight | 9375 | 854/000 | | diff --git a/src/Branch/PackageObjectLegacyVersion.cs b/src/Branch/PackageObjectLegacyVersion.cs index 4e9f029d..5d13e128 100644 --- a/src/Branch/PackageObjectLegacyVersion.cs +++ b/src/Branch/PackageObjectLegacyVersion.cs @@ -1,23 +1,24 @@ -namespace UELib.Branch +using System.Runtime.CompilerServices; + +namespace UELib.Branch { public enum PackageObjectLegacyVersion { Undefined = 0, /// - /// FIXME: Version 61 is the lowest package version I know that supports StateFlags. + /// FIXME: Version 61 is the lowest package version I know that supports StateFlags. /// AddedStateFlagsToUState = 61, /// - /// This should mark the first approximated version with dynamic arrays that are accessible using UnrealScript. - /// - /// FIXME: Version, generally not accessible in Unreal Engine 1 except for some, so we'll map the tokens for v62. + /// This should mark the first approximated version with dynamic arrays that are accessible using UnrealScript. + /// FIXME: Version, generally not accessible in Unreal Engine 1 except for some, so we'll map the tokens for v62. /// DynamicArrayTokensAdded = 62, /// - /// Mixed changes. + /// Mixed changes. /// Release62 = 62, ReturnExpressionAddedToReturnToken = 62, @@ -25,14 +26,14 @@ public enum PackageObjectLegacyVersion LazyArraySkipCountChangedToSkipOffset = 62, /// - /// Mixed changes. + /// Mixed changes. /// Release64 = 64, CharRemapAddedToUFont = 69, /// - /// FIXME: Unknown version. + /// FIXME: Unknown version. /// CastStringSizeTokenDeprecated = 70, @@ -44,7 +45,7 @@ public enum PackageObjectLegacyVersion DynamicArrayInsertTokenAdded = 95, /// - /// FIXME: Version, set 95 (Deus Ex: IW) + /// FIXME: Version, set 95 (Deus Ex: IW) /// PrimitiveCastTokenAdded = 95, @@ -67,9 +68,8 @@ public enum PackageObjectLegacyVersion CompactIndexDeprecated = 178, /// - /// Present in all released UE3 games (starting with RoboBlitz). - /// - /// FIXME: Unknown version. + /// Present in all released UE3 games (starting with RoboBlitz). + /// FIXME: Unknown version. /// IsLocalAddedToDelegateFunctionToken = 181, @@ -109,7 +109,7 @@ public enum PackageObjectLegacyVersion AddedFuncMapToUState = 220, /// - /// And ComponentMap + /// And ComponentMap /// ArchetypeAddedToExports = 220, @@ -144,7 +144,7 @@ public enum PackageObjectLegacyVersion InterfaceClassesDeprecated = 288, /// - /// Some properties like SizeX, SizeY, Format have been displaced to ScriptProperties. + /// Some properties like SizeX, SizeY, Format have been displaced to ScriptProperties. /// DisplacedUTextureProperties = 297, @@ -159,21 +159,52 @@ public enum PackageObjectLegacyVersion NetObjectCountAdded = 322, + CompressionAdded = 334, + NumberAddedToName = 343, // FIXME: Version 374-491; Delegate source type changed from Name to Object ChangedDelegateSourceFromNameToObject = 376, + [Discardable] GameGOW = 374, + + /// + /// Not attested in (GoW v374, oldest attest (v421) + /// FIXME: Version + /// + SkipSizeAddedToArrayFindTokenIntrinsics = GameGOW + 1, + + /// + /// Not attested in GoW (v374), oldest attest (v421) + /// FIXME: Unknown version + /// + StructReferenceAddedToStructMember = GameGOW + 1, + // 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, + /// + /// Oldest attest MOHA (v421), but not MKKE (v472, non standard) + /// FIXME: Unknown version + /// + IsCopyAddedToStructMember = GameGOW + 1, + + [Discardable] GameFFOW = 433, + + /// + /// Oldest attest FFOW (v433), but not MKKE (v472, non standard) + /// FIXME: Unknown version + /// + IsModificationAddedToStructMember = GameFFOW, + + [Discardable] GameGOWPC = 490, + + /// + /// FIXME: Version, not attested in (GoW v490) + /// + SkipSizeAddedToArrayTokenIntrinsics = GameGOWPC + 1, VerticalOffsetAddedToUFont = 506, CleanupFonts = 511, @@ -181,7 +212,7 @@ public enum PackageObjectLegacyVersion ComponentMapDeprecated = 543, /// - /// Added with + /// Added with /// ClassPlatformFlagsDeprecated = 547, @@ -221,6 +252,6 @@ public enum PackageObjectLegacyVersion AddedNativeClassNameToUClass = 813, AddedATITCToUTexture2D = 857, - AddedETCToUTexture2D = 864, + AddedETCToUTexture2D = 864 } } diff --git a/src/Branch/UE2/ShadowStrike/EngineBranch.ShadowStrike.cs b/src/Branch/UE2/ShadowStrike/EngineBranch.ShadowStrike.cs new file mode 100644 index 00000000..32e35c1c --- /dev/null +++ b/src/Branch/UE2/ShadowStrike/EngineBranch.ShadowStrike.cs @@ -0,0 +1,25 @@ +using UELib.Core; +using UELib.Core.Tokens; + +namespace UELib.Branch.UE2.ShadowStrike +{ + public class EngineBranchShadowStrike : DefaultEngineBranch + { + public EngineBranchShadowStrike(BuildGeneration generation) : base(BuildGeneration.UE2) + { + } + + protected override TokenMap BuildTokenMap(UnrealPackage linker) + { + var tokenMap = base.BuildTokenMap(linker); + + if (linker.Build == UnrealPackage.GameBuild.BuildName.SC_DA_Online) + { + // TODO: All tokens + tokenMap[0x28] = typeof(UStruct.UByteCodeDecompiler.NativeParameterToken); + } + + return tokenMap; + } + } +} diff --git a/src/Branch/UE3/SFX/Classes/UBioMask4Property.cs b/src/Branch/UE3/SFX/Classes/UBioMask4Property.cs new file mode 100644 index 00000000..15f0e2f6 --- /dev/null +++ b/src/Branch/UE3/SFX/Classes/UBioMask4Property.cs @@ -0,0 +1,15 @@ +using UELib.Core; + +namespace UELib.Branch.UE3.SFX.Classes +{ + [UnrealRegisterClass] + [BuildGeneration(BuildGeneration.SFX)] + public class UBioMask4Property : UProperty + { + /// + public override string GetFriendlyType() + { + return "mask4"; + } + } +} diff --git a/src/Branch/UE3/SFX/Classes/UStringRefProperty.cs b/src/Branch/UE3/SFX/Classes/UStringRefProperty.cs new file mode 100644 index 00000000..80ed7e4e --- /dev/null +++ b/src/Branch/UE3/SFX/Classes/UStringRefProperty.cs @@ -0,0 +1,15 @@ +using UELib.Core; + +namespace UELib.Branch.UE3.SFX.Classes +{ + [UnrealRegisterClass] + [BuildGeneration(BuildGeneration.SFX)] + public class UStringRefProperty : UProperty + { + /// + public override string GetFriendlyType() + { + return "strref"; + } + } +} diff --git a/src/Branch/UE3/SFX/EngineBranch.SFX.cs b/src/Branch/UE3/SFX/EngineBranch.SFX.cs new file mode 100644 index 00000000..d182a359 --- /dev/null +++ b/src/Branch/UE3/SFX/EngineBranch.SFX.cs @@ -0,0 +1,94 @@ +using UELib.Branch.UE3.BL2.Tokens; +using UELib.Branch.UE3.SFX.Tokens; +using UELib.Core; +using UELib.Core.Tokens; +using static UELib.Core.UStruct.UByteCodeDecompiler; + +namespace UELib.Branch.UE3.SFX +{ + public class EngineBranchSFX : DefaultEngineBranch + { + public EngineBranchSFX(BuildGeneration generation) : base(generation) + { + } + + public override void Setup(UnrealPackage linker) + { + base.Setup(linker); + + // FIXME: Temporary workaround + if (linker.LicenseeVersion == 1008) + { + linker.Summary.LicenseeVersion = 112; + } + } + + protected override void SetupSerializer(UnrealPackage linker) + { + SetupSerializer(); + } + + protected override TokenMap BuildTokenMap(UnrealPackage linker) + { + var tokenMap = base.BuildTokenMap(linker); + + // Xenon + if (linker.Build == UnrealPackage.GameBuild.BuildName.ME1 && + linker.Version == 391 && linker.LicenseeVersion == 92) + { + tokenMap[0x4A] = typeof(StringRefConstToken); + tokenMap[0x4B] = typeof(DynamicArrayAddToken); + } + + tokenMap[0x4F] = typeof(StringRefConstToken); + + if (linker.Build == UnrealPackage.GameBuild.BuildName.ME1) + { + return tokenMap; + } + + tokenMap[0x5B] = typeof(LocalVariableToken); + tokenMap[0x5C] = typeof(LocalVariableToken); + tokenMap[0x5D] = typeof(LocalVariableToken); + tokenMap[0x5E] = typeof(LocalVariableToken); + tokenMap[0x5F] = typeof(InstanceVariableToken); + tokenMap[0x60] = typeof(InstanceVariableToken); + tokenMap[0x61] = typeof(InstanceVariableToken); + tokenMap[0x62] = typeof(InstanceVariableToken); + tokenMap[0x63] = typeof(JumpIfNotVariableToken); // IfLocal + tokenMap[0x64] = typeof(JumpIfNotVariableToken); // IfInstance + tokenMap[0x65] = typeof(NamedFunctionToken); + + tokenMap[0x66] = typeof(BadToken); + tokenMap[0x67] = typeof(BadToken); + tokenMap[0x68] = typeof(BadToken); + tokenMap[0x69] = typeof(BadToken); + tokenMap[0x6A] = typeof(BadToken); + tokenMap[0x6B] = typeof(BadToken); + tokenMap[0x6C] = typeof(BadToken); + tokenMap[0x6D] = typeof(BadToken); + tokenMap[0x6E] = typeof(BadToken); + tokenMap[0x6F] = typeof(BadToken); + + return tokenMap; + } + + protected override void SetupTokenFactory(UnrealPackage linker) + { + if (linker.Build == UnrealPackage.GameBuild.BuildName.ME1) + { + base.SetupTokenFactory(linker); + + return; + } + + var tokenMap = BuildTokenMap(linker); + + SetupTokenFactory( + tokenMap, + TokenFactory.FromPackage(linker.NTLPackage), + 0x70, + 0x80); + } + } +} diff --git a/src/Branch/UE3/SFX/PackageSerializer.SFX.cs b/src/Branch/UE3/SFX/PackageSerializer.SFX.cs new file mode 100644 index 00000000..48ab99a6 --- /dev/null +++ b/src/Branch/UE3/SFX/PackageSerializer.SFX.cs @@ -0,0 +1,63 @@ +using UELib.Annotations; + +namespace UELib.Branch.UE3.SFX +{ + [UsedImplicitly] + public class PackageSerializerSFX : PackageSerializerBase + { + public override void Serialize(IUnrealStream stream, UNameTableItem item) + { + stream.Write(item._Name); + if (stream.LicenseeVersion >= 142 && stream.LicenseeVersion != 1008) + { + return; + } + + if (stream.LicenseeVersion >= 102) + { + stream.Write((uint)item._Flags); + return; + } + + stream.Write(item._Flags); + } + + // FIXME: Fails on Mass Effect 2 (513, 0130) + public override void Deserialize(IUnrealStream stream, UNameTableItem item) + { + stream.Read(out item._Name); + if (stream.LicenseeVersion >= 142 && stream.LicenseeVersion != 1008) + { + return; + } + + if (stream.LicenseeVersion >= 102) + { + stream.Read(out item._Flags); + return; + } + + stream.Read(out item._Flags); + } + + public override void Serialize(IUnrealStream stream, UImportTableItem item) + { + item.Serialize(stream); + } + + public override void Deserialize(IUnrealStream stream, UImportTableItem item) + { + item.Deserialize(stream); + } + + public override void Serialize(IUnrealStream stream, UExportTableItem item) + { + item.Serialize(stream); + } + + public override void Deserialize(IUnrealStream stream, UExportTableItem item) + { + item.Deserialize(stream); + } + } +} diff --git a/src/Branch/UE3/SFX/Tokens/JumpIfNotVariableToken.cs b/src/Branch/UE3/SFX/Tokens/JumpIfNotVariableToken.cs new file mode 100644 index 00000000..03d4a005 --- /dev/null +++ b/src/Branch/UE3/SFX/Tokens/JumpIfNotVariableToken.cs @@ -0,0 +1,39 @@ +using UELib.Core; +using UELib.ObjectModel.Annotations; +using UELib.Tokens; + +namespace UELib.Branch.UE3.SFX.Tokens +{ + [ExprToken(ExprToken.JumpIfNot)] + public class JumpIfNotVariableToken : UStruct.UByteCodeDecompiler.JumpIfNotToken + { + public UObject Variable; + public byte Negation; + + public override void Deserialize(IUnrealStream stream) + { + stream.Read(out Variable); + Decompiler.AlignObjectSize(); + + stream.Read(out Negation); + Decompiler.AlignSize(sizeof(byte)); + + stream.Read(out CodeOffset); + Decompiler.AlignSize(sizeof(ushort)); + } + + public override string Decompile() + { + Decompiler.Nester.AddNest( + UStruct.UByteCodeDecompiler.NestManager.Nest.NestType.If, + Position, CodeOffset, + this + ); + + bool isNegated = Negation != 0; + return isNegated + ? $"if(!{Variable.Name})" + : $"if({Variable.Name})"; + } + } +} diff --git a/src/Branch/UE3/SFX/Tokens/NamedFunctionToken.cs b/src/Branch/UE3/SFX/Tokens/NamedFunctionToken.cs new file mode 100644 index 00000000..5a124b1c --- /dev/null +++ b/src/Branch/UE3/SFX/Tokens/NamedFunctionToken.cs @@ -0,0 +1,17 @@ +using UELib.Core; +using UELib.ObjectModel.Annotations; +using UELib.Tokens; + +namespace UELib.Branch.UE3.SFX.Tokens +{ + [ExprToken(ExprToken.VirtualFunction)] + public class NamedFunctionToken : UStruct.UByteCodeDecompiler.VirtualFunctionToken + { + public override string Decompile() + { + Decompiler.MarkSemicolon(); + + return DecompileCall($"'{FunctionName}'"); + } + } +} diff --git a/src/Branch/UE3/SFX/Tokens/StringRefConstToken.cs b/src/Branch/UE3/SFX/Tokens/StringRefConstToken.cs new file mode 100644 index 00000000..71732e3d --- /dev/null +++ b/src/Branch/UE3/SFX/Tokens/StringRefConstToken.cs @@ -0,0 +1,27 @@ +using UELib.Core; +using UELib.ObjectModel.Annotations; +using UELib.Tokens; + +namespace UELib.Branch.UE3.SFX.Tokens +{ + [ExprToken(ExprToken.StringConst)] + public class StringRefConstToken : UStruct.UByteCodeDecompiler.Token + { + /// + /// Index to the string in a Tlk file. + /// + public int Index; + + public override void Deserialize(IUnrealStream stream) + { + stream.Read(out Index); + Decompiler.AlignSize(sizeof(int)); + } + + public override string Decompile() + { + // FIXME: pseudo syntax + return $"strref[{Index}]"; + } + } +} diff --git a/src/ByteCodeDecompiler.cs b/src/ByteCodeDecompiler.cs index a511d553..07826e3b 100644 --- a/src/ByteCodeDecompiler.cs +++ b/src/ByteCodeDecompiler.cs @@ -337,6 +337,7 @@ public bool TryAddNestEnd(Nest.NestType type, int pos) } private NestManager _Nester; + public NestManager Nester => _Nester; // Checks if we're currently within a nest of type nestType in any stack! private NestManager.Nest IsWithinNest(NestManager.Nest.NestType nestType) diff --git a/src/Core/Classes/Props/UProperty.cs b/src/Core/Classes/Props/UProperty.cs index a3bfe0a5..f3bda59f 100644 --- a/src/Core/Classes/Props/UProperty.cs +++ b/src/Core/Classes/Props/UProperty.cs @@ -97,12 +97,15 @@ protected override void Deserialize() Record(nameof(aa2FixedPack), aa2FixedPack); } #endif -#if XIII || DNF +#if XIII || DNF || MOV + // TODO: (UE2X) Version 131 ArrayDim size changed from DWORD to WORD if (Package.Build == UnrealPackage.GameBuild.BuildName.XIII || - Package.Build == UnrealPackage.GameBuild.BuildName.DNF) + Package.Build == UnrealPackage.GameBuild.BuildName.DNF || + Package.Build == UnrealPackage.GameBuild.BuildName.MOV) { ArrayDim = _Buffer.ReadInt16(); Record(nameof(ArrayDim), ArrayDim); + goto skipArrayDim; } #endif @@ -152,8 +155,14 @@ protected override void Deserialize() Record(nameof(deusFlags), deusFlags); } #endif - if (!Package.IsConsoleCooked()) + if (!Package.IsConsoleCooked() +#if MASS_EFFECT + // M1:LE is cooked for "WindowsConsole" yet retains this data. + || Package.Build == BuildGeneration.SFX +#endif + ) { + // TODO: Not serialized if XENON (UE2X) // FIXME: UE4 version if (_Buffer.UE4Version < 160) { @@ -192,6 +201,7 @@ protected override void Deserialize() { RepNotifyFuncName = _Buffer.ReadNameReference(); Record(nameof(RepNotifyFuncName), RepNotifyFuncName); + return; } #endif @@ -250,13 +260,21 @@ protected override void Deserialize() (Package.Build == BuildGeneration.UE2_5 || Package.Build == BuildGeneration.AGP || Package.Build == BuildGeneration.Flesh)) - // No property flag + // No property flag check +#if VENGEANCE || Package.Build == BuildGeneration.Vengeance +#endif +#if MOV + // No property flag check + || Package.Build == UnrealPackage.GameBuild.BuildName.MOV +#endif #if LSGAME + // No property flag check || (Package.Build == UnrealPackage.GameBuild.BuildName.LSGame && Package.LicenseeVersion >= 3) #endif #if DEVASTATION + // No property flag check || Package.Build == UnrealPackage.GameBuild.BuildName.Devastation #endif ) diff --git a/src/Core/Classes/Props/UPropertyDecompiler.cs b/src/Core/Classes/Props/UPropertyDecompiler.cs index df96fd11..4b0af79f 100644 --- a/src/Core/Classes/Props/UPropertyDecompiler.cs +++ b/src/Core/Classes/Props/UPropertyDecompiler.cs @@ -370,7 +370,7 @@ public string FormatFlags() } // Properties flagged with automated, automatically get those flags added by the compiler. - if (Package.Version == 128 && (PropertyFlags & (ulong)Flags.PropertyFlagsLO.Automated) != 0) + if (Package.Build == BuildGeneration.UE2_5 && (PropertyFlags & (ulong)Flags.PropertyFlagsLO.Automated) != 0) { output += "automated "; copyFlags &= ~((ulong)Flags.PropertyFlagsLO.Automated @@ -494,8 +494,8 @@ public string FormatFlags() copyFlags &= ~(ulong)Flags.PropertyFlagsLO.Localized; } - // FIXME: Is this exclusively UT2004? - if (Package.Version == 128) + // Assuming UE2.5 (introduced with UT2004 but is also used in SG1) + if (Package.Build == BuildGeneration.UE2_5) { if (HasPropertyFlag(Flags.PropertyFlagsLO.Cache)) { diff --git a/src/Core/Classes/UClass.cs b/src/Core/Classes/UClass.cs index 8d75083f..5c995a9a 100644 --- a/src/Core/Classes/UClass.cs +++ b/src/Core/Classes/UClass.cs @@ -520,6 +520,22 @@ protected override void Deserialize() DLLBindName = _Buffer.ReadNameReference(); Record(nameof(DLLBindName), DLLBindName); } +#if MASS_EFFECT + if (Package.Build == BuildGeneration.SFX) + { + if (_Buffer.LicenseeVersion - 138u < 15) + { + _Buffer.Read(out int v40); + Record(nameof(v40), v40); + } + + if (_Buffer.LicenseeVersion >= 139) + { + _Buffer.Read(out int v1ec); + Record(nameof(v1ec), v1ec); + } + } +#endif #if REMEMBERME if (Package.Build == UnrealPackage.GameBuild.BuildName.RememberMe) { diff --git a/src/Core/Classes/UDefaultProperty.cs b/src/Core/Classes/UDefaultProperty.cs index 90fe7353..777e0c6c 100644 --- a/src/Core/Classes/UDefaultProperty.cs +++ b/src/Core/Classes/UDefaultProperty.cs @@ -4,6 +4,7 @@ using System.Diagnostics.Contracts; using System.IO; using System.Linq; +using System.Reflection; using System.Runtime.InteropServices; using UELib.Annotations; using UELib.Branch; @@ -648,6 +649,25 @@ private string DeserializeDefaultPropertyValue(PropertyType type, ref Deserializ propertyValue = $"JsonRef<{jsonObject.GetClassName()}>'{jsonObjectName}'"; break; } +#endif +#if MASS_EFFECT + case PropertyType.StringRefProperty: + { + _Buffer.Read(out int index); + Record(nameof(index), index); + + propertyValue = PropertyDisplay.FormatLiteral(index); + break; + } + + case PropertyType.BioMask4Property: + { + _Buffer.Read(out byte value); + Record(nameof(value), value); + + propertyValue = PropertyDisplay.FormatLiteral(value); + break; + } #endif case PropertyType.IntProperty: { diff --git a/src/Core/Classes/UFunction.cs b/src/Core/Classes/UFunction.cs index 98d727d5..77865f6a 100644 --- a/src/Core/Classes/UFunction.cs +++ b/src/Core/Classes/UFunction.cs @@ -143,6 +143,10 @@ protected override void Deserialize() #if MKKE // Cooked and stripped, but FriendlyName still remains || Package.Build == UnrealPackage.GameBuild.BuildName.MKKE +#endif +#if MASS_EFFECT + // Cooked and stripped, but FriendlyName still remains + || Package.Build == BuildGeneration.SFX #endif ) { diff --git a/src/Core/Classes/UStruct.cs b/src/Core/Classes/UStruct.cs index 753e402e..8d39fd27 100644 --- a/src/Core/Classes/UStruct.cs +++ b/src/Core/Classes/UStruct.cs @@ -169,7 +169,12 @@ protected override void Deserialize() // UT2004 reports version 26, and BioShock version 2 if ((Package.Build == BuildGeneration.UE2_5 && _Buffer.LicenseeVersion >= 26) || (Package.Build == BuildGeneration.AGP && _Buffer.LicenseeVersion >= 17) || - (Package.Build == BuildGeneration.Vengeance && _Buffer.LicenseeVersion >= 2)) + (Package.Build == BuildGeneration.Vengeance && _Buffer.LicenseeVersion >= 2) +#if SG1 + // Same offset and version check as CppText (120) probably an incorrectly back-ported feature. + || (Package.Build == UnrealPackage.GameBuild.BuildName.SG1_TA && _Buffer.Version >= 120) +#endif + ) { StructFlags = _Buffer.ReadUInt32(); Record(nameof(StructFlags), (StructFlags)StructFlags); diff --git a/src/Core/Tables/UExportTableItem.cs b/src/Core/Tables/UExportTableItem.cs index 2a383d9a..7370deeb 100644 --- a/src/Core/Tables/UExportTableItem.cs +++ b/src/Core/Tables/UExportTableItem.cs @@ -315,7 +315,7 @@ public override string GetReferencePath() public override string ToString() { - return $"{ObjectName}({Index}{1})"; + return $"{ObjectName}({Index + 1})"; } [Obsolete("Use ToString()")] @@ -326,7 +326,7 @@ public string ToString(bool v) public static explicit operator int(UExportTableItem item) { - return item.Index; + return (item.Index + 1); } } } diff --git a/src/Core/Tables/UImportTableItem.cs b/src/Core/Tables/UImportTableItem.cs index 5d09a02a..3127f48f 100644 --- a/src/Core/Tables/UImportTableItem.cs +++ b/src/Core/Tables/UImportTableItem.cs @@ -65,7 +65,7 @@ public string ToString(bool v) public static explicit operator int(UImportTableItem item) { - return -item.Index; + return -(item.Index + 1); } } } diff --git a/src/Core/Tables/UNameTableItem.cs b/src/Core/Tables/UNameTableItem.cs index 12ca7009..24e671c3 100644 --- a/src/Core/Tables/UNameTableItem.cs +++ b/src/Core/Tables/UNameTableItem.cs @@ -16,14 +16,14 @@ public string Name get => _Name; set => _Name = value; } - private string _Name; + internal string _Name; public ulong Flags { get => _Flags; set => _Flags = value; } - private ulong _Flags; + internal ulong _Flags; public ushort NonCasePreservingHash; public ushort CasePreservingHash; diff --git a/src/Core/Tokens/CastTokens.cs b/src/Core/Tokens/CastTokens.cs index 214f4613..3686b068 100644 --- a/src/Core/Tokens/CastTokens.cs +++ b/src/Core/Tokens/CastTokens.cs @@ -1,7 +1,6 @@ using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; -using System.Runtime.CompilerServices; using UELib.Branch; using UELib.ObjectModel.Annotations; using UELib.Tokens; @@ -75,7 +74,7 @@ protected virtual void DeserializeCastToken(IUnrealStream stream) private void RemapCastToken(IUnrealArchive stream) { if (stream.Version >= (uint)PackageObjectLegacyVersion.AddedInterfacesFeature) return; - + // TODO: Could there be more? switch (CastOpCode) { @@ -84,7 +83,7 @@ private void RemapCastToken(IUnrealArchive stream) break; } } - + public override void Deserialize(IUnrealStream stream) { DeserializeCastToken(stream); @@ -121,8 +120,31 @@ public override string Decompile() break; } } +#endif +#if MASS_EFFECT + if (Package.Build == BuildGeneration.SFX) + { + switch ((uint)CastOpCode) + { + // StringRefToInt + case 0x5B: + castTypeName = "int"; + break; + + // StringRefToString + case 0x5C: + castTypeName = "string"; + break; + + // IntToStringRef + case 0x5D: + castTypeName = "strref"; + break; + } + } #endif } + Debug.Assert(castTypeName != default, $"Detected an unresolved token '0x{CastOpCode:X}'."); return $"{castTypeName}({DecompileNext()})"; } diff --git a/src/Core/Tokens/ContextTokens.cs b/src/Core/Tokens/ContextTokens.cs index 89cf0d42..ea551571 100644 --- a/src/Core/Tokens/ContextTokens.cs +++ b/src/Core/Tokens/ContextTokens.cs @@ -1,5 +1,5 @@ using System.Diagnostics; -using UELib.Annotations; +using UELib.Branch; using UELib.ObjectModel.Annotations; using UELib.Tokens; @@ -14,7 +14,7 @@ public class ContextToken : Token { public UProperty Property; public ushort PropertyType; - + public override void Deserialize(IUnrealStream stream) { // A.? @@ -23,7 +23,7 @@ public override void Deserialize(IUnrealStream stream) // SkipSize stream.ReadUInt16(); Decompiler.AlignSize(sizeof(ushort)); - + // Doesn't seem to exist in APB // Definitely not in UT3(512), APB, CrimeCraft, GoW2, MoonBase and Singularity. @@ -95,7 +95,14 @@ public override string Decompile() public class StructMemberToken : Token { public UField Property; - [CanBeNull] public UStruct Struct; + + /// + /// Will be null if not deserialized ( < ) + /// + public UStruct Struct; + + public byte IsCopy; + public byte IsModification; public override void Deserialize(IUnrealStream stream) { @@ -110,28 +117,29 @@ public override void Deserialize(IUnrealStream stream) Debug.Assert(Struct != null); } #endif - // TODO: Corrigate version. Definitely didn't exist in Roboblitz(369), first seen in MOHA(421). - if (stream.Version > 374) + if (stream.Version >= (int)PackageObjectLegacyVersion.StructReferenceAddedToStructMember) { Struct = stream.ReadObject(); Decompiler.AlignObjectSize(); Debug.Assert(Struct != null); + } #if MKKE - if (Package.Build == UnrealPackage.GameBuild.BuildName.MKKE) - { - goto skipToNext; - } + if (Package.Build == UnrealPackage.GameBuild.BuildName.MKKE) + { + goto skipToNext; + } #endif + if (stream.Version >= (int)PackageObjectLegacyVersion.IsCopyAddedToStructMember) + { // Copy? - stream.ReadByte(); + IsCopy = stream.ReadByte(); Decompiler.AlignSize(sizeof(byte)); } - // TODO: Corrigate version. Definitely didn't exist in MKKE(472), first seen in FFOW(433). - if (stream.Version >= 433) + if (stream.Version >= (int)PackageObjectLegacyVersion.IsModificationAddedToStructMember) { // Modification? - stream.ReadByte(); + IsModification = stream.ReadByte(); Decompiler.AlignSize(sizeof(byte)); } diff --git a/src/Core/Tokens/JumpTokens.cs b/src/Core/Tokens/JumpTokens.cs index 4691db31..643fb865 100644 --- a/src/Core/Tokens/JumpTokens.cs +++ b/src/Core/Tokens/JumpTokens.cs @@ -84,7 +84,7 @@ public class JumpToken : Token { public bool MarkedAsSwitchBreak; public NestManager.NestEnd LinkedIfNest; - public ushort CodeOffset { get; private set; } + public ushort CodeOffset; public override void Deserialize(IUnrealStream stream) { diff --git a/src/Decompiler/app.config b/src/Decompiler/app.config deleted file mode 100644 index 1696df66..00000000 --- a/src/Decompiler/app.config +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/src/Eliot.UELib.csproj b/src/Eliot.UELib.csproj index 7fb8299f..a42d1896 100644 --- a/src/Eliot.UELib.csproj +++ b/src/Eliot.UELib.csproj @@ -1,7 +1,7 @@ - DECOMPILE;BINARYMETADATA;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;netstandard2.0 + DECOMPILE;BINARYMETADATA;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;SG1;MASS_EFFECT;MOV + net48;netstandard2.0;netstandard2.1;net8.0 Library UELib true @@ -70,7 +70,7 @@ Eliot.UELib $(AssemblyName) - $(VersionPrefix)1.6.2 + $(VersionPrefix)1.7.0 EliotVU $(AssemblyName) UnrealScript decompiler library for Unreal package files (.upk, .u, .uasset; etc), with support for Unreal Engine 1, 2, and 3. diff --git a/src/Eliot.UELib.csproj.DotSettings b/src/Eliot.UELib.csproj.DotSettings index 93d1555d..d4fa2e26 100644 --- a/src/Eliot.UELib.csproj.DotSettings +++ b/src/Eliot.UELib.csproj.DotSettings @@ -1,4 +1,5 @@  + Latest True True True diff --git a/src/Eliot.UELib.sln.DotSettings b/src/Eliot.UELib.sln.DotSettings index b22814ce..4dfe6195 100644 --- a/src/Eliot.UELib.sln.DotSettings +++ b/src/Eliot.UELib.sln.DotSettings @@ -1,5 +1,6 @@  True + SFX True True True diff --git a/src/UnrealBuild.cs b/src/UnrealBuild.cs index 387b32a1..9b766045 100644 --- a/src/UnrealBuild.cs +++ b/src/UnrealBuild.cs @@ -18,7 +18,7 @@ public enum BuildGeneration UE1, /// - /// Modified version for Harry Potter's Unreal Engine 1 + /// Modified version for Harry Potter's Unreal Engine 1. /// HP, @@ -30,10 +30,20 @@ public enum BuildGeneration UE2, /// - /// Heavily modified Unreal Engine 2 by Ion Storm for Thief: Deadly Shadows + /// Heavily modified Unreal Engine 2 by Ion Storm for Thief: Deadly Shadows. /// Flesh, + /// + /// Unreal Engine 2 based, offline version for the Splinter Cell series. + /// + SCX, + + /// + /// Unreal Engine 2 based, online version for the Splinter Cell series. + /// + ShadowStrike, + /// /// Unreal Engine 2 with some early UE3 upgrades. /// @@ -50,14 +60,14 @@ public enum BuildGeneration Vengeance, /// - /// Heavily modified Unreal Engine 2.5 for Splinter Cell + /// Heavily modified Unreal Engine 2.5 for Splinter Cell. /// /// Not yet supported. /// Lead, /// - /// Modified Unreal Engine 2 for Xbox e.g. Unreal Championship 2: The Liandri Conflict + /// Modified Unreal Engine 2 for Xbox e.g. Unreal Championship 2: The Liandri Conflict. /// UE2X, @@ -68,17 +78,22 @@ public enum BuildGeneration /// UE3, + /// + /// A modified Unreal Engine 3 for the Mass Effect series. + /// + SFX, + /// /// Rocksteady Studios /// - /// Heavily modified Unreal Engine 3 for the Arkham series + /// Heavily modified Unreal Engine 3 for the Arkham series. /// RSS, /// /// High Moon Studios /// - /// Heavily modified Unreal Engine 3 for Transformers and Deadpool etc + /// Heavily modified Unreal Engine 3 for Transformers and Deadpool etc. /// HMS, @@ -114,8 +129,25 @@ public enum BuildFlags : byte public enum BuildPlatform { + /// + /// Unknown, an auto-detection algorithm will be performed to determine the platform. + /// Undetermined, + + /// + /// The package is optimized for PC. + /// + /// Auto-detected if the package is located within a directory named "CookedPC" + /// PC, + + /// + /// The package is optimized for console. + /// All editor-data is assumed to have been stripped out. + /// This may also even be true for some pc games, such as Mass Effect: LE + /// + /// Auto-detected if the package is located within a directory named "CookedPCConsole" + /// Console } diff --git a/src/UnrealFlags.cs b/src/UnrealFlags.cs index 4c612209..34d696d5 100644 --- a/src/UnrealFlags.cs +++ b/src/UnrealFlags.cs @@ -42,7 +42,12 @@ public bool HasFlags(ulong flags) { return (_Flags & flags) != 0; } - + + public static explicit operator int(UnrealFlags flags) + { + return (int)flags._Flags; + } + public static explicit operator uint(UnrealFlags flags) { return (uint)flags._Flags; diff --git a/src/UnrealPackage.cs b/src/UnrealPackage.cs index 81e43185..a55e5041 100644 --- a/src/UnrealPackage.cs +++ b/src/UnrealPackage.cs @@ -23,7 +23,9 @@ namespace UELib using Core; using Decoding; using Branch.UE2.DVS; - using UELib.Branch.UE3.RL; + using Branch.UE3.RL; + using Branch.UE3.SFX; + using Branch.UE2.ShadowStrike; /// /// Represents the method that will handle the UELib.UnrealPackage.NotifyObjectAdded @@ -306,6 +308,13 @@ public enum BuildName /// [Build(129, 3, BuildGeneration.UE2)] LSGame, + /// + /// Stargate SG-1: The Alliance + /// + /// 130/004 + /// + [Build(130, 4, BuildGeneration.UE2_5)] SG1_TA, + /// /// BioShock 1 & 2 /// @@ -314,6 +323,14 @@ public enum BuildName [Build(130, 143, 56u, 59u, BuildGeneration.Vengeance)] BioShock, + /// + /// Men of Valor + /// + /// 137/000 + /// + [Build(137, 0u, BuildGeneration.UE2_5)] + MOV, + /// /// Duke Nukem Forever /// @@ -332,13 +349,26 @@ public enum BuildName [Build(159, 29u, BuildGeneration.UE2_5)] Spellborn, + [Build(100, 167, BuildGeneration.SCX)] + SC_DA_Offline, + + /// + /// Tom Clancy's Splinter Cell: Double Agent + /// + /// 275/000 + /// Overriden to version 120, so we can pickup the CppText property in UStruct (although this might be a ProcessedText reference) + /// + [Build(275, 0, BuildGeneration.ShadowStrike)] + [BuildEngineBranch(typeof(EngineBranchShadowStrike))] + [OverridePackageVersion(120)] + SC_DA_Online, + /// /// EndWar /// /// 369/006 /// - [Build(329, 0)] - [OverridePackageVersion((uint)PackageObjectLegacyVersion.AddedInterfacesFeature)] + [Build(329, 0)] [OverridePackageVersion((uint)PackageObjectLegacyVersion.AddedInterfacesFeature)] EndWar, /// @@ -353,7 +383,14 @@ public enum BuildName /// /// 421/011 /// - [Build(421, 11)] MOHA, + [Build(421, 11)] MoHA, + + /// + /// Frontlines: Fuel of War + /// + /// 433/052 + /// + [Build(433, 52)] FFoW, /// /// 472/046 @@ -437,7 +474,7 @@ public enum BuildName /// 581/058 /// [Build(581, 58, BuildFlags.ConsoleCooked)] [BuildEngineBranch(typeof(EngineBranchMOH))] - MOH, + MoH, /// /// Borderlands @@ -484,6 +521,34 @@ public enum BuildName /// [Build(648, 6405)] DCUO, + /// + /// Mass Effect: Legendary Edition + /// + /// 684/171 + /// Engine: 6383 + /// Cooker: 65643 + /// + [Build(391, 0092, BuildGeneration.SFX)] // Xenon + [Build(491, 1008, BuildGeneration.SFX)] // PC + [Build(684, 0153, BuildFlags.ConsoleCooked, BuildGeneration.SFX)] // PS3 + [Build(684, 0171, BuildFlags.ConsoleCooked, BuildGeneration.SFX)] // LE + [BuildEngineBranch(typeof(EngineBranchSFX))] + ME1, + + [Build(512, 0130, BuildGeneration.SFX)] // Demo + [Build(513, 0130, BuildGeneration.SFX)] // PC + [Build(684, 0150, BuildFlags.ConsoleCooked, BuildGeneration.SFX)] // PS3 + [Build(684, 0168, BuildGeneration.SFX)] // LE + [BuildEngineBranch(typeof(EngineBranchSFX))] + ME2, + + [Build(684, 0185, BuildGeneration.SFX)] // Demo + [Build(684, 0194, BuildFlags.ConsoleCooked, BuildGeneration.SFX)] // PC + [Build(845, 0194, BuildFlags.ConsoleCooked, BuildGeneration.SFX)] // Wii + [Build(685, 0205, BuildGeneration.SFX)] // LE + [BuildEngineBranch(typeof(EngineBranchSFX))] + ME3, + /// /// Dungeon Defenders 2 /// @@ -884,8 +949,16 @@ public struct PackageFileSummary : IUnrealSerializableClass public int EngineVersion; public int CookerVersion; - [Obsolete] private const int VCompression = 334; public uint CompressionFlags; + + /// + /// A list of compressed chunks in the package. + /// The package should be considered compressed if any. + /// + /// If equals 0 then the list will be cleared on + /// + /// Will be null if not deserialized ( < ) + /// public UArray CompressedChunks; [Obsolete] private const int VPackageSource = 482; @@ -921,12 +994,53 @@ private void SetupBuild(UnrealPackage package) if (package.Build == null) { package.Build = new GameBuild(package); + if (package.Build.Flags.HasFlag(BuildFlags.ConsoleCooked)) { package.CookerPlatform = BuildPlatform.Console; } } + if (package.CookerPlatform == BuildPlatform.Undetermined) + { + if (string.Compare( + package.PackageDirectory, + "CookedPC", + StringComparison.OrdinalIgnoreCase) == 0) + { + package.CookerPlatform = BuildPlatform.PC; + } + // file may also end in .pcc + else if (string.Compare( + package.PackageDirectory, + "CookedPCConsole", + StringComparison.OrdinalIgnoreCase + ) == 0) + { + package.CookerPlatform = BuildPlatform.Console; + } + else if (string.Compare( + package.PackageDirectory, + "CookedPCServer", + StringComparison.OrdinalIgnoreCase + ) == 0) + { + package.CookerPlatform = BuildPlatform.Console; + } + else if (string.Compare( + package.PackageDirectory, + "CookedXenon", + StringComparison.OrdinalIgnoreCase + ) == 0) + { + package.CookerPlatform = BuildPlatform.Console; + } + else if (Path.GetExtension(package.FullPackageName) == ".xxx") + { + // ... fully compressed + } + } + if (package.Build.OverrideVersion.HasValue) Version = package.Build.OverrideVersion.Value; if (package.Build.OverrideLicenseeVersion.HasValue) LicenseeVersion = package.Build.OverrideLicenseeVersion.Value; @@ -1064,7 +1178,14 @@ public void Deserialize(IUnrealStream stream) { FolderName = stream.ReadString(); } - +#if SHADOW_STRIKE + if (stream.Package.Build == BuildGeneration.SCX && + stream.LicenseeVersion >= 83) + { + // reads 0 + int scInt32 = stream.ReadInt32(); + } +#endif PackageFlags = stream.ReadFlags32(); Console.WriteLine("Package Flags:" + PackageFlags); #if HAWKEN || GIGANTIC @@ -1074,6 +1195,24 @@ public void Deserialize(IUnrealStream stream) { stream.Read(out int vUnknown); } +#endif +#if MASS_EFFECT + if (stream.Package.Build == BuildGeneration.SFX) + { + // Untested, but seen in the reverse-engineered assembly... + if ((int)PackageFlags < 0) + { + // ... virtual call (didn't reverse) + } + + if (PackageFlags.HasFlag(PackageFlag.Cooked) && + stream.LicenseeVersion >= 194 && + stream.LicenseeVersion != 1008) + { + // SFXPatch Version (according to a localized string that references the same global constant) + int v94 = stream.ReadInt32(); + } + } #endif NameCount = stream.ReadInt32(); NameOffset = stream.ReadInt32(); @@ -1110,12 +1249,23 @@ public void Deserialize(IUnrealStream stream) + " Exports Count:" + ExportCount + " Exports Offset:" + ExportOffset + " Imports Count:" + ImportCount + " Imports Offset:" + ImportOffset ); - +#if SHADOW_STRIKE + // No version check, not serialized for DA_Online. + if (stream.Package.Build == BuildGeneration.SCX) + { + int scInt32_2 = stream.ReadInt32(); + Debug.Assert(scInt32_2 == 0xff0adde); + + string scSaveInfo = stream.ReadText(); + } +#endif if (stream.Version < 68) { HeritageCount = stream.ReadInt32(); Contract.Assert(HeritageCount > 0); + HeritageOffset = stream.ReadInt32(); + return; } @@ -1175,6 +1325,7 @@ public void Deserialize(IUnrealStream stream) { // ThumbnailTableOffset? But if so, the partial-upgrade must have skipped @AdditionalPackagesToCook stream.Skip(4); + return; } #endif @@ -1193,7 +1344,9 @@ public void Deserialize(IUnrealStream stream) #if SPELLBORN if (stream.Package.Build == GameBuild.BuildName.Spellborn && stream.Version >= 148) + { goto skipGuid; + } #endif stream.ReadStruct(out Guid); Console.WriteLine("GUID:" + Guid); @@ -1202,24 +1355,24 @@ public void Deserialize(IUnrealStream stream) if (stream.Package.Build == GameBuild.BuildName.Tera) stream.Position -= 4; #endif #if MKKE - if (stream.Package.Build != GameBuild.BuildName.MKKE) + if (stream.Package.Build == GameBuild.BuildName.MKKE) { + goto skipGenerations; + } #endif - int generationCount = stream.ReadInt32(); - Contract.Assert(generationCount >= 0); - Console.WriteLine("Generations Count:" + generationCount); + 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); -#if MKKE + // 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); + skipGenerations: #if DNF if (stream.Package.Build == GameBuild.BuildName.DNF && stream.Version >= 151) @@ -1282,23 +1435,71 @@ public void Deserialize(IUnrealStream stream) CookerVersion = stream.ReadInt32(); Console.WriteLine("CookerVersion:" + CookerVersion); } +#if MASS_EFFECT + if (stream.Package.Build == BuildGeneration.SFX) + { + // Appears to be similar to a PackageFileEngineVersion + + if (stream.LicenseeVersion >= 16 && stream.LicenseeVersion < 136) + { + stream.Read(out int _); + } + if (stream.LicenseeVersion >= 32 && stream.LicenseeVersion < 136) + { + stream.Read(out int _); + } + + if (stream.LicenseeVersion >= 35 && stream.LicenseeVersion < 113) + { + stream.ReadMap(out UMap> branch); + Console.WriteLine("Branch:" + branch); + } + + if (stream.LicenseeVersion >= 37) + { + // Compiler-Constant ? 1 + stream.Read(out int _); + + // Compiler-Constant changelist? 1376256 (Mass Effect 1: LE) + stream.Read(out int _); + } + + if (stream.LicenseeVersion >= 39 && stream.LicenseeVersion < 136) + { + stream.Read(out int _); + } + } +#endif // Read compressed info? - if (stream.Version >= VCompression) + if (stream.Version >= (uint)PackageObjectLegacyVersion.CompressionAdded) { CompressionFlags = stream.ReadUInt32(); Console.WriteLine("CompressionFlags:" + CompressionFlags); + stream.ReadArray(out CompressedChunks); } + // SFX reads 392? if (stream.Version >= VPackageSource) { PackageSource = stream.ReadUInt32(); Console.WriteLine("PackageSource:" + PackageSource); } +#if MASS_EFFECT + if (stream.Package.Build == BuildGeneration.SFX) + { + if (stream.LicenseeVersion >= 44 && stream.LicenseeVersion < 136) + { + stream.Read(out int _); + } + } +#endif #if UE4 if (stream.UE4Version > 0) + { return; + } #endif if (stream.Version >= VAdditionalPackagesToCook) { @@ -1339,6 +1540,7 @@ public void Deserialize(IUnrealStream stream) { // FIXME: Package format is being deserialized incorrectly and fails here. stream.ReadUInt32(); + return; } #endif @@ -1462,19 +1664,19 @@ public void Deserialize(IUnrealStream stream) /// List of unique unreal names. /// [PublicAPI] - public List Names { get; private set; } + public List Names { get; private set; } = new List(); /// /// List of info about exported objects. /// [PublicAPI] - public List Exports { get; private set; } + public List Exports { get; private set; } = new List(); /// /// List of info about imported objects. /// [PublicAPI] - public List Imports { get; private set; } + public List Imports { get; private set; } = new List(); /// /// List of info about dependency objects. @@ -1494,7 +1696,7 @@ public void Deserialize(IUnrealStream stream) /// Includes Exports and Imports!. /// [PublicAPI] - public List Objects { get; private set; } + public List Objects { get; private set; } = new List(); [PublicAPI] public NativesTablePackage NTLPackage; diff --git a/src/UnrealStream.cs b/src/UnrealStream.cs index 6394eab3..595576f3 100644 --- a/src/UnrealStream.cs +++ b/src/UnrealStream.cs @@ -305,7 +305,7 @@ public int ReadIndex() => : ReadCompactIndex(); [Obsolete] - public long ReadNameIndex() => (uint)ReadNameIndex(out int n) | ((long)n << 32); + // HACK: Specific builds logic should be displaced by a specialized stream. public int ReadNameIndex(out int num) { @@ -313,6 +313,9 @@ public int ReadNameIndex(out int num) if (Archive.Version >= (uint)PackageObjectLegacyVersion.NumberAddedToName #if BIOSHOCK || Archive.Package.Build == UnrealPackage.GameBuild.BuildName.BioShock +#endif +#if SHADOW_STRIKE + || Archive.Package.Build == BuildGeneration.ShadowStrike #endif ) { @@ -967,6 +970,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 UName key); + ReadArray(stream, out UArray value); + map.Add(key, value); + } + } + public static void ReadMap(this IUnrealStream stream, out UMap map) { int c = stream.ReadLength(); diff --git a/src/UnrealTypes.cs b/src/UnrealTypes.cs index 003b5797..2a68132f 100644 --- a/src/UnrealTypes.cs +++ b/src/UnrealTypes.cs @@ -31,6 +31,10 @@ public enum PropertyType : byte #if GIGANTIC JsonRefProperty, #endif +#if MASS_EFFECT + StringRefProperty, + BioMask4Property, +#endif InterfaceProperty, // >= UE3, displaced FixedArrayProperty, actual value 15, but we don't need the value for UE3 types. ComponentProperty, // >= UE3