diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml new file mode 100644 index 00000000..9c2a57e1 --- /dev/null +++ b/.github/workflows/benchmark.yml @@ -0,0 +1,33 @@ +name: Benchmark +on: + push: + branches: + - master + +permissions: + contents: write + deployments: write + +jobs: + benchmark: + name: Run Benchmark + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-dotnet@v4 + with: + dotnet-version: 8.0.x + + - name: Run benchmark + run: cd Benchmark && dotnet run -c Release --exporters json --filter '*' + + - name: Store benchmark results + uses: rhysd/github-action-benchmark@v1 + with: + name: Benchmark.Net Benchmark + tool: 'benchmarkdotnet' + output-file-path: Benchmark/BenchmarkDotNet.Artifacts/results/Eliot.UELib.Benchmark.Benchmarks-report-full-compressed.json + github-token: ${{ secrets.GITHUB_TOKEN }} + auto-push: true + alert-threshold: '200%' + comment-on-alert: true diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index ed8d415b..e8ea20c2 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -1,4 +1,4 @@ -name: Build .NET Framework and Publish to NuGet +name: Build .NET and Publish on: workflow_dispatch: @@ -8,25 +8,39 @@ on: jobs: build: - runs-on: windows-2019 + runs-on: windows-2022 + strategy: + matrix: + dotnet: [ '8.0.x' ] permissions: packages: write contents: read steps: - uses: actions/checkout@v4 - - - name: Setup MSBuild - uses: microsoft/setup-msbuild@v2 - - - name: Setup NuGet - uses: NuGet/setup-nuget@v2 - - - name: Restore dependencies - run: nuget restore src/Eliot.UELib.csproj - + + - name: Setup + uses: actions/setup-dotnet@v4 + with: + dotnet-version: ${{ matrix.dotnet }} + + - name: Install + run: dotnet restore src/Eliot.UELib.csproj + - name: Build - run: msbuild src/Eliot.UELib.csproj -t:rebuild -property:Configuration=Release - + run: dotnet build src/Eliot.UELib.csproj + + - name: Restore + run: dotnet restore src/Eliot.UELib.csproj + + #- name: Test + # run: dotnet test Test/Eliot.UELib.Test.csproj + + - name: Pack + run: dotnet pack --configuration Release src/Eliot.UELib.csproj + + #- name: Push + # run: dotnet nuget push src/Eliot.UELib.csproj -k ${{ secrets.NUGET_API_KEY }}} -s https://api.nuget.org/v3/index.json + - name: Publish Eliot.UELib to NuGet id: nuget uses: alirezanet/publish-nuget@v3.1.0 diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e9d6259..148240e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # +## [1.8.0](https://github.com/EliotVU/Unreal-Library/releases/tag/1.8.0) + +* 89401543 Support for Stranglehold +* 6c89b66a Improved support for Batman series +* 4222595f Improved support for Borderlands: GOTYE +* 6c89b66a Fixed bad decompilation of intrinsic array function calls under certain circumstances. +* 8ba153b3 Fixed missing ')' in a replication statement +* 3eab0cba #92 Fixed issue with overriding a class type when initializing a package. + ## [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. diff --git a/CLI/Eliot.UELib.CLI.csproj b/CLI/Eliot.UELib.CLI.csproj index 1ef437bb..d4c86efc 100644 --- a/CLI/Eliot.UELib.CLI.csproj +++ b/CLI/Eliot.UELib.CLI.csproj @@ -1,6 +1,7 @@ net8.0 + 12.0 Exe UELib.CLI publish\ @@ -23,17 +24,4 @@ - - - False - Microsoft .NET Framework 4.8 %28x86 and x64%29 - true - - - False - .NET Framework 3.5 SP1 - false - - - \ No newline at end of file diff --git a/README.md b/README.md index eebce869..f35357a5 100644 --- a/README.md +++ b/README.md @@ -134,6 +134,7 @@ 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 | | +| Stranglehold | 2605 | 375/025 | | | Mass Effect (Xbox 360) | 2674 | 391/092 | Xenon | | Medal of Honor: Airborne | 2859 | 421/011 | | | Frontlines: Fuel of War | 2917 | 433/052 | | @@ -221,10 +222,10 @@ This is a table of games that are confirmed to be compatible with the current st | Gal\*Gun: Double Peace | 10897 | 871/000 | | | Battleborn | 8623/1055 | 874/078 | | | A Hat in Time | 12097 | 877-893/005 | | +| Blue Estate The Game | 10246 | 893/000 | | | Shadow Complex Remastered | 10897 | 893/001 | | | Soldier Front 2 | 6712 | 904/009 | | | Rise of the Triad | 10508 | Unknown | | -| Outlast | 12046 | Unknown | | | Sherlock Holmes: Crimes and Punishments | 10897 | Unknown | | | Alien Rage | 7255 | Unknown | | diff --git a/Test/Eliot.UELib.Test.csproj b/Test/Eliot.UELib.Test.csproj index b2da294f..54952c49 100644 --- a/Test/Eliot.UELib.Test.csproj +++ b/Test/Eliot.UELib.Test.csproj @@ -1,6 +1,7 @@ net8.0 + 12.0 false Debug;Release diff --git a/Test/UnrealPackageTests.cs b/Test/UnrealPackageTests.cs index e21d0cef..8abfdc5c 100644 --- a/Test/UnrealPackageTests.cs +++ b/Test/UnrealPackageTests.cs @@ -10,6 +10,31 @@ namespace Eliot.UELib.Test [TestClass] public class UnrealPackageTests { + public class MyUModel : UModel; + + [TestMethod] + public void TestClassTypeOverride() + { + using var stream = UnrealPackageUtilities.CreateTempPackageStream(); + using var linker = new UnrealPackage(stream); + + Assert.IsTrue(linker.GetClassType("Model") == typeof(UnknownObject)); + linker.AddClassType("Model", typeof(MyUModel)); + Assert.IsTrue(linker.GetClassType("Model") == typeof(MyUModel)); + linker.InitializePackage(UnrealPackage.InitFlags.RegisterClasses); + Assert.IsTrue(linker.GetClassType("Model") == typeof(UModel)); + + using var stream2 = UnrealPackageUtilities.CreateTempPackageStream(); + using var linker2 = new UnrealPackage(stream2); + + // Swapped order... + Assert.IsTrue(linker2.GetClassType("Model") == typeof(UnknownObject)); + linker2.InitializePackage(UnrealPackage.InitFlags.RegisterClasses); + Assert.IsTrue(linker2.GetClassType("Model") == typeof(UModel)); + linker2.AddClassType("Model", typeof(MyUModel)); + Assert.IsTrue(linker2.GetClassType("Model") == typeof(MyUModel)); + } + internal static void AssertTestClass(UnrealPackage linker) { var testClass = linker.FindObject("Test"); diff --git a/Test/UnrealPackageUtilities.cs b/Test/UnrealPackageUtilities.cs new file mode 100644 index 00000000..fa9dac5b --- /dev/null +++ b/Test/UnrealPackageUtilities.cs @@ -0,0 +1,19 @@ +using System; +using System.IO; +using UELib; + +namespace Eliot.UELib.Test +{ + internal static class UnrealPackageUtilities + { + // HACK: Ugly workaround the issues with UPackageStream + public static UPackageStream CreateTempPackageStream() + { + string tempFilePath = Path.Join(Path.GetTempFileName()); + File.WriteAllBytes(tempFilePath, BitConverter.GetBytes(UnrealPackage.Signature)); + + var stream = new UPackageStream(tempFilePath, FileMode.Open, FileAccess.ReadWrite); + return stream; + } + } +} diff --git a/Test/UnrealStreamTests.cs b/Test/UnrealStreamTests.cs index 6082e92f..60e0e61f 100644 --- a/Test/UnrealStreamTests.cs +++ b/Test/UnrealStreamTests.cs @@ -12,16 +12,6 @@ namespace Eliot.UELib.Test [TestClass] public class UnrealStreamTests { - // HACK: Ugly workaround the issues with UPackageStream - private static UPackageStream CreateTempStream() - { - string tempFilePath = Path.Join(Path.GetTempFileName()); - File.WriteAllBytes(tempFilePath, BitConverter.GetBytes(UnrealPackage.Signature)); - - var stream = new UPackageStream(tempFilePath, FileMode.Open, FileAccess.ReadWrite); - return stream; - } - [DataTestMethod] [DataRow(PackageObjectLegacyVersion.Undefined, 1, +0b0000000000000000000000100001)] [DataRow(PackageObjectLegacyVersion.Undefined, 1, -0b0000000000000000000000100001)] @@ -32,7 +22,7 @@ private static UPackageStream CreateTempStream() [DataRow(PackageObjectLegacyVersion.CompactIndexDeprecated, 4, int.MaxValue)] public void SerializeCompactIndex(PackageObjectLegacyVersion version, int count, int compactIndex) { - using var stream = CreateTempStream(); + using var stream = UnrealPackageUtilities.CreateTempPackageStream(); using var linker = new UnrealPackage(stream); linker.Build = new UnrealPackage.GameBuild(linker); linker.Summary = new UnrealPackage.PackageFileSummary @@ -65,7 +55,7 @@ public void SerializeCompactIndex(PackageObjectLegacyVersion version, int count, [DataRow(PackageObjectLegacyVersion.Undefined, "语言处理")] public void SerializeString(PackageObjectLegacyVersion version, string text) { - using var stream = CreateTempStream(); + using var stream = UnrealPackageUtilities.CreateTempPackageStream(); using var linker = new UnrealPackage(stream); linker.Build = new UnrealPackage.GameBuild(linker); linker.Summary = new UnrealPackage.PackageFileSummary @@ -86,7 +76,7 @@ public void SerializeString(PackageObjectLegacyVersion version, string text) [TestMethod] public void SerializeStruct() { - using var stream = CreateTempStream(); + using var stream = UnrealPackageUtilities.CreateTempPackageStream(); using var linker = new UnrealPackage(stream); linker.Build = new UnrealPackage.GameBuild(linker); linker.Summary = new UnrealPackage.PackageFileSummary(); @@ -113,7 +103,7 @@ public void SerializeStruct() [TestMethod] public void SerializeStructMarshal() { - using var stream = CreateTempStream(); + using var stream = UnrealPackageUtilities.CreateTempPackageStream(); using var linker = new UnrealPackage(stream); linker.Build = new UnrealPackage.GameBuild(linker); linker.Summary = new UnrealPackage.PackageFileSummary(); @@ -156,7 +146,7 @@ public void SerializeStructMarshal() [DataRow(PackageObjectLegacyVersion.LazyArrayReplacedWithBulkData)] public void SerializeBulkData(PackageObjectLegacyVersion version) { - using var stream = CreateTempStream(); + using var stream = UnrealPackageUtilities.CreateTempPackageStream(); using var linker = new UnrealPackage(stream); linker.Build = new UnrealPackage.GameBuild(linker); linker.Summary = new UnrealPackage.PackageFileSummary @@ -192,7 +182,7 @@ public void SerializeBulkData(PackageObjectLegacyVersion version) [DataRow(PackageObjectLegacyVersion.VerticalOffsetAddedToUFont)] public void SerializeDataTypes(PackageObjectLegacyVersion version) { - using var stream = CreateTempStream(); + using var stream = UnrealPackageUtilities.CreateTempPackageStream(); using var linker = new UnrealPackage(stream); linker.Build = new UnrealPackage.GameBuild(linker); linker.Summary = new UnrealPackage.PackageFileSummary diff --git a/src/Branch/DefaultEngineBranch.cs b/src/Branch/DefaultEngineBranch.cs index fe74cc6f..3ff54b57 100644 --- a/src/Branch/DefaultEngineBranch.cs +++ b/src/Branch/DefaultEngineBranch.cs @@ -378,6 +378,10 @@ protected override TokenMap BuildTokenMap(UnrealPackage linker) tokenMap[0x5B] = typeof(ByteConstToken); break; + + case UnrealPackage.GameBuild.BuildName.Borderlands_GOTYE: + tokenMap[0x5B] = typeof(BLVariableToken); + break; #endif #if BIOSHOCK case UnrealPackage.GameBuild.BuildName.BioShock: diff --git a/src/Branch/PackageObjectLegacyVersion.cs b/src/Branch/PackageObjectLegacyVersion.cs index 5d13e128..064f7107 100644 --- a/src/Branch/PackageObjectLegacyVersion.cs +++ b/src/Branch/PackageObjectLegacyVersion.cs @@ -162,23 +162,27 @@ public enum PackageObjectLegacyVersion CompressionAdded = 334, NumberAddedToName = 343, + + [Discardable] GameGOW = 374, // Engine Version: 2451 + [Discardable] GameStranglehold = 375, // Engine Version: 2605 - // FIXME: Version 374-491; Delegate source type changed from Name to Object - ChangedDelegateSourceFromNameToObject = 376, - - [Discardable] GameGOW = 374, + /// + /// Possibly attested first with Stranglehold (v375) + /// FIXME: Version 375-491; Delegate source type changed from Name to Object + /// + ChangedDelegateSourceFromNameToObject = GameStranglehold, /// - /// Not attested in (GoW v374, oldest attest (v421) + /// Not attested in GoW v374), oldest attests (v375,v421) /// FIXME: Version /// SkipSizeAddedToArrayFindTokenIntrinsics = GameGOW + 1, /// - /// Not attested in GoW (v374), oldest attest (v421) + /// Not attested in GoW (v374), oldest attests (v375,v421) /// FIXME: Unknown version /// - StructReferenceAddedToStructMember = GameGOW + 1, + StructReferenceAddedToStructMember = GameStranglehold, // 417 according to the GoW client LightingChannelsAddedToPoly = 417, @@ -189,9 +193,9 @@ public enum PackageObjectLegacyVersion /// Oldest attest MOHA (v421), but not MKKE (v472, non standard) /// FIXME: Unknown version /// - IsCopyAddedToStructMember = GameGOW + 1, + IsCopyAddedToStructMember = GameStranglehold + 1, - [Discardable] GameFFOW = 433, + [Discardable] GameFFOW = 433, // Engine Version: 2917 /// /// Oldest attest FFOW (v433), but not MKKE (v472, non standard) diff --git a/src/Branch/UE3/BL2/Tokens/BLVariableToken.cs b/src/Branch/UE3/BL2/Tokens/BLVariableToken.cs new file mode 100644 index 00000000..e07e2c1a --- /dev/null +++ b/src/Branch/UE3/BL2/Tokens/BLVariableToken.cs @@ -0,0 +1,12 @@ +using UELib.Core; +using UELib.ObjectModel.Annotations; +using UELib.Tokens; + +namespace UELib.Branch.UE3.BL2.Tokens +{ + [ExprToken(ExprToken.InstanceVariable)] + public class BLVariableToken : UStruct.UByteCodeDecompiler.FieldToken + { + + } +} diff --git a/src/Branch/UE3/RSS/EngineBranch.RSS.cs b/src/Branch/UE3/RSS/EngineBranch.RSS.cs index 0d6fb7e9..6e5bfc31 100644 --- a/src/Branch/UE3/RSS/EngineBranch.RSS.cs +++ b/src/Branch/UE3/RSS/EngineBranch.RSS.cs @@ -12,11 +12,17 @@ public EngineBranchRSS(BuildGeneration generation) : base(BuildGeneration.RSS) protected override TokenMap BuildTokenMap(UnrealPackage linker) { var tokenMap = base.BuildTokenMap(linker); + + // Identical to ContextToken and ClassContextToken. Spotted in BM 1, 2, and 4 + tokenMap[0x50] = typeof(RSSContextToken); + if (linker.Build == UnrealPackage.GameBuild.BuildName.Batman4) { - tokenMap[0x2B] = typeof(NameConstNoNumberToken); // FIXME: NameConst but without the Int32 number at the end + // FIXME: NameConst but without the Int32 number at the end + tokenMap[0x2B] = typeof(NameConstNoNumberToken); } + return tokenMap; } } -} \ No newline at end of file +} diff --git a/src/Branch/UE3/RSS/Tokens/Bm4ContextToken.cs b/src/Branch/UE3/RSS/Tokens/Bm4ContextToken.cs deleted file mode 100644 index 86d19950..00000000 --- a/src/Branch/UE3/RSS/Tokens/Bm4ContextToken.cs +++ /dev/null @@ -1,9 +0,0 @@ -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/RSSContextToken.cs b/src/Branch/UE3/RSS/Tokens/RSSContextToken.cs new file mode 100644 index 00000000..bfee8bfd --- /dev/null +++ b/src/Branch/UE3/RSS/Tokens/RSSContextToken.cs @@ -0,0 +1,12 @@ +using UELib.Core; +using UELib.ObjectModel.Annotations; +using UELib.Tokens; + +namespace UELib.Branch.UE3.RSS.Tokens +{ + [ExprToken(ExprToken.Context)] + public class RSSContextToken : UStruct.UByteCodeDecompiler.ContextToken + { + + } +} diff --git a/src/Core/Classes/Props/UProperty.cs b/src/Core/Classes/Props/UProperty.cs index f3bda59f..095015a2 100644 --- a/src/Core/Classes/Props/UProperty.cs +++ b/src/Core/Classes/Props/UProperty.cs @@ -170,7 +170,11 @@ protected override void Deserialize() Record(nameof(CategoryName), CategoryName); } - if (_Buffer.Version >= (uint)PackageObjectLegacyVersion.AddedArrayEnumToUProperty) + if (_Buffer.Version >= (uint)PackageObjectLegacyVersion.AddedArrayEnumToUProperty +#if MIDWAY + || Package.Build == UnrealPackage.GameBuild.BuildName.Stranglehold +#endif + ) { ArrayEnum = _Buffer.ReadObject(); Record(nameof(ArrayEnum), ArrayEnum); diff --git a/src/Core/Classes/UClassDecompiler.cs b/src/Core/Classes/UClassDecompiler.cs index dba2021e..35b26ea6 100644 --- a/src/Core/Classes/UClassDecompiler.cs +++ b/src/Core/Classes/UClassDecompiler.cs @@ -544,7 +544,7 @@ public string FormatReplication() : string.Empty; string statementFormat = statementType != string.Empty ? $"{statementType} if({statementCode})" - : $"if({statementCode}"; + : $"if({statementCode})"; output.Append(statementFormat); UDecompilingState.AddTab(); diff --git a/src/Core/Classes/UComponent.cs b/src/Core/Classes/UComponent.cs index ad03d8f5..35c6373f 100644 --- a/src/Core/Classes/UComponent.cs +++ b/src/Core/Classes/UComponent.cs @@ -1,4 +1,5 @@ -using UELib.Branch; +using UELib.Annotations; +using UELib.Branch; namespace UELib.Core { @@ -6,7 +7,7 @@ namespace UELib.Core [BuildGeneration(BuildGeneration.UE3)] public class UComponent : UObject { - public UClass TemplateOwnerClass; + [CanBeNull] public UClass TemplateOwnerClass; public UName TemplateName; public UComponent() @@ -14,4 +15,4 @@ public UComponent() ShouldDeserializeOnDemand = true; } } -} \ No newline at end of file +} diff --git a/src/Core/Classes/UDefaultProperty.cs b/src/Core/Classes/UDefaultProperty.cs index 777e0c6c..fef49176 100644 --- a/src/Core/Classes/UDefaultProperty.cs +++ b/src/Core/Classes/UDefaultProperty.cs @@ -499,7 +499,11 @@ private void DeserializeTypeDataUE3() break; case PropertyType.ByteProperty: - if (_Buffer.Version >= (uint)PackageObjectLegacyVersion.EnumNameAddedToBytePropertyTag) + if (_Buffer.Version >= (uint)PackageObjectLegacyVersion.EnumNameAddedToBytePropertyTag +#if BATMAN + && _Buffer.Package.Build.Generation != BuildGeneration.RSS +#endif + ) { _Buffer.Read(out _TypeData.EnumName); Record(nameof(_TypeData.EnumName), _TypeData.EnumName); @@ -659,7 +663,7 @@ private string DeserializeDefaultPropertyValue(PropertyType type, ref Deserializ propertyValue = PropertyDisplay.FormatLiteral(index); break; } - + case PropertyType.BioMask4Property: { _Buffer.Read(out byte value); @@ -1069,7 +1073,8 @@ private string DeserializeDefaultPropertyValue(PropertyType type, ref Deserializ string tagExpr = tag.Name; if (tag.ArrayIndex > 0) { - tagExpr += PropertyDisplay.FormatT3DElementAccess(tag.ArrayIndex.ToString(), _Buffer.Version); + tagExpr += PropertyDisplay.FormatT3DElementAccess(tag.ArrayIndex.ToString(), + _Buffer.Version); } propertyValue += $"{tagExpr}={tag.Value}"; @@ -1154,7 +1159,7 @@ private string DeserializeDefaultPropertyValue(PropertyType type, ref Deserializ for (var i = 0; i < arraySize; ++i) { string elementAccessText = - PropertyDisplay.FormatT3DElementAccess(i.ToString(), _Buffer.Version); + PropertyDisplay.FormatT3DElementAccess(i.ToString(), _Buffer.Version); string elementValue = DeserializeDefaultPropertyValue(arrayType, ref deserializeFlags); if ((_TempFlags & ReplaceNameMarker) != 0) { diff --git a/src/Core/Classes/UObject.cs b/src/Core/Classes/UObject.cs index 585fbd6a..eff6944b 100644 --- a/src/Core/Classes/UObject.cs +++ b/src/Core/Classes/UObject.cs @@ -176,7 +176,7 @@ public void BeginDeserializing() DeserializationState |= ObjectState.Errorlized; Console.Error.WriteLine($"\r\n> Object deserialization error for {GetReferencePath()} as {GetType()}" + - $"\r\n> Exception: {ThrownException}"); + $"\r\n> Exception: {ThrownException}"); } finally { @@ -195,7 +195,8 @@ private void InitBuffer() // Bypass the terrible and slow endian reverse call int read = Package.Stream.EndianAgnosticRead(buffer, 0, ExportTable.SerialSize); - Contract.Assert(ExportTable.SerialOffset + ExportTable.SerialSize <= Package.Stream.Length, "Exceeded file's length"); + Contract.Assert(ExportTable.SerialOffset + ExportTable.SerialSize <= Package.Stream.Length, + "Exceeded file's length"); //Debug.Assert(read == ExportTable.SerialSize, $"Incomplete read; expected a total bytes of {ExportTable.SerialSize} but got {read}"); } @@ -311,6 +312,7 @@ protected virtual void Deserialize() if (header.Item2 == 2) { _Buffer.ReadInt32(); + _Buffer.ConformRecordPosition(); } } } @@ -319,6 +321,7 @@ protected virtual void Deserialize() if (HasObjectFlag(ObjectFlagsLO.HasStack)) { _Buffer.ReadClass(out StateFrame); + Record(nameof(StateFrame), StateFrame); } #if MKKE || BATMAN if (Package.Build == UnrealPackage.GameBuild.BuildName.MKKE || @@ -338,15 +341,32 @@ protected virtual void Deserialize() // HACK: Ugly work around for unregistered component classes... // Simply for checking for the parent's class is not reliable without importing objects. - case UnknownObject _ when _Buffer.Length >= 12 && IsTemplate(): + case UnknownObject _ when _Buffer.Length >= 12 && Archetype != null && IsTemplate(): { + long backupPosition = _Buffer.Position; + var fakeComponent = new UComponent(); - DeserializeTemplate(fakeComponent); + try + { + DeserializeTemplate(fakeComponent); + } + catch (InvalidCastException exception) + { + Console.Error.WriteLine("Failed attempt to interpret object as a template {0}", + exception); + + _Buffer.Position = backupPosition; + _Buffer.ConformRecordPosition(); + + // ISSUE: If the above recorded any data, the data will not be undone. + } + break; } } } - skipNetIndex: + + skipNetIndex: DeserializeNetIndex(); #if THIEF_DS || DEUSEX_IW diff --git a/src/Core/Classes/UObjectDecompiler.cs b/src/Core/Classes/UObjectDecompiler.cs index de848d06..edeef035 100644 --- a/src/Core/Classes/UObjectDecompiler.cs +++ b/src/Core/Classes/UObjectDecompiler.cs @@ -24,19 +24,26 @@ public virtual string Decompile() return output + $"\r\n{UDecompilingState.Tabs}// Cannot decompile an imported object"; } + // FIXME: Won't be detected, an UnknownObject might be a UComponent. + if (this is UComponent uComponent) + { + output += $"{UDecompilingState.Tabs}// TemplateOwnerClass: {PropertyDisplay.FormatLiteral(uComponent.TemplateOwnerClass)}\r\n"; + output += $"{UDecompilingState.Tabs}// TemplateOwnerName: {PropertyDisplay.FormatLiteral(uComponent.TemplateName)}\r\n"; + } + + if (Archetype != null) + { + output += $"{UDecompilingState.Tabs}// Archetype: {PropertyDisplay.FormatLiteral(Archetype)}\r\n"; + } + output += UDecompilingState.Tabs; - output += $"begin object name={Name}"; + output += $"begin object name=\"{Name}\""; // If null then we have a new sub-object (not an override) if (Archetype == null) { Debug.Assert(Class != null); output += $" class={Class.GetReferencePath()}"; } - else - { - // Commented out, too noisy but useful. - //output += $" /*archetype={Archetype.GetReferencePath()}*/"; - } output += "\r\n"; UDecompilingState.AddTabs(1); diff --git a/src/Core/Tokens/ArrayTokens.cs b/src/Core/Tokens/ArrayTokens.cs index 3b1d0303..a51c684e 100644 --- a/src/Core/Tokens/ArrayTokens.cs +++ b/src/Core/Tokens/ArrayTokens.cs @@ -1,4 +1,5 @@ -using UELib.Branch; +using System.IO; +using UELib.Branch; using UELib.ObjectModel.Annotations; using UELib.Tokens; @@ -145,6 +146,13 @@ protected string DecompileOneParamMethod(string functionName) Decompiler._CanAddSemicolon = true; string context = DecompileNext(); string param1 = DecompileNext(); + + if (Package.Version >= (uint)PackageObjectLegacyVersion.EndTokenAppendedToArrayTokenIntrinsics) + { + // EndParms + AssertSkipCurrentToken(); + } + return $"{context}.{functionName}({param1})"; } @@ -154,6 +162,13 @@ protected string DecompileTwoParamMethod(string functionName) string context = DecompileNext(); string param1 = DecompileNext(); string param2 = DecompileNext(); + + if (Package.Version >= (uint)PackageObjectLegacyVersion.EndTokenAppendedToArrayTokenIntrinsics) + { + // EndParms + AssertSkipCurrentToken(); + } + return $"{context}.{functionName}({param1}, {param2})"; } } @@ -203,6 +218,7 @@ public override string Decompile() [ExprToken(ExprToken.DynArrayAdd)] public class DynamicArrayAddToken : DynamicArrayMethodToken { + // Ugly copy, but this is the only array token that always has EndParms regardless of engine version. public override void Deserialize(IUnrealStream stream) { // Array @@ -217,9 +233,16 @@ public override void Deserialize(IUnrealStream stream) Decompiler.DeserializeDebugToken(); } + // Ugly copy, but this is the only array token that always has EndParms regardless of engine version. public override string Decompile() { - return DecompileOneParamMethod("Add"); + Decompiler._CanAddSemicolon = true; + string context = DecompileNext(); + string param1 = DecompileNext(); + + AssertSkipCurrentToken(); + + return $"{context}.Add({param1})"; } } diff --git a/src/Eliot.UELib.csproj b/src/Eliot.UELib.csproj index a42d1896..21c2b706 100644 --- a/src/Eliot.UELib.csproj +++ b/src/Eliot.UELib.csproj @@ -1,7 +1,8 @@ - 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 + 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;MIDWAY net48;netstandard2.0;netstandard2.1;net8.0 + 12.0 Library UELib true @@ -70,7 +71,7 @@ Eliot.UELib $(AssemblyName) - $(VersionPrefix)1.7.0 + $(VersionPrefix)1.8.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/UnrealBuild.cs b/src/UnrealBuild.cs index 9b766045..2998d14e 100644 --- a/src/UnrealBuild.cs +++ b/src/UnrealBuild.cs @@ -100,10 +100,17 @@ public enum BuildGeneration /// /// Gearbox Software /// - /// Modified Unreal Engine 3 for Borderlands + /// Modified Unreal Engine 3 for Borderlands. /// GB, + /// + /// Midway + /// + /// Modified Unreal Engine3 for Midway games. + /// + Midway3, + /// /// Unreal Engine 4 /// diff --git a/src/UnrealPackage.cs b/src/UnrealPackage.cs index a55e5041..349f2705 100644 --- a/src/UnrealPackage.cs +++ b/src/UnrealPackage.cs @@ -26,6 +26,7 @@ namespace UELib using Branch.UE3.RL; using Branch.UE3.SFX; using Branch.UE2.ShadowStrike; + using System.Text; /// /// Represents the method that will handle the UELib.UnrealPackage.NotifyObjectAdded @@ -378,6 +379,13 @@ public enum BuildName /// [Build(369, 6)] RoboBlitz, + /// + /// Stranglehold + /// + /// 375/025 + /// + [Build(375, 25, BuildGeneration.Midway3)] Stranglehold, + /// /// Medal of Honor: Airborne /// @@ -456,10 +464,10 @@ public enum BuildName /// /// Batman: Arkham Asylum /// - /// 576/021 - /// No Special support, but there's no harm in recognizing this build. + /// 576/021 (Missing most changes guarded by ) /// - [Build(576, 21)] Batman1, + [Build(576, 21)] [BuildEngineBranch(typeof(EngineBranchRSS))] + Batman1, /// /// 576/100 @@ -1142,6 +1150,8 @@ public void Deserialize(IUnrealStream stream) Version &= 0xFFFFU; Console.WriteLine("Package Version:" + Version + "/" + LicenseeVersion); + Contract.Assert(Version != 0, "Bad package version 0!"); + SetupBuild(stream.Package); Debug.Assert(stream.Package.Build != null); Console.WriteLine("Build:" + stream.Package.Build); @@ -1173,7 +1183,23 @@ public void Deserialize(IUnrealStream stream) HeaderSize = stream.ReadInt32(); Console.WriteLine("Header Size: " + HeaderSize); } +#if MIDWAY + if (stream.Package.Build == BuildGeneration.Midway3 && + stream.LicenseeVersion >= 2) + { + stream.Read(out int abbrev); + + string codename = Encoding.UTF8.GetString(BitConverter.GetBytes(abbrev)); + Console.WriteLine($"Midway game codename: {codename}"); + + stream.Read(out int customVersion); + if (customVersion >= 256) + { + stream.Read(out int _); + } + } +#endif if (stream.Version >= VFolderName) { FolderName = stream.ReadString(); @@ -1268,7 +1294,13 @@ public void Deserialize(IUnrealStream stream) return; } - +#if MIDWAY + if (stream.Package.Build == GameBuild.BuildName.Stranglehold && + stream.Version >= 375) + { + stream.Read(out int _); + } +#endif if (stream.Version >= VDependsOffset) { DependsOffset = stream.ReadInt32(); @@ -2244,7 +2276,7 @@ public void RegisterClass(string className, Type classObject) [PublicAPI] public void AddClassType(string className, Type classObject) { - _ClassTypes.Add(className.ToLower(), classObject); + _ClassTypes[className.ToLower()] = classObject; } [PublicAPI]