diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..96687271 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "dotnet.defaultSolution": "src\\Eliot.UELib.sln" +} \ No newline at end of file diff --git a/Benchmark/Eliot.UELib.Benchmark.csproj b/Benchmark/Eliot.UELib.Benchmark.csproj index 5605e8ec..48087c5e 100644 --- a/Benchmark/Eliot.UELib.Benchmark.csproj +++ b/Benchmark/Eliot.UELib.Benchmark.csproj @@ -4,15 +4,17 @@ Exe netcoreapp3.1 Debug;Release;UnitTest + true + False + enable - + - diff --git a/Benchmark/UnrealPackageBenchmark.cs b/Benchmark/UnrealPackageBenchmark.cs index e3ab8675..c92c0459 100644 --- a/Benchmark/UnrealPackageBenchmark.cs +++ b/Benchmark/UnrealPackageBenchmark.cs @@ -1,7 +1,6 @@ using System.IO; using System.Reflection; using BenchmarkDotNet.Attributes; -using Eliot.UELib.Test; using UELib; namespace Eliot.UELib.Benchmark @@ -9,11 +8,11 @@ namespace Eliot.UELib.Benchmark public class UnrealPackageBenchmark { private readonly string _TempFilePath; - private UnrealPackage _Linker; + private readonly UnrealPackage _Linker; public UnrealPackageBenchmark() { - byte[] testFileBytes = Packages.TestUC2; + byte[] testFileBytes = { }; _TempFilePath = Path.Join(Assembly.GetExecutingAssembly().Location, "../test.u"); // Workaround due the enforced use of UnrealLoader's UPackageStream File.WriteAllBytes(_TempFilePath, testFileBytes); @@ -34,8 +33,8 @@ public void PackageDeserialization() public void NamesDeserialization() { _Linker.Stream.Position = 4; - _Linker.Stream.Seek(_Linker.Summary.NamesOffset, SeekOrigin.Begin); - for (var i = 0; i < _Linker.Summary.NamesCount; ++i) + _Linker.Stream.Seek(_Linker.Summary.NameOffset, SeekOrigin.Begin); + for (var i = 0; i < _Linker.Summary.NameCount; ++i) { var nameEntry = new UNameTableItem(); nameEntry.Deserialize(_Linker.Stream); diff --git a/Benchmark/UnrealStreamBenchmark.cs b/Benchmark/UnrealStreamBenchmark.cs index 3f127f6b..5fa8b766 100644 --- a/Benchmark/UnrealStreamBenchmark.cs +++ b/Benchmark/UnrealStreamBenchmark.cs @@ -1,31 +1,135 @@ using System.IO; -using UELib; -using UELib.Core.Types; using BenchmarkDotNet.Attributes; +using UELib; +using UELib.Core; namespace Eliot.UELib.Benchmark { public class UnrealStreamBenchmark { - private IUnrealStream _Stream; - + private readonly byte[] _ArchiveData = new byte[20]; + private readonly IUnrealStream _Stream; + + private UColor _Color = new UColor(128, 64, 32, 0); + private readonly long _ColorPosition; + + private int _CompactIndex1 = 0x40 - 1; + private int _CompactIndex2 = 0x40 + (0x80 - 1); + private int _CompactIndex3 = 0x40 + 0x80 + (0x80 - 1); + private readonly long _CompactIndexPosition1, _CompactIndexPosition2, _CompactIndexPosition3; + + private string _String = "String"; + private readonly long _StringPosition; + public UnrealStreamBenchmark() { - // B, G, R, A; - var structBuffer = new byte[] { 255, 128, 64, 80 }; - var baseStream = new MemoryStream(structBuffer); - _Stream = new UnrealTestStream(null, baseStream); + var baseStream = new MemoryStream(_ArchiveData); + var testArchive = new UnrealTestArchive(null, 100); + _Stream = new UnrealTestStream(testArchive, baseStream); + + _CompactIndexPosition1 = _Stream.Position; + _Stream.WriteIndex(_CompactIndex1); + + _CompactIndexPosition2 = _Stream.Position; + _Stream.WriteIndex(_CompactIndex2); + + _CompactIndexPosition3 = _Stream.Position; + _Stream.WriteIndex(_CompactIndex3); + + _StringPosition = _Stream.Position; + _Stream.WriteString(_String); + + _ColorPosition = _Stream.Position; + _Stream.WriteStruct(ref _Color); + } + + [Benchmark] + public void ReadCompactIndex1() + { + _Stream.Position = _CompactIndexPosition1; + _CompactIndex1 = _Stream.ReadIndex(); + } + + [Benchmark] + public void WriteCompactIndex1() + { + _Stream.Position = _CompactIndexPosition1; + _Stream.WriteIndex(_CompactIndex1); + } + + [Benchmark] + public void ReadCompactIndex2() + { + _Stream.Position = _CompactIndexPosition2; + _CompactIndex2 = _Stream.ReadIndex(); + } + + [Benchmark] + public void WriteCompactIndex2() + { + _Stream.Position = _CompactIndexPosition2; + _Stream.WriteIndex(_CompactIndex2); + } + + [Benchmark] + public void ReadCompactIndex3() + { + _Stream.Position = _CompactIndexPosition3; + _CompactIndex3 = _Stream.ReadIndex(); + } + + [Benchmark] + public void WriteCompactIndex3() + { + _Stream.Position = _CompactIndexPosition3; + _Stream.WriteIndex(_CompactIndex3); + } + + [Benchmark] + public void ReadString() + { + _Stream.Position = _StringPosition; + _String = _Stream.ReadString(); + } + + [Benchmark] + public void WriteString() + { + _Stream.Position = _StringPosition; + _Stream.WriteString(_String); + } + + [Benchmark] + public void ReadColor() + { + _Stream.Position = _ColorPosition; + _Stream.ReadStruct(_Color); + } + + [Benchmark] + public void WriteColor() + { + _Stream.Position = _ColorPosition; + _Stream.WriteStruct(ref _Color); } - - /// - /// Verify that ReadAtomicStruct is indeed performing its purpose :) - /// + + // So far marshal is faster! But writing is still slower + // | Method | Mean | Error | StdDev | + // |------------------- |---------:|---------:|---------:| + // | ReadColor | 38.17 ns | 0.781 ns | 0.767 ns | + // | ReadColorMarshal | 16.06 ns | 0.344 ns | 0.545 ns | + [Benchmark] + public void ReadColorMarshal() + { + _Stream.Position = _ColorPosition; + _Stream.ReadStructMarshal(out _Color); + } + [Benchmark] - public void ReadAtomicStruct() + public void WriteColorMarshal() { - var stream = _Stream; - stream.Seek(0, SeekOrigin.Begin); - stream.ReadAtomicStruct(out UColor color); + _Stream.Position = _ColorPosition; + _Stream.WriteStructMarshal(ref _Color); } } } diff --git a/Benchmark/UnrealTestStream.cs b/Benchmark/UnrealTestStream.cs index 2a842280..8ab532c7 100644 --- a/Benchmark/UnrealTestStream.cs +++ b/Benchmark/UnrealTestStream.cs @@ -1,34 +1,52 @@ using System; using System.IO; using UELib; +using UELib.Branch; using UELib.Core; +using UELib.Decoding; namespace Eliot.UELib.Benchmark { - /// Hackish workaround for the issue with UPackageStream requiring a file and path, so that we can perform stream tests without a package. - public class UnrealTestStream : UnrealReader, IUnrealStream + public class UnrealTestArchive : IUnrealArchive { - public UnrealTestStream(IUnrealArchive archive, Stream baseStream) : base(archive, baseStream) + public UnrealTestArchive(UnrealPackage package, uint version, uint licenseeVersion = 0, uint ue4Version = 0, bool bigEndianCode = false) { - _Archive = archive; + Package = package; + Version = version; + LicenseeVersion = licenseeVersion; + UE4Version = ue4Version; + BigEndianCode = bigEndianCode; } - public UnrealPackage Package => _Archive.Package; - public UnrealReader UR => this; + public UnrealPackage Package { get; } + public uint Version { get; } + public uint LicenseeVersion { get; } + public uint UE4Version { get; } + public bool BigEndianCode { get; } + } + + /// Hackish workaround for the issue with UPackageStream requiring a file and path, so that we can perform stream tests without a package. + public class UnrealTestStream : UnrealReader, IUnrealStream + { + public uint Version => Archive.Version; + public uint LicenseeVersion => Archive.LicenseeVersion; + public uint UE4Version => Archive.UE4Version; + public bool BigEndianCode => Archive.BigEndianCode; + + public UnrealPackage Package => Archive.Package; + public UnrealReader UR { get; } public UnrealWriter UW { get; } - public uint Version => _Archive.Version; - public string ReadASCIIString() - { - throw new NotImplementedException(); - } + public IBufferDecoder Decoder { get; set; } + public IPackageSerializer Serializer { get; set; } - public int ReadObjectIndex() + public UnrealTestStream(IUnrealArchive archive, Stream baseStream) : base(archive, baseStream) { - throw new NotImplementedException(); + UR = new UnrealReader(archive, baseStream); + UW = new UnrealWriter(archive, baseStream); } - public UObject ReadObject() + public int ReadObjectIndex() { throw new NotImplementedException(); } @@ -38,12 +56,12 @@ public UObject ParseObject(int index) throw new NotImplementedException(); } - public int ReadNameIndex() + public new int ReadNameIndex() { throw new NotImplementedException(); } - public int ReadNameIndex(out int num) + public new int ReadNameIndex(out int num) { throw new NotImplementedException(); } @@ -53,27 +71,21 @@ public string ParseName(int index) throw new NotImplementedException(); } - public float ReadFloat() - { - throw new NotImplementedException(); - } - public void Skip(int bytes) { throw new NotImplementedException(); } - public long Length => BaseStream.Length; public long Position { get => BaseStream.Position; set => BaseStream.Position = value; } - public long LastPosition + public long AbsolutePosition { - get => _Archive.LastPosition; - set => _Archive.LastPosition = value; + get => Position; + set => Position = value; } public long Seek(long offset, SeekOrigin origin) @@ -81,4 +93,4 @@ public long Seek(long offset, SeekOrigin origin) return BaseStream.Seek(offset, origin); } } -} \ No newline at end of file +} diff --git a/CHANGELOG.md b/CHANGELOG.md index 1893b893..e68f3a0b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,13 +1,62 @@ +# [1.4.0](https://github.com/EliotVU/Unreal-Library/releases/tag/1.4.0) + +Notable changes that affect UnrealScript output: + +* Improved decompilation output of string and decimal literals. +* 5141285c Improved decompilation of delegate assignments (in a T3D context) +* 6d889c87 Added decompilation of optional parameter assignments e.g. `function MyFunction(option bool A = true);`. +* e55cfce0 Fixed decompilation with arrays of bools + +Notable changes that affect support of games: + +General deserialization fixes that affect all of UE1, UE2, and UE3 builds, as well as more specifically: + +* 13460cca Support for Battleborn +* 4aff61fa Support for Duke Nukem Forever (2011) +* bce38c4f Support for Tom Clancy's Splinter Cell +* 809edaad Support for Harry Potter (UE1) data class {USound} +* b3e1489d Support for Devastation (UE2, 2003) +* 4780771a Support for Clive Barker's Undying (UE1) data classes {UClass, UTextBuffer, UPalette, USound} +* 01772a83 Support for Lemony Snicket's A Series of Unfortunate Events data class {UProperty} +* c4c1978d Fixed support for Dungeon Defenders 2 (versions 687-688/111-117) +* 86538e5d Fixed support for Vanguard: Saga of Heroes +* eb82dba5 Fixed support for Rocket League (version 868/003) +* 6ed6ed74 Fixed support for Hawken (version 860/002) +* b4b79773 Fixed ResizeStringToken for UE1 builds +* 3653f8e1 Fixed ReturnToken and BeginFunctionToken for UE1 builds (with a package version of 61) +* 9a659549 Fixed deserialization of Heritages for UE1 builds (with a package version older than 68) + +Notable changes that affect various data structures: + +* Improved detection of UComponent objects and class types. +* ea3c1aa5 Support for UE4 .uasset packages (earlier builds only) +* e37b8a12 Support for class {UTexture}, f1b74af1 {UPrimitive, UTexture2D and its derivatives} (UE3) +* aa5ca861 Support for classes: {UFont, UMultiFont} +* ab290b6c Support for types {UPolys, FPoly} +* 02bea77b Support for types {FUntypedBulkData} (UE3) and {TLazyArray} (UE1, UE2) +* 94e02927 Support for structures: {FPointRegion, FCoords, FPlane, FScale, FSphere, FRotator, FVector, FGuid, FBox, FLinearColor, FMatrix, FQuat, FRange, FRangeVector, FVector2D, FVector4} +* 09c76240 Support for class {USoundGroup} (UE2.5) + +**Support for the data types listed above have only been implemented for the standard structure that Epic Games uses** + # [1.3.1](https://github.com/EliotVU/Unreal-Library/releases/tag/1.3.1) +Notable changes back-ported from 'develop' version 1.4.0: + +* Added support for various data structures: FColor; and data class UBitmapMaterial (UE2) + * Improved support for Batman series * Improved support for Transformers series -* Fixed the decompilation of UnrealScript casting byte-codes that were swapped i.e. `InterfaceToBool` with `InterfaceToObject`. -* Fixed a missing package version check in UStateFrame (this affected some object classes that are usually found in map package files). -* Added the capability to override the interpreted version for packages of builds that are auto-detected. +* e8308284 Fixed decompilation of primitive castings for UE1 builds +* ffaca763 Fixed decompilation of interface castings i.e. `InterfaceToBool` with `InterfaceToObject` (UE3). +* 3317e06a Fixed a missing package version check in UStateFrame (this affected some object classes that are usually found in map package files). + +* 42783b16 Added the capability to override the interpreted version for packages of builds that are auto-detected. # [1.3.0](https://github.com/EliotVU/Unreal-Library/releases/tag/1.3.0.0) +Notable changes: + * Support for Vengeance which includes BioShock 1 & 2, Swat4, and Tribes: Vengeance * Support for Batman series (to the release branch, incomplete) * Support for Thief: Deadly Shadows and Deus Ex: Invisible War diff --git a/CLI/App.config b/CLI/App.config index 4bfa0056..bcb2ae2d 100644 --- a/CLI/App.config +++ b/CLI/App.config @@ -1,6 +1,14 @@ - + - + + + + + + + + + diff --git a/CLI/Program.cs b/CLI/Program.cs index 44b61c85..3ac2880f 100644 --- a/CLI/Program.cs +++ b/CLI/Program.cs @@ -59,7 +59,7 @@ private static void Main(string[] args) break; } - Console.WriteLine($"Object:{obj.GetOuterGroup()}"); + Console.WriteLine($"Object:{obj.GetPath()}"); break; } @@ -69,12 +69,12 @@ private static void Main(string[] args) if (classLimitor == null) { foreach (var obj in pkg.Objects) - Console.WriteLine($"{obj.GetClassName()}'{obj.GetOuterGroup()}'"); + Console.WriteLine(obj.GetReferencePath()); break; } foreach (var obj in pkg.Objects.Where(e => e.Class?.Name == classLimitor)) - Console.WriteLine($"{obj.GetClassName()}'{obj.GetOuterGroup()}'"); + Console.WriteLine(obj.GetReferencePath()); break; } @@ -108,7 +108,7 @@ private static void Main(string[] args) if (defaultProperty == null) { Console.Error.WriteLine("Object '{0}' has no property of the name '{1}'", - obj.GetOuterGroup(), propertyName); + obj.GetPath(), propertyName); break; } diff --git a/README.md b/README.md index e0d01ba8..b1167fc4 100644 --- a/README.md +++ b/README.md @@ -18,9 +18,10 @@ Classes such as UStruct, UState, UClass, and UFunction contain the UnrealScript 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: -``` +``` cmd Install-Package Eliot.UELib ``` * NuGet: @@ -72,14 +73,16 @@ This is a table of games that are confirmed to be compatible with the current st | Disney's Brother Bear | 433 | 80/000 | [Link](https://github.com/metallicafan212/HarryPotterUnrealWiki/wiki/Main-Resources#other-kw-games) | | Mobile Forces | 436 | 81-83/000, 69 | | | Clive Barker's Undying | 420 | 72-85/000 | Licensee modifications are supported in the "develop" branch. Versions 72 to 83 are not auto detected. | -| Deus Ex: Invisible War | 777:Flesh | 95/069 | LinkedData not supported | | Thief: Deadly Shadows | 777:Flesh | 95/133 | LinkedData not supported | +| Deus Ex: Invisible War | 777:Flesh | 95/069 | LinkedData not supported | | | | | | | | | | | | XIII | 829 | 100/058 | | | Postal 2: Paradise Lost | 1417 | 118/002 | | +| Tom Clancy's Splinter Cell | 829 | 100/017 | | | 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 | | | Unreal II: The Awakening | 829-2001 | 126/2609 | | | Unreal II: eXpanded MultiPlayer | 2226 | 126/000 | Custom features are not decompiled | | Unreal Tournament 2004 | 3120-3369 | 128/029 | | @@ -88,6 +91,7 @@ This is a table of games that are confirmed to be compatible with the current st | Red Orchestra: Ostfront 41-45 | 3323-3369 | 128/029 | | | Killing Floor | 3369 | 128/029 | | | Battle Territory: Battery | 3369 | 128/029? | | +| Vanguard: Saga of Heroes | Unknown | 129/035 | | | Harry Potter and the Prisoner of Azkaban | 2226 | 129/000 | [Link](https://github.com/metallicafan212/HarryPotterUnrealWiki/wiki/Main-Resources#hp3) | | Shrek 2 | 2226 | 129 | | | Shark Tale | 2226 | 129/003 | | @@ -98,6 +102,7 @@ This is a table of games that are confirmed to be compatible with the current st | 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 | | +| Duke Nukem Forever (2011) | Unknown | 156/036 | [Extraction is required](https://github.com/DaZombieKiller/MegaPackageExtractor) | | | | | | | | | | | | Roboblitz | 2306 | 369/006 | | @@ -165,6 +170,7 @@ This is a table of games that are confirmed to be compatible with the current st | Painkiller HD | 9953 | 860/000 | | | Chivalry: Medieval Warfare | 10246 | 860/000 | | | Hawken | 10681 | 860/004 | /002 is not auto-detected | +| Rocket League | 10897 | 867/009 (868/032 has not been tested) | [Decryption required](https://github.com/AltimorTASDK/RLUPKTool) | | Styx: Master of Shadows | 10499 | 860/004 | | | Batman: Arkham Knight | | 863/32995 | Not verified | | Guilty Gear Xrd | 10246 | 868/003 | [Decryption required](https://github.com/gdkchan/GGXrdRevelatorDec) | diff --git a/Test/Eliot.UELib.Test.csproj b/Test/Eliot.UELib.Test.csproj index 3f7cd046..90034400 100644 --- a/Test/Eliot.UELib.Test.csproj +++ b/Test/Eliot.UELib.Test.csproj @@ -25,10 +25,10 @@ - - - - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Test/Packages.Designer.cs b/Test/Packages.Designer.cs index fc04bf26..22670303 100644 --- a/Test/Packages.Designer.cs +++ b/Test/Packages.Designer.cs @@ -69,15 +69,6 @@ public class Packages { } } - /// - /// Looks up a localized string similar to D:\Downloads\AAA-2.6-Linux-All\System. - /// - public static string Packages_Path_AAA_2_6 { - get { - return ResourceManager.GetString("Packages_Path_AAA_2_6", resourceCulture); - } - } - /// /// Looks up a localized resource of type System.Byte[]. /// @@ -97,5 +88,23 @@ public class Packages { return ((byte[])(obj)); } } + + /// + /// Looks up a localized string similar to C:\UT2004\Maps\Stock. + /// + public static string UE2MapFilesPath { + get { + return ResourceManager.GetString("UE2MapFilesPath", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to C:\UT2004\Textures\Stock. + /// + public static string UE2MaterialFilesPath { + get { + return ResourceManager.GetString("UE2MaterialFilesPath", resourceCulture); + } + } } } diff --git a/Test/Packages.resx b/Test/Packages.resx index d493b0b1..9c516019 100644 --- a/Test/Packages.resx +++ b/Test/Packages.resx @@ -121,9 +121,6 @@ D:\Projecten\UE Explorer Tests\Supported Path to directory with all known .upk files that are compatible (no errors on load). - - D:\Downloads\AAA-2.6-Linux-All\System - upk\TestUC2\TestUC2.u;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 @@ -131,4 +128,10 @@ upk\testuc3\testuc3.u;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + C:\UT2004\Maps\Stock + + + C:\UT2004\Textures\Stock + \ No newline at end of file diff --git a/Test/UnrealFlagsTests.cs b/Test/UnrealFlagsTests.cs new file mode 100644 index 00000000..341c721a --- /dev/null +++ b/Test/UnrealFlagsTests.cs @@ -0,0 +1,48 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using UELib.Branch; +using UELib.Core; +using UELib.Flags; + +namespace Eliot.UELib.Test +{ + /// + /// Test if the mechanics of are working properly. + /// + /// We do not test if the mappings are correct for UE1-3 here, mere a verification of the mapping approach. + /// + [TestClass] + public class UnrealFlagsTests + { + [TestMethod] + public void TestUnrealPackageFlags() + { + const ulong serializedFlags = + (ulong)(DefaultEngineBranch.PackageFlagsDefault.AllowDownload | + DefaultEngineBranch.PackageFlagsDefault.ServerSideOnly); + + var flagsMap = new ulong[(int)PackageFlags.Max]; + flagsMap[(int)PackageFlags.AllowDownload] = (ulong)DefaultEngineBranch.PackageFlagsDefault.AllowDownload; + flagsMap[(int)PackageFlags.ServerSideOnly] = (ulong)DefaultEngineBranch.PackageFlagsDefault.ServerSideOnly; + var flags = new UnrealFlags(serializedFlags, ref flagsMap); + + // Verify mapped flags + Assert.IsTrue(flags.HasFlag(PackageFlags.AllowDownload)); + Assert.IsTrue(flags.HasFlag(PackageFlags.ServerSideOnly)); + Assert.IsFalse(flags.HasFlag(PackageFlags.ClientOptional)); + + // Verify actual flags + Assert.IsTrue(flags.HasFlags((uint)DefaultEngineBranch.PackageFlagsDefault.AllowDownload)); + Assert.IsFalse(flags.HasFlags((uint)DefaultEngineBranch.PackageFlagsDefault.ClientOptional)); + } + + [TestMethod] + public void TestBulkDataToCompressionFlags() + { + const BulkDataFlags dataFlags = BulkDataFlags.Unused | BulkDataFlags.CompressedLZX; + + var compressionFlags = dataFlags.ToCompressionFlags(); + Assert.IsTrue(compressionFlags.HasFlag(CompressionFlags.ZLX)); + Assert.IsTrue(compressionFlags == CompressionFlags.ZLX); + } + } +} diff --git a/Test/UnrealPackageTests.cs b/Test/UnrealPackageTests.cs index 3911451b..e21d0cef 100644 --- a/Test/UnrealPackageTests.cs +++ b/Test/UnrealPackageTests.cs @@ -1,79 +1,126 @@ -using System.IO; -using System.Reflection; +using System; +using System.Collections.Generic; +using System.Linq; using Microsoft.VisualStudio.TestTools.UnitTesting; +using UELib; using UELib.Core; -namespace UELib.Test +namespace Eliot.UELib.Test { - /// - /// The following tests requires UELib to be built without WinForms dependencies. - /// You will have to change your active solution configuration to Test in order to run the tests without WinForms. - /// [TestClass] public class UnrealPackageTests { - [TestMethod] - public void LoadPackageTest() + internal static void AssertTestClass(UnrealPackage linker) { - string testPackageUC2Path = Path.Join(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "upk", - "TestUC2", "TestUC2.u"); - var packageUC2 = UnrealLoader.LoadPackage(testPackageUC2Path); - Assert.IsNotNull(packageUC2); - AssertTestPackage(packageUC2); - AssertDefaultPropertiesClass(packageUC2); - - string testPackageUC3Path = Path.Join(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "upk", - "TestUC3", "TestUC3.u"); - var packageUC3 = UnrealLoader.LoadPackage(testPackageUC3Path); - Assert.IsNotNull(packageUC3); - AssertTestPackage(packageUC3); - } - - private static void AssertTestPackage(UnrealPackage package) - { - package.InitializePackage(); - - AssertTestClass(package); - } - - private static void AssertTestClass(UnrealPackage package) - { - var testClass = package.FindObject("Test"); + var testClass = linker.FindObject("Test"); Assert.IsNotNull(testClass); // Validate that Public/Protected/Private are correct and distinguishable. - var publicProperty = package.FindObject("Public"); + var publicProperty = linker.FindObject("Public"); Assert.IsNotNull(publicProperty); Assert.IsTrue(publicProperty.IsPublic()); Assert.IsFalse(publicProperty.IsProtected()); Assert.IsFalse(publicProperty.IsPrivate()); - var protectedProperty = package.FindObject("Protected"); + var protectedProperty = linker.FindObject("Protected"); Assert.IsNotNull(protectedProperty); Assert.IsTrue(protectedProperty.IsPublic()); Assert.IsTrue(protectedProperty.IsProtected()); Assert.IsFalse(protectedProperty.IsPrivate()); - var privateProperty = package.FindObject("Private"); + var privateProperty = linker.FindObject("Private"); Assert.IsNotNull(privateProperty); Assert.IsFalse(privateProperty.IsPublic()); Assert.IsFalse(privateProperty.IsProtected()); Assert.IsTrue(privateProperty.IsPrivate()); } - private static void AssertDefaultPropertiesClass(UnrealPackage package) + internal static void AssertScriptDecompile(UStruct scriptInstance) { - var testClass = package.FindObject("DefaultProperties"); - Assert.IsNotNull(testClass); + if (scriptInstance.ByteCodeManager != null) + { + try + { + scriptInstance.ByteCodeManager.Decompile(); + } + catch (Exception ex) + { + Assert.Fail($"Token decompilation exception in script instance {scriptInstance.GetReferencePath()}: {ex.Message}"); + } + } - Assert.IsNotNull(testClass.Properties); - string stringValue = testClass.Properties.Find("String").DeserializeValue(); - Assert.AreEqual("\"String_\\\"\\\\0abfnrtv\"", stringValue); + foreach (var subScriptInstance in scriptInstance + .EnumerateFields() + .OfType()) + { + if (subScriptInstance.ByteCodeManager == null) continue; + + try + { + subScriptInstance.ByteCodeManager.Decompile(); + // ... for states + AssertScriptDecompile(subScriptInstance); + } + catch (Exception ex) + { + Assert.Fail($"Token decompilation exception in script instance {subScriptInstance.GetReferencePath()}: {ex.Message}"); + } + } + } + + internal static UObject AssertDefaultPropertiesClass(UnrealPackage linker) + { + var testClass = linker.FindObject("DefaultProperties"); + Assert.IsNotNull(testClass); + + var defaults = testClass.Default ?? testClass; + defaults.BeginDeserializing(); + Assert.IsNotNull(defaults.Properties); + + return defaults; + } - Assert.IsNotNull(testClass.Properties); - string floatValue = testClass.Properties.Find("Float").DeserializeValue(); - // 0.0123456789 in its compiled form - Assert.AreEqual("0.012345679", floatValue); + internal static void AssertPropertyTagFormat(UObject obj, string tagName, string expectedFormat) + { + Assert.IsNotNull(obj.Properties); + var tag = obj.Properties.Find(tagName); + Assert.IsNotNull(tag, $"Couldn't find property tag of '{tagName}'"); + string colorValue = tag.DeserializeValue(); + Assert.AreEqual(expectedFormat, colorValue, $"tag '{tagName}'"); + } + + internal static void AssertExportsOfType(IEnumerable exports) + where T : UObject + { + var textures = exports.OfType() + .ToList(); + Assert.IsTrue(textures.Any()); + textures.ForEach(AssertObjectDeserialization); + } + + internal static void AssertObjectDeserialization(UObject obj) + { + obj.BeginDeserializing(); + Assert.IsTrue(obj.DeserializationState == UObject.ObjectState.Deserialied, obj.GetReferencePath()); + } + + internal static void AssertTokenType(UStruct.UByteCodeDecompiler.Token token) + where T : UStruct.UByteCodeDecompiler.Token + { + Assert.AreEqual(typeof(T), token.GetType()); + } + + internal static void AssertTokenType(UStruct.UByteCodeDecompiler.Token token, Type tokenType) + { + Assert.AreEqual(tokenType, token.GetType()); + } + + internal static void AssertTokens(UStruct.UByteCodeDecompiler script, params Type[] tokenTypesSequence) + { + foreach (var tokenType in tokenTypesSequence) + { + AssertTokenType(script.NextToken, tokenType); + } } } -} \ No newline at end of file +} diff --git a/Test/UnrealStreamTests.cs b/Test/UnrealStreamTests.cs index ee660106..6082e92f 100644 --- a/Test/UnrealStreamTests.cs +++ b/Test/UnrealStreamTests.cs @@ -1,11 +1,13 @@ using System; -using Microsoft.VisualStudio.TestTools.UnitTesting; using System.IO; -using System.Reflection; using System.Text; -using UELib.Core.Types; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using UELib; +using UELib.Branch; +using UELib.Core; +using UELib.Engine; -namespace UELib.Test +namespace Eliot.UELib.Test { [TestClass] public class UnrealStreamTests @@ -13,68 +15,217 @@ public class UnrealStreamTests // HACK: Ugly workaround the issues with UPackageStream private static UPackageStream CreateTempStream() { - string tempFilePath = Path.Join(Assembly.GetExecutingAssembly().Location, "../test.u"); + string tempFilePath = Path.Join(Path.GetTempFileName()); File.WriteAllBytes(tempFilePath, BitConverter.GetBytes(UnrealPackage.Signature)); var stream = new UPackageStream(tempFilePath, FileMode.Open, FileAccess.ReadWrite); return stream; } - - [TestMethod] - public void ReadString() + + [DataTestMethod] + [DataRow(PackageObjectLegacyVersion.Undefined, 1, +0b0000000000000000000000100001)] + [DataRow(PackageObjectLegacyVersion.Undefined, 1, -0b0000000000000000000000100001)] + [DataRow(PackageObjectLegacyVersion.Undefined, 2, +0b0000000000000001000001100001)] + [DataRow(PackageObjectLegacyVersion.Undefined, 3, +0b0000000010000011000001100001)] + [DataRow(PackageObjectLegacyVersion.Undefined, 4, +0b0100000110000011000001100001)] + [DataRow(PackageObjectLegacyVersion.Undefined, 5, +0b1100000110000011000001100001)] + [DataRow(PackageObjectLegacyVersion.CompactIndexDeprecated, 4, int.MaxValue)] + public void SerializeCompactIndex(PackageObjectLegacyVersion version, int count, int compactIndex) { using var stream = CreateTempStream(); using var linker = new UnrealPackage(stream); - // The easiest version to test against. - linker.Version = 300; - using var writer = new BinaryWriter(stream); + linker.Build = new UnrealPackage.GameBuild(linker); + linker.Summary = new UnrealPackage.PackageFileSummary + { + Version = (uint)version + }; + + using var writer = new UnrealWriter(stream, stream); // Skip past the signature writer.Seek(sizeof(int), SeekOrigin.Begin); - const string rawUtf8String = "String"; - byte[] utf8StringBytes = Encoding.UTF8.GetBytes(rawUtf8String); - writer.Write(rawUtf8String.Length + 1); - writer.Write(utf8StringBytes); - writer.Write((byte)'\0'); + writer.WriteIndex(compactIndex); - const string rawUnicodeString = "语言处理"; - byte[] unicodeStringBytes = Encoding.Unicode.GetBytes(rawUnicodeString); - writer.Write(-(rawUnicodeString.Length + 1)); - writer.Write(unicodeStringBytes); - writer.Write((short)'\0'); + long length = stream.Position - sizeof(int); + Assert.AreEqual(count, length); - // Test our stream implementation + using var reader = new UnrealReader(stream, stream); // Skip past the signature stream.Seek(sizeof(int), SeekOrigin.Begin); - string readString = stream.ReadText(); - Assert.AreEqual(rawUtf8String, readString); + int readCompactIndex = reader.ReadIndex(); + Assert.AreEqual(compactIndex, readCompactIndex); + + long readLength = stream.Position - sizeof(int); + Assert.AreEqual(length, readLength); + } - readString = stream.ReadText(); - Assert.AreEqual(rawUnicodeString, readString); + [DataTestMethod] + [DataRow(PackageObjectLegacyVersion.Undefined, "String")] + [DataRow(PackageObjectLegacyVersion.Undefined, "语言处理")] + public void SerializeString(PackageObjectLegacyVersion version, string text) + { + using var stream = CreateTempStream(); + using var linker = new UnrealPackage(stream); + linker.Build = new UnrealPackage.GameBuild(linker); + linker.Summary = new UnrealPackage.PackageFileSummary + { + Version = (uint)version + }; + + using var writer = new UnrealWriter(stream, stream); + writer.Seek(sizeof(int), SeekOrigin.Begin); + writer.WriteString(text); + + using var reader = new UnrealReader(stream, stream); + stream.Seek(sizeof(int), SeekOrigin.Begin); + string readString = reader.ReadString(); + Assert.AreEqual(text, readString); } [TestMethod] - public void ReadAtomicStruct() + public void SerializeStruct() { using var stream = CreateTempStream(); using var linker = new UnrealPackage(stream); - // The easiest version to test against. - linker.Version = 300; + linker.Build = new UnrealPackage.GameBuild(linker); + linker.Summary = new UnrealPackage.PackageFileSummary(); + using var writer = new BinaryWriter(stream); // Skip past the signature writer.Seek(sizeof(int), SeekOrigin.Begin); - + // B, G, R, A; - var structBuffer = new byte[] { 255, 128, 64, 80 }; - writer.Write(structBuffer); + var inColor = new UColor(255, 128, 64, 80); + stream.WriteStruct(ref inColor); + Assert.AreEqual(8, stream.Position); stream.Seek(sizeof(int), SeekOrigin.Begin); - stream.ReadAtomicStruct(out var color); - Assert.AreEqual(255, color.B); - Assert.AreEqual(128, color.G); - Assert.AreEqual(64, color.R); - Assert.AreEqual(80, color.A); + stream.ReadStruct(out UColor outColor); + Assert.AreEqual(8, stream.Position); + + Assert.AreEqual(255, outColor.B); + Assert.AreEqual(128, outColor.G); + Assert.AreEqual(64, outColor.R); + Assert.AreEqual(80, outColor.A); + } + + [TestMethod] + public void SerializeStructMarshal() + { + using var stream = CreateTempStream(); + using var linker = new UnrealPackage(stream); + linker.Build = new UnrealPackage.GameBuild(linker); + linker.Summary = new UnrealPackage.PackageFileSummary(); + using var writer = new BinaryWriter(stream); + // Skip past the signature + writer.Seek(sizeof(int), SeekOrigin.Begin); + + long p1 = stream.Position; + + // B, G, R, A; + var inColor = new UColor(255, 128, 64, 80); + stream.WriteStructMarshal(ref inColor); + var inColor2 = new UColor(128, 128, 64, 80); + stream.WriteStructMarshal(ref inColor2); + var inColor3 = new UColor(64, 128, 64, 80); + stream.WriteStructMarshal(ref inColor3); + + stream.Seek(p1, SeekOrigin.Begin); + stream.ReadStructMarshal(out UColor outColor); + + // Verify order + Assert.AreEqual(255, outColor.B); + Assert.AreEqual(128, outColor.G); + Assert.AreEqual(64, outColor.R); + Assert.AreEqual(80, outColor.A); + + stream.Seek(p1, SeekOrigin.Begin); + stream.ReadArrayMarshal(out UArray colors, 3); + Assert.AreEqual(inColor, colors[0]); + Assert.AreEqual(inColor2, colors[1]); + Assert.AreEqual(inColor3, colors[2]); + } + + [DataTestMethod] + [DataRow(PackageObjectLegacyVersion.Undefined)] + [DataRow(PackageObjectLegacyVersion.LazyArraySkipCountChangedToSkipOffset)] + [DataRow(PackageObjectLegacyVersion.LazyLoaderFlagsAddedToLazyArray)] + [DataRow(PackageObjectLegacyVersion.StorageSizeAddedToLazyArray)] + //[DataRow(PackageObjectLegacyVersion.L8AddedToLazyArray)] + [DataRow(PackageObjectLegacyVersion.LazyArrayReplacedWithBulkData)] + public void SerializeBulkData(PackageObjectLegacyVersion version) + { + using var stream = CreateTempStream(); + using var linker = new UnrealPackage(stream); + linker.Build = new UnrealPackage.GameBuild(linker); + linker.Summary = new UnrealPackage.PackageFileSummary + { + Version = (uint)version + }; + + using var writer = new BinaryWriter(stream); + // Skip past the signature + writer.Seek(sizeof(int), SeekOrigin.Begin); + + byte[] rawData = Encoding.ASCII.GetBytes("LET'S PRETEND THAT THIS IS BULK DATA!"); + var bulkData = new UBulkData(0, rawData); + + long bulkPosition = ((IUnrealStream)stream).Position; + stream.Write(ref bulkData); + Assert.AreEqual(rawData.Length, bulkData.StorageSize); + + stream.Position = bulkPosition; + stream.Read(out UBulkData readBulkData); + Assert.IsNull(readBulkData.ElementData); + Assert.AreEqual(bulkData.StorageSize, readBulkData.StorageSize); + Assert.AreEqual(bulkData.StorageOffset, readBulkData.StorageOffset); + Assert.AreEqual(bulkData.ElementCount, readBulkData.ElementCount); + + readBulkData.LoadData(stream); + Assert.IsNotNull(readBulkData.ElementData); + Assert.AreEqual(bulkData.ElementData.Length, readBulkData.ElementData.Length); + } + + [DataTestMethod] + [DataRow(PackageObjectLegacyVersion.FontPagesDisplaced)] + [DataRow(PackageObjectLegacyVersion.VerticalOffsetAddedToUFont)] + public void SerializeDataTypes(PackageObjectLegacyVersion version) + { + using var stream = CreateTempStream(); + using var linker = new UnrealPackage(stream); + linker.Build = new UnrealPackage.GameBuild(linker); + linker.Summary = new UnrealPackage.PackageFileSummary + { + Version = (uint)version + }; + using var writer = new BinaryWriter(stream); + // Skip past the signature + writer.Seek(sizeof(int), SeekOrigin.Begin); + + var fontPage = new UFont.FontPage + { + Characters = new UArray + { + new UFont.FontCharacter + { + StartU = 1, + StartV = 2, + USize = 64, + VSize = 64, + TextureIndex = 0, + VerticalOffset = 0 + } + }, + Texture = null + }; + + long p1 = stream.Position; + stream.WriteStruct(ref fontPage); + stream.Seek(p1, SeekOrigin.Begin); + stream.ReadStruct(out UFont.FontPage newFontPage); + Assert.AreEqual(fontPage.Texture, newFontPage.Texture); + Assert.AreEqual(fontPage.Characters[0].GetHashCode(), newFontPage.Characters[0].GetHashCode()); } } } diff --git a/Test/upk/Builds/PackageTests.AA2.cs b/Test/upk/Builds/PackageTests.AA2.cs index bc8479fa..f1932efe 100644 --- a/Test/upk/Builds/PackageTests.AA2.cs +++ b/Test/upk/Builds/PackageTests.AA2.cs @@ -1,10 +1,12 @@ #if AA2 using System; +using System.Diagnostics; using System.IO; using System.Text; using Microsoft.VisualStudio.TestTools.UnitTesting; using UELib; -using UELib.Decoding; +using UELib.Branch.UE2.AA2; +using UELib.Core; namespace Eliot.UELib.Test.upk.Builds { @@ -12,9 +14,9 @@ namespace Eliot.UELib.Test.upk.Builds public class PackageTestsAA2 { // Testing the "Arcade" packages only - private static readonly string PackagesPath = Packages.Packages_Path_AAA_2_6; - private static readonly string NoEncryptionCorePackagePath = Path.Join(PackagesPath, "AAA_Core.u"); - private static readonly string EncryptedCorePackagePath = Path.Join(PackagesPath, "Core.u"); + private static readonly string PackagesPath = Packages.Packages_Path; + private static readonly string NoEncryptionCorePackagePath = Path.Join(PackagesPath, "(V128_032,Encrypted)AAA_2_6_Core.u"); + private static readonly string EncryptedCorePackagePath = Path.Join(PackagesPath, "(V128_032,Encrypted)AAA_2_6_Core.u"); [TestMethod] public void TestPackageAAA2_6() @@ -26,17 +28,21 @@ public void TestPackageAAA2_6() return; } - using var linker = UnrealLoader.LoadPackage(NoEncryptionCorePackagePath); + using var linker = UnrealLoader.LoadPackage(NoEncryptionCorePackagePath, UnrealPackage.GameBuild.BuildName.AA2_2_6); Assert.IsNotNull(linker); - Assert.AreEqual(UnrealPackage.GameBuild.BuildName.AA2, linker.Build.Name, "Incorrect package's build"); + Assert.AreEqual(UnrealPackage.GameBuild.BuildName.AA2_2_6, linker.Build.Name, "Incorrect package's build"); + Assert.AreEqual(typeof(EngineBranchAA2), linker.Branch.GetType(), "Incorrect package's branch"); - // Requires UELib to be built without "Forms" - //linker.InitializePackage(UnrealPackage.InitFlags.Construct | UnrealPackage.InitFlags.RegisterClasses); - //var fn = linker.FindObject("ResetScores", typeof(UFunction)); - //Debug.WriteLine($"Testing Object: {fn.Class.Name}'{fn.GetOuterGroup()}'"); - //fn.BeginDeserializing(); - //fn.Decompile(); - //Assert.IsNull(fn.ThrownException); + linker.InitializePackage(UnrealPackage.InitFlags.Construct | UnrealPackage.InitFlags.RegisterClasses); + + var funcGetItemName = linker.FindObject("GetItemName"); + Assert.IsNotNull(funcGetItemName); + + Debug.WriteLine($"Testing Object: {funcGetItemName.GetReferencePath()}"); + funcGetItemName.BeginDeserializing(); + Assert.IsNull(funcGetItemName.ThrownException, "Function 'GetItemName' had thrown an exception during its deserialization process"); + + funcGetItemName.Decompile(); } [TestMethod] @@ -49,9 +55,10 @@ public void TestPackageDecryptionAAA2_6() return; } - using var linker = UnrealLoader.LoadPackage(EncryptedCorePackagePath); + using var linker = UnrealLoader.LoadPackage(EncryptedCorePackagePath, UnrealPackage.GameBuild.BuildName.AA2_2_6); Assert.IsNotNull(linker); - Assert.AreEqual(UnrealPackage.GameBuild.BuildName.AA2, linker.Build.Name, "Incorrect package's build"); + Assert.AreEqual(UnrealPackage.GameBuild.BuildName.AA2_2_6, linker.Build.Name, "Incorrect package's build"); + Assert.AreEqual(typeof(EngineBranchAA2), linker.Branch.GetType(), "Incorrect package's branch"); } [TestMethod("AA2 Decryption of string 'None'")] diff --git a/Test/upk/Builds/PackageTests.All.cs b/Test/upk/Builds/PackageTests.All.cs index 4dbb5dd3..d8dc0b83 100644 --- a/Test/upk/Builds/PackageTests.All.cs +++ b/Test/upk/Builds/PackageTests.All.cs @@ -12,50 +12,86 @@ namespace Eliot.UELib.Test.upk.Builds [TestClass] public class PackageTestsAll { - private static readonly string PackagesPath = Packages.Packages_Path; + private static readonly string s_packagesPath = Packages.Packages_Path; + /// + /// FIXME: Beware, we are experiencing a memory leak in this chain of events. + /// [TestMethod] public void TestPackagesNoExceptions() { // Skip test if the dev is not in possess of this game. - if (!Directory.Exists(PackagesPath)) + if (!Directory.Exists(s_packagesPath)) { - Console.Error.Write($"Couldn't find packages path '{PackagesPath}'"); + Console.Error.Write($"Couldn't find packages path '{s_packagesPath}'"); return; } UnrealConfig.SuppressSignature = true; - var files = Enumerable.Concat( - Directory.GetFiles(PackagesPath, "*.u"), - Directory.GetFiles(PackagesPath, "*.upk") - ); + var files = Directory + .EnumerateFiles(s_packagesPath, "*.*", SearchOption.AllDirectories) + .Where(UnrealLoader.IsUnrealFileExtension); var exceptions = new List(); - foreach (string file in files) + foreach (string filePath in files) { - Debug.WriteLine(file); + Debug.WriteLine($"Testing: {filePath}"); try { - using var linker = UnrealLoader.LoadPackage(file); - switch (linker.Build.Name) - { - // Not yet error free - case UnrealPackage.GameBuild.BuildName.BioShock: - continue; - } - - linker.InitializePackage(); - var objWithError = linker.Objects.Find(obj => - (obj.DeserializationState & UObject.ObjectState.Errorlized) != 0); - Assert.IsNull(objWithError, objWithError?.ThrownException.Message); + TestPackageFile(filePath, exceptions); } + // Likely a loading error. catch (Exception ex) { - exceptions.Add(new NotSupportedException(file, ex)); + exceptions.Add(new NotSupportedException($"{filePath} loading exception: {ex}")); } } + Assert.IsFalse(exceptions.Any(), string.Join('\n', exceptions)); + } + + //[DataTestMethod] + //[DataRow("(V490_009,E3329,C046)GoWPC_Engine.u", UnrealPackage.GameBuild.BuildName.GoW1)] + public void TestPackageNoExceptions(string fileName, UnrealPackage.GameBuild.BuildName buildName) + { + string filePath = Path.Combine(s_packagesPath, fileName); + Debug.WriteLine($"Testing: {filePath}"); + + var exceptions = new List(); + TestPackageFile(filePath, exceptions); + + Assert.IsFalse(exceptions.Any(), string.Join('\n', exceptions)); + Debug.WriteLine($"Successfully tested package \"{filePath}\""); + } + + private static void TestPackageFile(string filePath, List exceptions) + { + UnrealConfig.SuppressSignature = true; + using var linker = UnrealLoader.LoadPackage(filePath); + + try + { + // FIXME: RegisterClasses is wasteful + linker.InitializePackage(UnrealPackage.InitFlags.Construct | + UnrealPackage.InitFlags.RegisterClasses); + } + catch (Exception ex) + { + exceptions.Add(new NotSupportedException($"{filePath} initialization exception: {ex}")); + return; + } + + exceptions.AddRange(linker.Objects + .Where(obj => !(obj is UnknownObject)) + .Select(obj => + { + if (obj.DeserializationState == 0) + { + obj.BeginDeserializing(); + } - Assert.IsTrue(exceptions.Count == 0, string.Join('\n', exceptions)); - Debug.WriteLine($"Successfully tested {files.Count()} packages"); + return obj; + }) + .Where(obj => obj.ThrownException != null) + .Select(obj => new NotSupportedException($"{filePath} object exception: {obj.ThrownException}"))); } } -} \ No newline at end of file +} diff --git a/Test/upk/TestUC2/Classes/ClassDeclarations.uc b/Test/upk/TestUC2/Classes/ClassDeclarations.uc new file mode 100644 index 00000000..694e4742 --- /dev/null +++ b/Test/upk/TestUC2/Classes/ClassDeclarations.uc @@ -0,0 +1,46 @@ +class ClassDeclarations extends Object; + +const Const1 = ""; +const Const2 = ""; + +enum Enum1 +{ + E1_Element1, + E2_Element2, +}; + +struct Struct1 +{ + const Const3 = ""; + + var enum Enum2 + { + E2_Element1, + E2_Element2, + } Var1; + + var int Var2; +}; + +var int Var3; + +delegate Delegate1(); +event Event1(); + +final function int Function1(); + +final function int Function2(int param1, int param2) +{ + const Const4 = 1; + return Const4; +} + +state State1 +{ + begin: + stop; +} + +defaultproperties +{ +} diff --git a/Test/upk/TestUC2/Classes/Consts.uc b/Test/upk/TestUC2/Classes/Consts.uc new file mode 100644 index 00000000..03de1d64 --- /dev/null +++ b/Test/upk/TestUC2/Classes/Consts.uc @@ -0,0 +1,3 @@ +class Consts extends Object; + +const StringConst = "String"; \ No newline at end of file diff --git a/Test/upk/TestUC2/Classes/DefaultProperties.uc b/Test/upk/TestUC2/Classes/DefaultProperties.uc index f0d7b791..9fa4b597 100644 --- a/Test/upk/TestUC2/Classes/DefaultProperties.uc +++ b/Test/upk/TestUC2/Classes/DefaultProperties.uc @@ -1,8 +1,21 @@ -class DefaultProperties extends Object; +class DefaultProperties extends DefaultPropertiesBase; // Primitives -var string String; +var byte Byte; +var int Int; +// var bool Bool; var float Float; +var name NameProperty; +// var string[255] String; +var string String; + +// var delegate Delegate; +var Object Object; +var Class MetaClass; + +// FixedArray +var byte ByteFixedArray[2]; +var int IntFixedArray[2]; // Structs var Guid Guid; @@ -17,15 +30,46 @@ var Color Color; var Box Box; var Matrix Matrix; +var pointer Pointer; + +delegate OnDelegate(); +private function InternalOnDelegate(); defaultproperties { + BoolTrue=true + BoolFalse=false + + Byte=255 + Int=1000 + Float=.0123456789 + NameProperty="Name" // ASCII String="String_\"\\\0\a\b\f\n\r\t\v" - Float=.0123456789 + + Object=Object'DefaultProperties' + MetaClass=Class'DefaultProperties' + + ByteFixedArray[0]=1 + IntFixedArray[0]=1 + IntFixedArray[1]=2 + Vector=(X=1.0,Y=2.0,Z=3.0) - Plane=(W=0,X=1,Y=2,Z=3) + Plane=(W=0.0,X=1.0,Y=2.0,Z=3.0) + Rotator=(Pitch=180,Yaw=90,Roll=45) + Coords=(Origin=(X=0.2,Y=0.4,Z=1.0),XAxis=(X=1.0,Y=0.0,Z=0.0),YAxis=(X=0.0,Y=1.0,Z=0.0),ZAxis=(X=0.0,Y=0.0,Z=1.0)) + Quat=(X=1.0,Y=2.0,Z=3.0,W=4.0) + Range=(Min=80.0,Max=40.0) + Scale=(Scale=(X=1.0,Y=2.0,Z=3.0),SheerRate=5.0,SheerAxis=SHEER_ZY) Color=(B=20,G=40,R=80,A=160) Box=(Min=(X=0,Y=1,Z=2),Max=(X=0,Y=2,Z=1),IsValid=1) Matrix=(XPlane=(W=0,X=1,Y=2,Z=3),YPlane=(W=4,X=5,Y=6,Z=7),ZPlane=(W=8,X=9,Y=10,Z=11),WPlane=(W=12,X=13,Y=14,Z=15)) -} \ No newline at end of file + + Pointer=+1 + +// begin object name=__DefaultProperties class=DefaultProperties +// OnDelegate=none +// end object + OnDelegate=DefaultProperties.InternalOnDelegate + //Delegate=__DefaultProperties.InternalOnDelegate +} diff --git a/Test/upk/TestUC2/Classes/DefaultPropertiesBase.uc b/Test/upk/TestUC2/Classes/DefaultPropertiesBase.uc new file mode 100644 index 00000000..42f3d614 --- /dev/null +++ b/Test/upk/TestUC2/Classes/DefaultPropertiesBase.uc @@ -0,0 +1,11 @@ +class DefaultPropertiesBase extends Object; + +// Primitives +var bool BoolTrue, BoolFalse; + +defaultproperties +{ + // Reversed to test inheritance. + BoolTrue=false + BoolFalse=true +} diff --git a/Test/upk/TestUC2/Classes/ExprTokens.uc b/Test/upk/TestUC2/Classes/ExprTokens.uc new file mode 100644 index 00000000..2193c2a0 --- /dev/null +++ b/Test/upk/TestUC2/Classes/ExprTokens.uc @@ -0,0 +1,101 @@ +class ExprTokens extends Object; + +var int InstanceInt; + +// var pointer instancePointer; + +static final preoperator bool \ ( string v ) { return true; } + +function AllCasts() +{ + local byte localByte; + local int localInt; + local bool localBool; + local float localFloat; + local string localString; + local name localName; + local Object localObject; + local Class localClass; + local Vector localVector; + local Rotator localRotator; + + // ByteToX + assert (\"ByteToInt" && int(localByte) == 0); + assert (\"ByteToBool" && bool(localByte) == false); + assert (\"ByteToFloat" && float(localByte) == 0.0); + assert (\"ByteToString" && string(localByte) == ""); + + // IntToX + assert (\"IntToByte" && byte(localInt) == 0); + assert (\"IntToBool" && bool(localInt) == false); + assert (\"IntToFloat" && float(localInt) == 0.0); + assert (\"IntToString" && string(localInt) == ""); + + // BoolToX + assert (\"BoolToByte" && byte(localBool) == 0); + assert (\"BoolToInt" && int(localBool) == 0); + assert (\"BoolToFloat" && float(localBool) == 0.0); + assert (\"BoolToString" && string(localBool) == "false"); + assert (\"BoolToButton" && button(localBool) == ""); // Alias + + // FloatToX + assert (\"FloatToByte" && byte(localFloat) == 0); + assert (\"FloatToInt" && int(localFloat) == 0); + assert (\"FloatToBool" && bool(localFloat) == false); + assert (\"FloatToString" && string(localFloat) == ""); + + // StringToX + assert (\"StringToByte" && byte(localString) == 0); + assert (\"StringToInt" && int(localString) == 0); + assert (\"StringToBool" && bool(localString) == false); + assert (\"StringToFloat" && float(localString) == 0.0); + assert (\"StringToVector" && Vector(localString) == vect(0,0,0)); + assert (\"StringToRotator" && Rotator(localString) == rot(0,0,0)); + + // NameToX + assert (\"NameToBool" && bool(localName) == true); + assert (\"NameToString" && string(localName) == ""); + + // ObjectToX + assert (\"DynamicCast" && Casts(localObject) == none); + assert (\"ObjectToString" && string(localObject) == "None"); + assert (\"ObjectToBool" && bool(localObject) == false); + + // ClassToX + assert (\"MetaCast" && Class(localClass) == none); + + // VectorToX + assert (\"VectorToBool" && bool(localVector) == false); + assert (\"VectorToRotator" && Rotator(localVector) == rot(0,0,0)); + assert (\"VectorToString" && string(localVector) == "(X=0.0,Y=0.0,Z=0.0)"); + + // RotatorToX + assert (\"RotatorToBool" && bool(localRotator) == false); + assert (\"RotatorToVector" && Vector(localRotator) == vect(0,0,0)); + assert (\"RotatorToString" && string(localRotator) == "(Pitch=0,Yaw=0,Roll=0)"); + + // This emits EX_PointerConst, however this crashes the compiler because the byte code is not serialized. + // instancePointer = 1; + + // assert (int(instancePointer) == 0); +} + +function VarTokens() +{ + local int localInt; + + assert (\"LocalVariable" && localInt == 0); + assert (\"InstanceVariable" && InstanceInt == 0); + assert (\"DefaultVariable" && default.InstanceInt == 0); +} + +delegate OnDelegate(); +private function InternalOnDelegate(); + +// OnDelegate gets internally redirected to property __OnDelegate__Delegate +function DelegateTokens() +{ + OnDelegate(); + OnDelegate = InternalOnDelegate; + OnDelegate = none; +} diff --git a/Test/upk/TestUC2/TestUC2.u b/Test/upk/TestUC2/TestUC2.u index 5f126c35..f9ae059e 100644 Binary files a/Test/upk/TestUC2/TestUC2.u and b/Test/upk/TestUC2/TestUC2.u differ diff --git a/Test/upk/TestUC3/Classes/DefaultProperties.uc b/Test/upk/TestUC3/Classes/DefaultProperties.uc index cbd6ee4e..e8baadb5 100644 --- a/Test/upk/TestUC3/Classes/DefaultProperties.uc +++ b/Test/upk/TestUC3/Classes/DefaultProperties.uc @@ -1,8 +1,21 @@ -class DefaultProperties extends Object; +class DefaultProperties extends DefaultPropertiesBase; // Primitives -var string String; +var byte Byte; +var int Int; +// var bool Bool; var float Float; +var name NameProperty; +// var string[255] String; +var string String; + +var Object Object; +var Class MetaClass; +var delegate Delegate; + +// FixedArray +var byte ByteFixedArray[2]; +var int IntFixedArray[2]; // Structs var Guid Guid; @@ -19,15 +32,37 @@ var LinearColor LinearColor; var Box Box; var Matrix Matrix; +var array BoolArray; + +delegate OnDelegate(); +private function InternalOnDelegate(); + defaultproperties { + BoolTrue=true + BoolFalse=false + + Byte=255 + Int=1000 + Float=.0123456789 + NameProperty="Name" // ASCII String="String_\"\\\0\a\b\f\n\r\t\v" - Float=.0123456789 + + Object=Object'DefaultProperties' + MetaClass=Class'DefaultProperties' + Delegate=InternalOnDelegate + + ByteFixedArray[0]=1 + IntFixedArray[0]=1 + IntFixedArray[1]=2 + Vector=(X=1.0,Y=2.0,Z=3.0) Vector4=(X=1.0,Y=2.0,Z=3.0,W=4.0) Vector2D=(X=1.0,Y=2.0) Plane=(W=0,X=1,Y=2,Z=3) + Rotator=(Pitch=180,Yaw=90,Roll=45) + Quat=(X=1.0,Y=2.0,Z=3.0,W=4.0) Color=(B=20,G=40,R=80,A=160) LinearColor=(R=0.2,G=0.4,B=0.6,A=0.8) Box={( @@ -40,5 +75,10 @@ defaultproperties YPlane=(W=4,X=5,Y=6,Z=7), ZPlane=(W=8,X=9,Y=10,Z=11), WPlane=(W=12,X=13,Y=14,Z=15) - }) -} \ No newline at end of file + )} + + BoolArray(0)=true + BoolArray(1)=false + + OnDelegate=DefaultProperties.InternalOnDelegate +} diff --git a/Test/upk/TestUC3/Classes/DefaultPropertiesBase.uc b/Test/upk/TestUC3/Classes/DefaultPropertiesBase.uc new file mode 100644 index 00000000..42f3d614 --- /dev/null +++ b/Test/upk/TestUC3/Classes/DefaultPropertiesBase.uc @@ -0,0 +1,11 @@ +class DefaultPropertiesBase extends Object; + +// Primitives +var bool BoolTrue, BoolFalse; + +defaultproperties +{ + // Reversed to test inheritance. + BoolTrue=false + BoolFalse=true +} diff --git a/Test/upk/TestUC3/Classes/ExprTokens.uc b/Test/upk/TestUC3/Classes/ExprTokens.uc new file mode 100644 index 00000000..97d7c2c2 --- /dev/null +++ b/Test/upk/TestUC3/Classes/ExprTokens.uc @@ -0,0 +1,115 @@ +class ExprTokens extends Object; + +var int InstanceInt; +var delegate InstanceDelegate; + +static final preoperator bool \ ( string v ) { return true; } + +function AllCasts() +{ + local byte localByte; + local int localInt; + local bool localBool; + local float localFloat; + local string localString; + local name localName; + local Object localObject; + local Class localClass; + local Interface localInterface; + local Vector localVector; + local Rotator localRotator; + + // ByteToX + assert (\"ByteToInt" && int(localByte) == 0); + assert (\"ByteToBool" && bool(localByte) == false); + assert (\"ByteToFloat" && float(localByte) == 0.0); + assert (\"ByteToString" && string(localByte) == ""); + + // IntToX + assert (\"IntToByte" && byte(localInt) == 0); + assert (\"IntToBool" && bool(localInt) == false); + assert (\"IntToFloat" && float(localInt) == 0.0); + assert (\"IntToString" && string(localInt) == ""); + + // BoolToX + assert (\"BoolToByte" && byte(localBool) == 0); + assert (\"BoolToInt" && int(localBool) == 0); + assert (\"BoolToFloat" && float(localBool) == 0.0); + assert (\"BoolToString" && string(localBool) == "false"); + + // FloatToX + assert (\"FloatToByte" && byte(localFloat) == 0); + assert (\"FloatToInt" && int(localFloat) == 0); + assert (\"FloatToBool" && bool(localFloat) == false); + assert (\"FloatToString" && string(localFloat) == ""); + + // StringToX + assert (\"StringToByte" && byte(localString) == 0); + assert (\"StringToInt" && int(localString) == 0); + assert (\"StringToBool" && bool(localString) == false); + assert (\"StringToFloat" && float(localString) == 0.0); + assert (\"StringToName" && name(localString) == ''); + assert (\"StringToVector" && Vector(localString) == vect(0,0,0)); + assert (\"StringToRotator" && Rotator(localString) == rot(0,0,0)); + + // NameToX + assert (\"NameToBool" && bool(localName) == true); + assert (\"NameToString" && string(localName) == ""); + + // ObjectToX + assert (\"DynamicCast" && Casts(localObject) == none); + assert (\"ObjectToInterface" && Interface(localObject) == none); + assert (\"ObjectToString" && string(localObject) == "None"); + assert (\"ObjectToBool" && bool(localObject) == false); + + // ClassToX + assert (\"MetaCast" && Class(localClass) == none); + + // VectorToX + assert (\"VectorToBool" && bool(localVector) == false); + assert (\"VectorToRotator" && Rotator(localVector) == rot(0,0,0)); + assert (\"VectorToString" && string(localVector) == "(X=0.0,Y=0.0,Z=0.0)"); + + // RotatorToX + assert (\"RotatorToBool" && bool(localRotator) == false); + assert (\"RotatorToVector" && Vector(localRotator) == vect(0,0,0)); + assert (\"RotatorToString" && string(localRotator) == "(Pitch=0,Yaw=0,Roll=0)"); + + // DelegateToX + assert (\"DelegateToString" && string(__OnDelegate__Delegate) == "None"); + + // InterfaceToX + assert (\"InterfaceToObject" && Object(localInterface) == none); + assert (\"InterfaceToString" && string(localInterface) == "None"); + assert (\"InterfaceToBool" && bool(localInterface) == false); +} + +function VarTokens() +{ + local int localInt; + + assert (\"LocalVariable" && localInt == 0); + assert (\"InstanceVariable" && InstanceInt == 0); + assert (\"DefaultVariable" && default.InstanceInt == 0); +} + +delegate OnDelegate(); +private function InternalOnDelegate(); + +// OnDelegate gets internally redirected to property __OnDelegate__Delegate +function DelegateTokens() +{ + // DelegateFunction token + OnDelegate(); + // LetDelegate token + OnDelegate = InternalOnDelegate; + OnDelegate = none; + + // DelegateEq/Ne tokens + if (OnDelegate == InstanceDelegate); + if (OnDelegate != InstanceDelegate); + // Test for DelegateFunctionEq/Ne tokens and the InstanceDelegate(InternalOnDelegate) token + if (OnDelegate == InternalOnDelegate); + if (OnDelegate != InternalOnDelegate); + if (OnDelegate == none); // Test the EmptyDelegate token +} diff --git a/Test/upk/TestUC3/TestUC3.u b/Test/upk/TestUC3/TestUC3.u index 49717842..d853f615 100644 Binary files a/Test/upk/TestUC3/TestUC3.u and b/Test/upk/TestUC3/TestUC3.u differ diff --git a/Test/upk/UE2PackageContentTests.cs b/Test/upk/UE2PackageContentTests.cs new file mode 100644 index 00000000..757e0323 --- /dev/null +++ b/Test/upk/UE2PackageContentTests.cs @@ -0,0 +1,180 @@ +using System.IO; +using System.Linq; +using System.Reflection; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using UELib; +using UELib.Core; +using UELib.Engine; +using static Eliot.UELib.Test.UnrealPackageTests; +using static UELib.Core.UStruct.UByteCodeDecompiler; + +namespace Eliot.UELib.Test.upk +{ + [TestClass] + public class UE2PackageContentTests + { + public static UnrealPackage GetScriptPackageLinker() + { + string packagePath = Path.Join(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "upk", + "TestUC2", "TestUC2.u"); + var linker = UnrealLoader.LoadPackage(packagePath); + Assert.IsNotNull(linker); + return linker; + } + + public static UnrealPackage GetMapPackageLinker(string fileName) + { + string packagePath = Path.Join(Packages.UE2MapFilesPath, fileName); + var linker = UnrealLoader.LoadPackage(packagePath); + Assert.IsNotNull(linker); + return linker; + } + + public static UnrealPackage GetMaterialPackageLinker(string fileName) + { + string packagePath = Path.Join(Packages.UE2MaterialFilesPath, fileName); + var linker = UnrealLoader.LoadPackage(packagePath); + Assert.IsNotNull(linker); + return linker; + } + + [TestMethod] + public void TestScriptContent() + { + void AssertDefaults(UnrealPackage unrealPackage) + { + var defaults = AssertDefaultPropertiesClass(unrealPackage); + AssertPropertyTagFormat(defaults, "BoolTrue", + "true"); + AssertPropertyTagFormat(defaults, "BoolFalse", + "false"); + AssertPropertyTagFormat(defaults, "Byte", + "255"); + AssertPropertyTagFormat(defaults, "Int", + "1000"); + AssertPropertyTagFormat(defaults, "Float", + "0.0123457"); + AssertPropertyTagFormat(defaults, "NameProperty", + "\"Name\""); + AssertPropertyTagFormat(defaults, "String", + "\"String_\\\"\\\\0abfnrtv\""); + AssertPropertyTagFormat(defaults, "Vector", + "(X=1.0000000,Y=2.0000000,Z=3.0000000)"); + // Not atomic! + AssertPropertyTagFormat(defaults, "Plane", + "(W=0.0000000,X=1.0000000,Y=2.0000000,Z=3.0000000)"); + AssertPropertyTagFormat(defaults, "Rotator", + "(Pitch=180,Yaw=90,Roll=45)"); + // Not atomic! + AssertPropertyTagFormat(defaults, "Coords", + "(Origin=(X=0.2000000,Y=0.4000000,Z=1.0000000)," + + "XAxis=(X=1.0000000,Y=0.0000000,Z=0.0000000)," + + "YAxis=(X=0.0000000,Y=1.0000000,Z=0.0000000)," + + "ZAxis=(X=0.0000000,Y=0.0000000,Z=1.0000000))"); + // Not atomic! + AssertPropertyTagFormat(defaults, "Quat", + "(X=1.0000000,Y=2.0000000,Z=3.0000000,W=4.0000000)"); + // Not atomic! + AssertPropertyTagFormat(defaults, "Range", + "(Min=80.0000000,Max=40.0000000)"); + // Not atomic! + AssertPropertyTagFormat(defaults, "Scale", + "(Scale=(X=1.0000000,Y=2.0000000,Z=3.0000000),SheerRate=5.0000000,SheerAxis=6)"); + AssertPropertyTagFormat(defaults, "Color", + "(R=80,G=40,B=20,A=160)"); + // Not atomic! + AssertPropertyTagFormat(defaults, "Box", + "(Min=(X=0.0000000,Y=1.0000000,Z=2.0000000)," + + "Max=(X=0.0000000,Y=2.0000000,Z=1.0000000),IsValid=1)"); + // Not atomic! + AssertPropertyTagFormat(defaults, "Matrix", + "(XPlane=(W=0.0000000,X=1.0000000,Y=2.0000000,Z=3.0000000)," + + "YPlane=(W=4.0000000,X=5.0000000,Y=6.0000000,Z=7.0000000)," + + "ZPlane=(W=8.0000000,X=9.0000000,Y=10.0000000,Z=11.0000000)," + + "WPlane=(W=12.0000000,X=13.0000000,Y=14.0000000,Z=15.0000000))"); + } + + void AssertFunctionDelegateTokens(UnrealPackage linker) + { + var delegateTokensFunc = linker.FindObject("DelegateTokens"); + delegateTokensFunc.BeginDeserializing(); + + var script = delegateTokensFunc.ByteCodeManager; + script.CurrentTokenIndex = -1; + + // OnDelegate(); + AssertTokens(script, + typeof(DelegateFunctionToken), + typeof(EndFunctionParmsToken)); + + // OnDelegate = InternalOnDelegate; + AssertTokens(script, + typeof(LetDelegateToken), + typeof(InstanceVariableToken), + typeof(DelegatePropertyToken)); + + // OnDelegate = none; + AssertTokens(script, + typeof(LetDelegateToken), + typeof(InstanceVariableToken), + typeof(DelegatePropertyToken)); + + // (return) + AssertTokens(script, + typeof(ReturnToken), + typeof(NothingToken), + typeof(EndOfScriptToken)); + + Assert.AreEqual(script.DeserializedTokens.Last(), script.CurrentToken); + } + + using var linker = GetScriptPackageLinker(); + Assert.IsNotNull(linker); + linker.InitializePackage(); + + var exports = linker.Objects + .Where(obj => (int)obj > 0) + .ToList(); + + AssertTestClass(linker); + + var tokensClass = linker.FindObject("ExprTokens"); + Assert.IsNotNull(tokensClass); + + // Test a series of expected tokens + AssertFunctionDelegateTokens(linker); + AssertScriptDecompile(tokensClass); + AssertDefaults(linker); + AssertExportsOfType(exports); + } + + [TestMethod] + public void TestMapContent() + { + using var linker = GetMapPackageLinker("DM-Rankin.ut2"); + linker.InitializePackage(); + + var exports = linker.Objects + .Where(obj => (int)obj > 0) + .ToList(); + + AssertExportsOfType(exports); + AssertExportsOfType(exports); + } + + [TestMethod] + public void TestMaterialContent() + { + using var linker = GetMaterialPackageLinker("2k4Fonts.utx"); + linker.InitializePackage(); + + var exports = linker.Objects + .Where(obj => (int)obj > 0) + .ToList(); + + AssertExportsOfType(exports); + AssertExportsOfType(exports); + AssertExportsOfType(exports); + } + } +} diff --git a/Test/upk/UE3PackageContentTests.cs b/Test/upk/UE3PackageContentTests.cs new file mode 100644 index 00000000..52add633 --- /dev/null +++ b/Test/upk/UE3PackageContentTests.cs @@ -0,0 +1,159 @@ +using System.IO; +using System.Linq; +using System.Reflection; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using UELib; +using UELib.Core; +using static Eliot.UELib.Test.UnrealPackageTests; +using static UELib.Core.UStruct.UByteCodeDecompiler; + +namespace Eliot.UELib.Test.upk +{ + [TestClass] + public class UE3PackageContentTests + { + public static UnrealPackage GetScriptPackageLinker() + { + string packagePath = Path.Join(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "upk", + "TestUC3", "TestUC3.u"); + var linker = UnrealLoader.LoadPackage(packagePath); + Assert.IsNotNull(linker); + return linker; + } + + [TestMethod] + public void TestScriptContent() + { + void AssertDefaults(UnrealPackage unrealPackage) + { + var defaults = AssertDefaultPropertiesClass(unrealPackage); + AssertPropertyTagFormat(defaults, "BoolTrue", + "true"); + AssertPropertyTagFormat(defaults, "BoolFalse", + "false"); + AssertPropertyTagFormat(defaults, "Byte", + "255"); + AssertPropertyTagFormat(defaults, "Int", + "1000"); + AssertPropertyTagFormat(defaults, "Float", + "0.0123457"); + AssertPropertyTagFormat(defaults, "NameProperty", + "\"Name\""); + //UnrealPackageTests.AssertPropertyTagFormat(defaults, "String", + // "\"String_\\\"\\\\0abf\\\\n\\\\rtv\""); + AssertPropertyTagFormat(defaults, "Vector", + "(X=1.0000000,Y=2.0000000,Z=3.0000000)"); + AssertPropertyTagFormat(defaults, "Vector4", + "(X=1.0000000,Y=2.0000000,Z=3.0000000,W=4.0000000)"); + AssertPropertyTagFormat(defaults, "Vector2D", + "(X=1.0000000,Y=2.0000000)"); + AssertPropertyTagFormat(defaults, "Rotator", + "(Pitch=180,Yaw=90,Roll=45)"); + AssertPropertyTagFormat(defaults, "Quat", + "(X=1.0000000,Y=2.0000000,Z=3.0000000,W=4.0000000)"); + AssertPropertyTagFormat(defaults, "Plane", + "(W=0.0000000,X=1.0000000,Y=2.0000000,Z=3.0000000)"); + AssertPropertyTagFormat(defaults, "Color", + "(R=80,G=40,B=20,A=160)"); + AssertPropertyTagFormat(defaults, "LinearColor", + "(R=0.2000000,G=0.4000000,B=0.6000000,A=0.8000000)"); + AssertPropertyTagFormat(defaults, "Box", + "(Min=(X=0.0000000,Y=1.0000000,Z=2.0000000)," + + "Max=(X=0.0000000,Y=2.0000000,Z=1.0000000),IsValid=1)"); + AssertPropertyTagFormat(defaults, "Matrix", + "(XPlane=(W=0.0000000,X=1.0000000,Y=2.0000000,Z=3.0000000)," + + "YPlane=(W=4.0000000,X=5.0000000,Y=6.0000000,Z=7.0000000)," + + "ZPlane=(W=8.0000000,X=9.0000000,Y=10.0000000,Z=11.0000000)," + + "WPlane=(W=12.0000000,X=13.0000000,Y=14.0000000,Z=15.0000000))"); + } + + void AssertFunctionDelegateTokens(UnrealPackage linker) + { + var delegateTokensFunc = linker.FindObject("DelegateTokens"); + delegateTokensFunc.BeginDeserializing(); + + var script = delegateTokensFunc.ByteCodeManager; + script.Deserialize(); + script.CurrentTokenIndex = -1; + + // OnDelegate(); + AssertTokens(script, + typeof(DelegateFunctionToken), + typeof(EndFunctionParmsToken)); + + // OnDelegate = InternalOnDelegate; + AssertTokens(script, + typeof(LetDelegateToken), + typeof(InstanceVariableToken), + typeof(DelegatePropertyToken)); + + // OnDelegate = none; + AssertTokens(script, + typeof(LetDelegateToken), + typeof(InstanceVariableToken), + typeof(DelegatePropertyToken)); + + // if (OnDelegate == InstanceDelegate); + AssertTokens(script, + typeof(JumpIfNotToken), + typeof(DelegateCmpEqToken), + typeof(InstanceVariableToken), + typeof(InstanceVariableToken), + typeof(EndFunctionParmsToken)); + + // if (OnDelegate != InstanceDelegate); + AssertTokens(script, + typeof(JumpIfNotToken), + typeof(DelegateCmpNeToken), + typeof(InstanceVariableToken), + typeof(InstanceVariableToken), + typeof(EndFunctionParmsToken)); + + // if (OnDelegate == InternalOnDelegate); + AssertTokens(script, + typeof(JumpIfNotToken), + typeof(DelegateFunctionCmpEqToken), + typeof(InstanceVariableToken), + typeof(InstanceDelegateToken), + typeof(EndFunctionParmsToken)); + + // if (OnDelegate != InternalOnDelegate); + AssertTokens(script, + typeof(JumpIfNotToken), + typeof(DelegateFunctionCmpNeToken), + typeof(InstanceVariableToken), + typeof(InstanceDelegateToken), + typeof(EndFunctionParmsToken)); + + // if (OnDelegate == none); + AssertTokens(script, + typeof(JumpIfNotToken), + typeof(DelegateCmpEqToken), + typeof(InstanceVariableToken), + typeof(EmptyDelegateToken), + typeof(EndFunctionParmsToken)); + + // (return) + AssertTokens(script, + typeof(ReturnToken), + typeof(NothingToken), + typeof(EndOfScriptToken)); + + Assert.AreEqual(script.DeserializedTokens.Last(), script.CurrentToken); + } + + using var linker = GetScriptPackageLinker(); + Assert.IsNotNull(linker); + linker.InitializePackage(); + AssertTestClass(linker); + + var tokensClass = linker.FindObject("ExprTokens"); + Assert.IsNotNull(tokensClass); + + // Test a series of expected tokens + AssertFunctionDelegateTokens(linker); + AssertScriptDecompile(tokensClass); + AssertDefaults(linker); + } + } +} diff --git a/src/.gitignore b/src/.gitignore new file mode 100644 index 00000000..0f621b51 --- /dev/null +++ b/src/.gitignore @@ -0,0 +1 @@ +/nuget.config diff --git a/src/BinaryMetaData.cs b/src/BinaryMetaData.cs index f290371f..afec15e6 100644 --- a/src/BinaryMetaData.cs +++ b/src/BinaryMetaData.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Diagnostics; using UELib.Annotations; @@ -18,34 +19,41 @@ public class BinaryMetaData public struct BinaryField : IUnrealDecompilable { /// - /// Name of this field. + /// The offset in bytes where this field's value was read. /// - [PublicAPI] public string Name; - + public long Offset { get; set; } + /// - /// Value of this field. + /// Name of this field. /// - [PublicAPI] public object Tag; + public string Field { get; set; } /// - /// The position in bytes where this field's value was read from. + /// Value of this field. /// - [PublicAPI] public long Position; + public object Value { get; set; } /// /// The size in bytes of this field's value. /// - [PublicAPI] public long Size; + public long Size { get; set; } + + [Obsolete("Use Field")] + public string Name => Field; + + [Obsolete("Use Value")] + public object Tag => Value; + + [Obsolete("Use Offset")] public int Position => (int)Offset; /// /// Decompiles and returns the output of @Tag. /// /// Output of @Tag or "NULL" if @Tag is null - [PublicAPI] public string Decompile() { - return Tag != null - ? $"({Tag.GetType()}) : {Tag}" + return Value != null + ? $"({Value.GetType()}) : {Value}" : "NULL"; } } @@ -53,26 +61,25 @@ public string Decompile() /// /// Stack of all deserialized fields. /// - [PublicAPI] public Stack Fields = new Stack(1); + public Stack Fields = new Stack(1); /// /// Adds a new field to the @Fields stack. /// - /// Name of the field - /// Value of the field - /// Position in bytes where the field is read from + /// Name of the field + /// Value of the field + /// Position in bytes where the field is read from /// Size in bytes of the field - [PublicAPI] - public void AddField(string name, object tag, long position, long size) + public void AddField(string field, object value, long offset, long size) { - Debug.Assert(size > 0); + //Debug.Assert(size > 0, $"Size of field {field} at {offset} cannot be less than 1"); Fields.Push ( new BinaryField { - Name = name, - Tag = tag, - Position = position, + Field = field, + Value = value, + Offset = offset, Size = size } ); diff --git a/src/Branch/BuildAttribute.cs b/src/Branch/BuildAttribute.cs new file mode 100644 index 00000000..1ac22a49 --- /dev/null +++ b/src/Branch/BuildAttribute.cs @@ -0,0 +1,65 @@ +using System; + +namespace UELib.Branch +{ + /// + /// Not yet usable. + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Property | AttributeTargets.Field)] + public class BuildAttribute : Attribute + { + public readonly UnrealPackage.GameBuild.BuildName Build; + + public BuildAttribute(UnrealPackage.GameBuild.BuildName build) + { + Build = build; + } + } + + /// + /// Not yet usable. + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Property | AttributeTargets.Field)] + public class BuildGenerationAttribute : Attribute + { + public readonly BuildGeneration Generation; + public readonly int EngineVersion = -1; + + public BuildGenerationAttribute(BuildGeneration generation) + { + Generation = generation; + } + + public BuildGenerationAttribute(BuildGeneration generation, int engineVersion) + { + Generation = generation; + EngineVersion = engineVersion; + } + } + + + /// + /// Not yet usable. + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Property | AttributeTargets.Field)] + public class BuildGenerationRangeAttribute : Attribute + { + public readonly BuildGeneration MinGeneration, MaxGeneration; + public readonly int MinEngineVersion = -1, MaxEngineVersion = -1; + + public BuildGenerationRangeAttribute(BuildGeneration minGeneration, BuildGeneration maxGeneration) + { + MinGeneration = minGeneration; + MaxGeneration = maxGeneration; + } + + public BuildGenerationRangeAttribute(BuildGeneration minGeneration, int minEngineVersion, int maxEngineVersion, BuildGeneration maxGeneration) + { + MinGeneration = minGeneration; + MaxGeneration = maxGeneration; + + MinEngineVersion = minEngineVersion; + MaxEngineVersion = maxEngineVersion; + } + } +} \ No newline at end of file diff --git a/src/Branch/BuildEngineBranchAttribute.cs b/src/Branch/BuildEngineBranchAttribute.cs new file mode 100644 index 00000000..a8a4efbc --- /dev/null +++ b/src/Branch/BuildEngineBranchAttribute.cs @@ -0,0 +1,17 @@ +using System; +using System.Diagnostics; + +namespace UELib.Branch +{ + [AttributeUsage(AttributeTargets.Field)] + public class BuildEngineBranchAttribute : Attribute + { + public readonly Type EngineBranchType; + + public BuildEngineBranchAttribute(Type engineBranchType) + { + EngineBranchType = engineBranchType; + Debug.Assert(engineBranchType != null); + } + } +} diff --git a/src/Branch/DefaultEngineBranch.cs b/src/Branch/DefaultEngineBranch.cs new file mode 100644 index 00000000..3054c87b --- /dev/null +++ b/src/Branch/DefaultEngineBranch.cs @@ -0,0 +1,499 @@ +using System; +using UELib.Branch.UE2.VG.Tokens; +using UELib.Branch.UE3.BL2.Tokens; +using UELib.Core; +using UELib.Core.Tokens; +using UELib.Flags; +using UELib.Tokens; +using static UELib.Core.UStruct.UByteCodeDecompiler; + +namespace UELib.Branch +{ + /// + /// The default EngineBranch handles UE1, 2, and 3 packages, this helps us separate the UE3 or less specific code away + /// from the implementation. + /// Note: The current implementation is incomplete, i.e. it does not map any other enum flags other than PackageFlags + /// yet. + /// Its main focus is to split UE3 and UE4 logic. + /// + public class DefaultEngineBranch : EngineBranch + { + [Flags] + public enum PackageFlagsDefault : uint + { + /// + /// UEX: Whether clients are allowed to download the package from the server. + /// UE4: Displaced by "NewlyCreated" + /// + AllowDownload = 0x00000001U, + + /// + /// Whether clients can skip downloading the package but still able to join the server. + /// + ClientOptional = 0x00000002U, + + /// + /// Only necessary to load on the server. + /// + ServerSideOnly = 0x00000004U, + + /// + /// UE4: Displaced by "CompiledIn" + /// + Unsecure = 0x00000010U, + + Protected = 0x80000000U + } +#if TRANSFORMERS + [Flags] + public enum PackageFlagsHMS : uint + { + XmlFormat = 0x80000000U + } +#endif + [Flags] + public enum PackageFlagsUE1 : uint + { + /// + /// Whether the package has broken links. + /// Runtime-only;not-serialized + /// + [Obsolete] BrokenLinks = 0x00000008U, + + /// + /// Whether the client needs to download the package. + /// Runtime-only;not-serialized + /// + [Obsolete] Need = 0x00008000U, + + /// + /// The package is encrypted. + /// <= UT + /// + Encrypted = 0x00000020U + } + + [Flags] + public enum PackageFlagsUE2 : uint + { + // UE2.5 + Official = 0x00000020U, + } + + [Flags] + public enum PackageFlagsUE3 : uint + { + /// + /// Whether the package has been cooked. + /// + Cooked = 0x00000008U, + + /// + /// Whether the package contains a ULevel or UWorld object. + /// + ContainsMap = 0x00020000U, + + [Obsolete] Trash = 0x00040000, + + /// + /// Whether the package contains a UClass or any UnrealScript types. + /// + ContainsScript = 0x00200000U, + + /// + /// Whether the package contains debug info i.e. it was built with -Debug. + /// + ContainsDebugData = 0x00400000U, + + Imports = 0x00800000U, + + StoreCompressed = 0x02000000U, + StoreFullyCompressed = 0x04000000U, + + /// + /// Whether package has metadata exported(anything related to the editor). + /// + NoExportsData = 0x20000000U, + + /// + /// Whether the package TextBuffers' have been stripped of its content. + /// + StrippedSource = 0x40000000U + } + + public DefaultEngineBranch(BuildGeneration generation) : base(generation) + { + } + + public override void Setup(UnrealPackage linker) + { + SetupEnumPackageFlags(linker); + EnumFlagsMap.Add(typeof(PackageFlag), PackageFlags); + } + + protected virtual void SetupEnumPackageFlags(UnrealPackage linker) + { + PackageFlags[(int)Flags.PackageFlag.AllowDownload] = (uint)PackageFlagsDefault.AllowDownload; + PackageFlags[(int)Flags.PackageFlag.ClientOptional] = (uint)PackageFlagsDefault.ClientOptional; + PackageFlags[(int)Flags.PackageFlag.ServerSideOnly] = (uint)PackageFlagsDefault.ServerSideOnly; +#if UE1 + // FIXME: Version + if (linker.Version > 61 && linker.Version <= 69) // <= UT99 + PackageFlags[(int)Flags.PackageFlag.Encrypted] = (uint)PackageFlagsUE1.Encrypted; +#endif +#if UE2 + if (linker.Build == BuildGeneration.UE2_5) + PackageFlags[(int)Flags.PackageFlag.Official] = (uint)PackageFlagsUE2.Official; +#endif +#if UE3 + // Map the new PackageFlags, but the version is nothing but a guess! + if (linker.Version >= 180) + { + if (linker.Version >= UnrealPackage.PackageFileSummary.VCookerVersion) + PackageFlags[(int)Flags.PackageFlag.Cooked] = (uint)PackageFlagsUE3.Cooked; + + PackageFlags[(int)Flags.PackageFlag.ContainsMap] = (uint)PackageFlagsUE3.ContainsMap; + PackageFlags[(int)Flags.PackageFlag.ContainsDebugData] = (uint)PackageFlagsUE3.ContainsDebugData; + PackageFlags[(int)Flags.PackageFlag.ContainsScript] = (uint)PackageFlagsUE3.ContainsScript; + PackageFlags[(int)Flags.PackageFlag.StrippedSource] = (uint)PackageFlagsUE3.StrippedSource; + } +#endif + } + + protected override void SetupSerializer(UnrealPackage linker) + { + SetupSerializer(); + } + + /// + /// Builds a tokens map for UE1, 2, and 3. + /// The default byte-codes are correct for UE2 and are adjusted accordingly for UE1, and UE3. + /// + /// FYI: Any version is not actually correct, in most cases changes that have been made to the UnrealScript byte-code are not versioned. + /// -- Any version here is an approximation that works best for most packages. + /// + protected override TokenMap BuildTokenMap(UnrealPackage linker) + { + var tokenMap = new TokenMap((byte)ExprToken.ExtendedNative) + { + { 0x00, typeof(LocalVariableToken) }, + { 0x01, typeof(InstanceVariableToken) }, + { 0x02, typeof(DefaultVariableToken) }, + { 0x03, typeof(BadToken) }, + { 0x04, typeof(ReturnToken) }, + { 0x05, typeof(SwitchToken) }, + { 0x06, typeof(JumpToken) }, + { 0x07, typeof(JumpIfNotToken) }, + { 0x08, typeof(StopToken) }, + { 0x09, typeof(AssertToken) }, + { 0x0A, typeof(CaseToken) }, + { 0x0B, typeof(NothingToken) }, + { 0x0C, typeof(LabelTableToken) }, + { 0x0D, typeof(GotoLabelToken) }, + { + 0x0E, linker.Version < 62 + // Serialized but never emitted, must have been a really old expression. + ? typeof(ValidateObjectToken) + : typeof(EatStringToken) + }, + { 0x0F, typeof(LetToken) }, + { 0x10, typeof(BadToken) }, + + // Bad expr in UE1 v61 + { + 0x11, linker.Version < 62 + ? typeof(BadToken) + : typeof(NewToken) + }, + { 0x12, typeof(ClassContextToken) }, + { 0x13, typeof(MetaClassCastToken) }, + { + 0x14, linker.Version < 62 + ? typeof(BeginFunctionToken) + : typeof(LetBoolToken) + }, + { + 0x15, linker.Version < 62 + ? typeof(EndOfScriptToken) + // Attested in UE2 builds such as Unreal2 and Unreal2XMP, but not in any UE1 or UE2.5 builds, nor RS3 (UE2) + : linker.Version < (uint)PackageObjectLegacyVersion.UE3 + ? typeof(LineNumberToken) + : typeof(BadToken) + }, + { 0x16, typeof(EndFunctionParmsToken) }, + { 0x17, typeof(SelfToken) }, + { 0x18, typeof(SkipToken) }, + { 0x19, typeof(ContextToken) }, + { 0x1A, typeof(ArrayElementToken) }, + { 0x1B, typeof(VirtualFunctionToken) }, + { 0x1C, typeof(FinalFunctionToken) }, + { 0x1D, typeof(IntConstToken) }, + { 0x1E, typeof(FloatConstToken) }, + { 0x1F, typeof(StringConstToken) }, + { 0x20, typeof(ObjectConstToken) }, + { 0x21, typeof(NameConstToken) }, + { 0x22, typeof(RotationConstToken) }, + { 0x23, typeof(VectorConstToken) }, + { 0x24, typeof(ByteConstToken) }, + { 0x25, typeof(IntZeroToken) }, + { 0x26, typeof(IntOneToken) }, + { 0x27, typeof(TrueToken) }, + { 0x28, typeof(FalseToken) }, + { 0x29, typeof(NativeParameterToken) }, + { 0x2A, typeof(NoObjectToken) }, + { + 0x2B, linker.Version < (uint)PackageObjectLegacyVersion.CastStringSizeTokenDeprecated + ? typeof(ResizeStringToken) + : typeof(BadToken) + }, + { 0x2C, typeof(IntConstByteToken) }, + { 0x2D, typeof(BoolVariableToken) }, + { 0x2E, typeof(DynamicCastToken) }, + { 0x2F, typeof(IteratorToken) }, + { 0x30, typeof(IteratorPopToken) }, + { 0x31, typeof(IteratorNextToken) }, + { 0x32, typeof(StructCmpEqToken) }, + { 0x33, typeof(StructCmpNeToken) }, + { + 0x34, linker.Version < 62 + // Actually a StructConstToken but is not implemented in the VM. + ? typeof(BadToken) + : typeof(UnicodeStringConstToken) + }, + { + 0x35, linker.Version < 62 + ? typeof(BadToken) + // Defined and emitted but ignored by the VM in UE2, + // -- however some builds do serialize this token, so we'll keep it + : linker.Build == BuildGeneration.UE2 + ? typeof(RangeConstToken) + : typeof(BadToken) + }, + { 0x36, typeof(StructMemberToken) }, + { 0x37, typeof(BadToken) }, + { 0x38, typeof(GlobalFunctionToken) }, + + // PrimitiveCast:MinConversion/RotationToVector (UE1) + { 0x39, typeof(PrimitiveCastToken) }, + + // PrimitiveCast:ByteToInt (UE1) + { + 0x3A, linker.Version < (uint)PackageObjectLegacyVersion.PrimitiveCastTokenAdded + ? typeof(BadToken) // will be overridden down if UE1 + : typeof(ReturnNothingToken) + }, + + // Added with UE2 (FIXME: version) + // FIXME: Bad expr in GoW + { 0x3B, typeof(DelegateCmpEqToken) }, + // FIXME: Bad expr in GoW + { 0x3C, typeof(DelegateCmpNeToken) }, + // FIXME: Bad expr in GoW + { 0x3D, typeof(DelegateFunctionCmpEqToken) }, + // FIXME: Bad expr in GoW + { 0x3E, typeof(DelegateFunctionCmpNeToken) }, + // FIXME: Bad expr in GoW + { 0x3F, typeof(EmptyDelegateToken) }, + { 0x40, typeof(BadToken) }, + // FIXME: Valid in GoW, no bytes + { 0x41, typeof(BadToken) }, + { 0x42, typeof(BadToken) }, + { 0x43, typeof(BadToken) }, + { 0x44, typeof(BadToken) }, + { 0x45, typeof(BadToken) }, + // Unused PrimitiveCast (UE1) + { 0x46, typeof(BadToken) }, + // PrimitiveCast:ObjectToTool (UE1) + { 0x47, typeof(EndOfScriptToken) }, + // PrimitiveCast:NameToBool (UE1) + { 0x48, typeof(ConditionalToken) }, + { 0x49, typeof(BadToken) }, + { 0x4A, typeof(BadToken) }, + { 0x4B, typeof(BadToken) }, + { 0x4C, typeof(BadToken) }, + { 0x4D, typeof(BadToken) }, + { 0x4E, typeof(BadToken) }, + { 0x4F, typeof(BadToken) }, + { 0x50, typeof(BadToken) }, + { 0x51, typeof(BadToken) }, + { 0x52, typeof(BadToken) }, + { 0x53, typeof(BadToken) }, + { 0x54, typeof(BadToken) }, + { 0x55, typeof(BadToken) }, + { 0x56, typeof(BadToken) }, + { 0x57, typeof(BadToken) }, + { 0x58, typeof(BadToken) }, + { 0x59, typeof(BadToken) }, + // PrimitiveCast:MaxConversion (UE1) + { 0x5A, typeof(BadToken) }, + { 0x5B, typeof(BadToken) }, + { 0x5C, typeof(BadToken) }, + { 0x5D, typeof(BadToken) }, + { 0x5E, typeof(BadToken) }, + { 0x5F, typeof(BadToken) }, + }; + + if (linker.Version >= (uint)PackageObjectLegacyVersion.DynamicArrayTokensAdded) + { + tokenMap[0x10] = typeof(DynamicArrayElementToken); + // Added in a later engine build, but some UE1 games (or special builds) do allow this token. + tokenMap[0x37] = typeof(DynamicArrayLengthToken); + } + + if (linker.Version < (uint)PackageObjectLegacyVersion.PrimitiveCastTokenAdded) + { + DowngradePrimitiveCasts(tokenMap); + } + else + { + if (linker.Version >= (uint)PackageObjectLegacyVersion.DynamicArrayInsertTokenAdded) + { + // Beware! these will be shifted down, see UnshiftTokens3 + tokenMap[0x40] = typeof(DynamicArrayInsertToken); + tokenMap[0x41] = typeof(DynamicArrayRemoveToken); + } + + tokenMap[0x42] = typeof(DebugInfoToken); + tokenMap[0x43] = typeof(DelegateFunctionToken); + tokenMap[0x44] = typeof(DelegatePropertyToken); + tokenMap[0x45] = typeof(LetDelegateToken); + } +#if UE3 + // RangeConst was deprecated to add new tokens, and as a result all op codes past it were shifted around. + if (linker.Version >= (uint)PackageObjectLegacyVersion.RangeConstTokenDeprecated) + UnshiftTokens3(tokenMap); +#endif + // ReSharper disable once SwitchStatementMissingSomeEnumCasesNoDefault + switch (linker.Build.Name) + { +#if BORDERLANDS2 + case UnrealPackage.GameBuild.BuildName.Battleborn: + case UnrealPackage.GameBuild.BuildName.Borderlands2: + tokenMap[0x4C] = typeof(LocalVariableToken); + tokenMap[0x4D] = typeof(LocalVariableToken); + tokenMap[0x4E] = typeof(LocalVariableToken); + tokenMap[0x4F] = typeof(LocalVariableToken); + tokenMap[0x50] = typeof(LocalVariableToken); + tokenMap[0x51] = typeof(LocalVariableToken); + break; +#endif +#if BIOSHOCK + case UnrealPackage.GameBuild.BuildName.BioShock: + tokenMap[0x49] = typeof(LogFunctionToken); + break; +#endif +#if MIRRORSEDGE + case UnrealPackage.GameBuild.BuildName.MirrorsEdge: + tokenMap[0x4F] = typeof(UnresolvedToken); + break; +#endif + } + + return tokenMap; + } + + // TODO: Confirm if these are correct for UE1 + /// + /// Downgrades any UE2+ byte codes to their UE1 counterpart. + /// In UE1 primitive casts were on the same level as any other token expression. + /// In UE2 or earlier these were displaced and inlined within a new "PrimitiveCast" token. + /// + protected void DowngradePrimitiveCasts(TokenMap tokenMap) + { + var primitiveCastTokenType = typeof(PrimitiveInlineCastToken); + // Functions as the "MinConversion" (UE1) and also "RotatorToVector" + tokenMap[(byte)CastToken.RotatorToVector] = primitiveCastTokenType; + tokenMap[(byte)CastToken.ByteToInt] = primitiveCastTokenType; + tokenMap[(byte)CastToken.ByteToBool] = primitiveCastTokenType; + tokenMap[(byte)CastToken.ByteToFloat] = primitiveCastTokenType; + tokenMap[(byte)CastToken.IntToByte] = primitiveCastTokenType; + tokenMap[(byte)CastToken.IntToBool] = primitiveCastTokenType; + tokenMap[(byte)CastToken.IntToFloat] = primitiveCastTokenType; + tokenMap[(byte)CastToken.BoolToByte] = primitiveCastTokenType; + tokenMap[(byte)CastToken.BoolToInt] = primitiveCastTokenType; + tokenMap[(byte)CastToken.BoolToFloat] = primitiveCastTokenType; + tokenMap[(byte)CastToken.FloatToByte] = primitiveCastTokenType; + tokenMap[(byte)CastToken.FloatToInt] = primitiveCastTokenType; + tokenMap[(byte)CastToken.FloatToBool] = primitiveCastTokenType; + + // "StringToName" seen in some builds, otherwise a bad token but we cannot assume any version boundaries. + tokenMap[(byte)CastToken.ObjectToInterface] = primitiveCastTokenType; + + tokenMap[(byte)CastToken.ObjectToBool] = primitiveCastTokenType; + tokenMap[(byte)CastToken.NameToBool] = primitiveCastTokenType; + tokenMap[(byte)CastToken.StringToByte] = primitiveCastTokenType; + tokenMap[(byte)CastToken.StringToInt] = primitiveCastTokenType; + tokenMap[(byte)CastToken.StringToBool] = primitiveCastTokenType; + tokenMap[(byte)CastToken.StringToFloat] = primitiveCastTokenType; + tokenMap[(byte)CastToken.StringToVector] = primitiveCastTokenType; + tokenMap[(byte)CastToken.StringToRotator] = primitiveCastTokenType; + tokenMap[(byte)CastToken.VectorToBool] = primitiveCastTokenType; + tokenMap[(byte)CastToken.VectorToRotator] = primitiveCastTokenType; + tokenMap[(byte)CastToken.RotatorToBool] = primitiveCastTokenType; + tokenMap[(byte)CastToken.ByteToString] = primitiveCastTokenType; + tokenMap[(byte)CastToken.IntToString] = primitiveCastTokenType; + tokenMap[(byte)CastToken.BoolToString] = primitiveCastTokenType; + tokenMap[(byte)CastToken.FloatToString] = primitiveCastTokenType; + tokenMap[(byte)CastToken.ObjectToString] = primitiveCastTokenType; + tokenMap[(byte)CastToken.NameToString] = primitiveCastTokenType; + tokenMap[(byte)CastToken.VectorToString] = primitiveCastTokenType; + tokenMap[(byte)CastToken.RotatorToString] = primitiveCastTokenType; + + // Represents the "MaxConversion" (UE1) + // "DelegateToString" (UE2+), later deprecated + tokenMap[(byte)CastToken.DelegateToString] = typeof(BadToken); + } +#if UE3 + protected void UnshiftTokens3(TokenMap tokenMap) + { + // EatString -> EatReturnValueToken + tokenMap[0x0E] = typeof(EatReturnValueToken); + + tokenMap[0x15] = typeof(EndParmValueToken); + + tokenMap[0x35] = typeof(StructMemberToken); + tokenMap[0x36] = typeof(DynamicArrayLengthToken); + tokenMap[0x37] = typeof(GlobalFunctionToken); + tokenMap[0x38] = typeof(PrimitiveCastToken); + tokenMap[0x39] = typeof(DynamicArrayInsertToken); + + tokenMap[0x3A] = typeof(ReturnNothingToken); + + // 0x3B to 0x3F were not shifted. + + // These as well. + tokenMap[0x40] = typeof(DynamicArrayRemoveToken); + tokenMap[0x41] = typeof(DebugInfoToken); + tokenMap[0x42] = typeof(DelegateFunctionToken); + tokenMap[0x43] = typeof(DelegatePropertyToken); + tokenMap[0x44] = typeof(LetDelegateToken); + tokenMap[0x45] = typeof(ConditionalToken); + tokenMap[0x46] = typeof(DynamicArrayFindToken); + tokenMap[0x47] = typeof(DynamicArrayFindStructToken); + tokenMap[0x48] = typeof(OutVariableToken); + tokenMap[0x49] = typeof(DefaultParameterToken); + // FIXME: added post GoW + tokenMap[0x4A] = typeof(EmptyParmToken); + // FIXME: added post GoW + tokenMap[0x4B] = typeof(InstanceDelegateToken); + // Attested in GoW + tokenMap[0x50] = typeof(UndefinedVariableToken); + + tokenMap[0x51] = typeof(InterfaceContextToken); + tokenMap[0x52] = typeof(InterfaceCastToken); + tokenMap[0x53] = typeof(EndOfScriptToken); + tokenMap[0x54] = typeof(DynamicArrayAddToken); + tokenMap[0x55] = typeof(DynamicArrayAddItemToken); + tokenMap[0x56] = typeof(DynamicArrayRemoveItemToken); + tokenMap[0x57] = typeof(DynamicArrayInsertItemToken); + tokenMap[0x58] = typeof(DynamicArrayIteratorToken); + // FIXME: added post GoW + tokenMap[0x59] = typeof(DynamicArraySortToken); + + // Added with a late UDK build. + tokenMap[0x03] = typeof(StateVariableToken); + tokenMap[0x5A] = typeof(FilterEditorOnlyToken); + } +#endif + } +} diff --git a/src/Branch/DefaultPackageSerializer.cs b/src/Branch/DefaultPackageSerializer.cs new file mode 100644 index 00000000..658ed077 --- /dev/null +++ b/src/Branch/DefaultPackageSerializer.cs @@ -0,0 +1,51 @@ +namespace UELib.Branch +{ + /// + /// Simply redirects all serialize/deserialize calls to the default implementation. + /// This is useful so that we can ensure that there's always a valid + /// + /// For overriding purposes see + /// + public sealed class DefaultPackageSerializer : IPackageSerializer + { + public void Serialize(IUnrealStream stream, IUnrealSerializableClass obj) + { + obj.Serialize(stream); + } + + public void Deserialize(IUnrealStream stream, IUnrealDeserializableClass obj) + { + obj.Deserialize(stream); + } + + public void Serialize(IUnrealStream stream, UNameTableItem item) + { + item.Serialize(stream); + } + + public void Deserialize(IUnrealStream stream, UNameTableItem item) + { + item.Deserialize(stream); + } + + public void Serialize(IUnrealStream stream, UImportTableItem item) + { + item.Serialize(stream); + } + + public void Deserialize(IUnrealStream stream, UImportTableItem item) + { + item.Deserialize(stream); + } + + public void Serialize(IUnrealStream stream, UExportTableItem item) + { + item.Serialize(stream); + } + + public void Deserialize(IUnrealStream stream, UExportTableItem item) + { + item.Deserialize(stream); + } + } +} \ No newline at end of file diff --git a/src/Branch/EngineBranch.cs b/src/Branch/EngineBranch.cs new file mode 100644 index 00000000..a4977e7c --- /dev/null +++ b/src/Branch/EngineBranch.cs @@ -0,0 +1,126 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using UELib.Annotations; +using UELib.Core.Tokens; +using UELib.Decoding; +using UELib.Tokens; + +namespace UELib.Branch +{ + /// + /// EngineBranch lets you override the common serialization methods to assist with particular engine branches or generations. + /// Some games or engine generations may drift away from the main UE branch too much, therefor it is useful to separate specialized logic as much as possible. + /// + /// For UE1, 2, and 3 see . + /// For UE4 see . + /// + public abstract class EngineBranch + { + public readonly BuildGeneration Generation; + + [CanBeNull] public IBufferDecoder Decoder; + + // TODO: Re-factor this as a factory where we can retrieve the correct type-specific serializer. + public IPackageSerializer Serializer; + [CanBeNull] private TokenFactory _TokenFactory; + + /// + /// Which flag enums do we need to map? + /// See for an implementation. + /// This field is essential to + /// + public readonly Dictionary EnumFlagsMap = new Dictionary(); + + protected readonly ulong[] PackageFlags = new ulong[(int)Flags.PackageFlag.Max]; + + public EngineBranch() + { + Generation = BuildGeneration.Undefined; + } + + public EngineBranch(BuildGeneration generation) + { + Generation = generation; + } + + protected void SetupSerializer() + where T : IPackageSerializer + { + Debug.Assert(Serializer == null, $"{nameof(Serializer)} is already setup"); + Serializer = Activator.CreateInstance(); + } + + protected void SetupTokenFactory( + TokenMap tokenMap, + Dictionary nativeTokenMap, + byte extendedNative, + byte firstNative) + where T : TokenFactory + { + Debug.Assert(_TokenFactory == null, $"{nameof(_TokenFactory)} is already setup"); + _TokenFactory = + (T)Activator.CreateInstance(typeof(T), tokenMap, nativeTokenMap, extendedNative, firstNative); + } + + /// + /// Called right after the EngineBranch has been constructed. + /// + public abstract void Setup(UnrealPackage linker); + + /// + /// Provides an opportunity to swap the serializer instance based on any linker's condition. + /// + protected abstract void SetupSerializer(UnrealPackage linker); + + /// + /// Provides an opportunity to swap the token factory instance based on any linker's condition. + /// + protected virtual void SetupTokenFactory(UnrealPackage linker) + { + SetupTokenFactory( + BuildTokenMap(linker), + TokenFactory.FromPackage(linker.NTLPackage), + (byte)ExprToken.ExtendedNative, + (byte)ExprToken.FirstNative); + } + + [NotNull] + public TokenFactory GetTokenFactory(UnrealPackage linker) + { + if (_TokenFactory != null) return _TokenFactory; + SetupTokenFactory(linker); + // Sanity check for derived branches + Debug.Assert(_TokenFactory != null, "Branch.TokenFactory cannot be null"); + return _TokenFactory; + } + + protected virtual TokenMap BuildTokenMap(UnrealPackage linker) + { + return new TokenMap(); + } + + /// + /// Called right after the has been deserialized. + /// + /// + /// The open stream that deserialized the summary. + /// A reference to the deserialized summary. + public virtual void PostDeserializeSummary(UnrealPackage linker, + IUnrealStream stream, + ref UnrealPackage.PackageFileSummary summary) + { + SetupSerializer(linker); + stream.Serializer = Serializer; + } + + /// + /// Called right after the package's tables (Names, Imports, and Exports, etc) have been deserialized. + /// + /// + /// The open stream that deserialized the package's summary and tables. + public virtual void PostDeserializePackage(UnrealPackage linker, IUnrealStream stream) + { + } + } +} \ No newline at end of file diff --git a/src/Branch/PackageObjectLegacyVersion.cs b/src/Branch/PackageObjectLegacyVersion.cs new file mode 100644 index 00000000..e5fe178d --- /dev/null +++ b/src/Branch/PackageObjectLegacyVersion.cs @@ -0,0 +1,147 @@ +using System.Runtime.CompilerServices; + +namespace UELib.Branch +{ + public enum PackageObjectLegacyVersion + { + Undefined = 0, + + /// + /// This is one particular update with A LOT of general package changes. + /// + ReturnExpressionAddedToReturnToken = 62, + + SphereExtendsPlane = 62, + LazyArraySkipCountChangedToSkipOffset = 62, + + /// + /// 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, + + CharRemapAddedToUFont = 69, + + /// + /// FIXME: Unknown version. + /// + CastStringSizeTokenDeprecated = 70, + + PanUVRemovedFromPoly = 78, + + CompMipsDeprecated = 84, + + // FIXME: Version, attested as of UE2 + DynamicArrayInsertTokenAdded = 95, + + /// + /// FIXME: Version, set 95 (Deus Ex: IW) + /// + PrimitiveCastTokenAdded = 95, + + LightMapScaleAddedToPoly = 106, + + KerningAddedToUFont = 119, + FontPagesDisplaced = 122, + + // The estimated version changes that came after the latest known UE2 build. + TextureDeprecatedFromPoly = 170, + MaterialAddedToPoly = 170, + + UE3 = 178, + CompactIndexDeprecated = 178, + + // FIXME: Version, def not <= 178, found in GoW but no version check, so this approximation should do :) + TemplateDataAddedToUComponent = 200, + + DisplacedScriptPropertiesWithClassDefaultObject = 200, + + /// + /// Present in all released UE3 games (starting with RoboBlitz). + /// + /// FIXME: Unknown version. + /// + IsLocalAddedToDelegateFunctionToken = 181, + + // FIXME: Version + RangeConstTokenDeprecated = UE3, + + // FIXME: Version + FastSerializeStructs = UE3, + + // FIXME: Version + EnumTagNameAddedToBytePropertyTag = UE3, + + // 227 according to the GoW client + FixedVerticesToArrayFromPoly = 227, + + // Thanks to @https://www.gildor.org/ for reverse-engineering the lazy-loader version changes. + LazyLoaderFlagsAddedToLazyArray = 251, + StorageSizeAddedToLazyArray = 254, + L8AddedToLazyArray = 260, + LazyArrayReplacedWithBulkData = 266, + + // 267 according to the GoW client, + // -- albeit the exact nature is not clear + // -- whether if this indicates the addition of such an ObjectFlag or just the conditional test. + ClassDefaultCheckAddedToTemplateName = 267, + + ComponentGuidDeprecated = 273, + + /// + /// Some properties like SizeX, SizeY, Format have been displaced to ScriptProperties. + /// + DisplacedUTextureProperties = 297, + + // FIXME: Not attested in the GoW client, must have been before v321 + LightMapScaleRemovedFromPoly = 300, + + // FIXME: Not attested in the GoW client, must have been before v321 + ShadowMapScaleAddedToPoly = 300, + + // 321 according to the GoW client + ElementOwnerAddedToUPolys = 321, + + NetObjectsAdded = 322, + + NumberAddedToName = 343, + + // 417 according to the GoW client + LightingChannelsAddedToPoly = 417, + + // FIXME: Version, not attested in (RoboBlitz v369, but attested in GoW v490). + SkipSizeAddedToArrayFindTokenIntrinsics = 400, + + // FIXME: Version, not attested in (GoW v490) + SkipSizeAddedToArrayTokenIntrinsics = 491, + + VerticalOffsetAddedToUFont = 506, + CleanupFonts = 511, + + AddedTextureFileCacheGuidToTexture2D = 567, + + LightmassAdded = 600, + UProcBuildingReferenceAddedToPoly = 606, + + EnumNameAddedToBytePropertyTag = 633, + + LightmassExplicitEmissiveLightRadiusAdded = 636, + + // FIXME: Version + EndTokenAppendedToArrayTokenIntrinsics = 649, + LightmassShadowIndirectOnlyOptionAdded = 652, + PolyRulesetVariationTypeChangedToName = 670, + + BoolValueToByteForBoolPropertyTag = 673, + + AddedPVRTCToUTexture2D = 674, + + ProbeMaskReducedAndIgnoreMaskRemoved = 692, + ForceScriptOrderAddedToUClass = 749, + SuperReferenceMovedToUStruct = 756, + + AddedATITCToUTexture2D = 857, + AddedETCToUTexture2D = 864, + } +} diff --git a/src/Branch/PackageSerializer.cs b/src/Branch/PackageSerializer.cs new file mode 100644 index 00000000..2aa04fdf --- /dev/null +++ b/src/Branch/PackageSerializer.cs @@ -0,0 +1,21 @@ +namespace UELib.Branch +{ + /// + /// Provides a way to override the default package Serialize/Deserialize methods for a particular . + /// + public interface IPackageSerializer + { + // Not yet called + void Serialize(IUnrealStream stream, IUnrealSerializableClass obj); + + // Not yet called + void Deserialize(IUnrealStream stream, IUnrealDeserializableClass obj); + + void Serialize(IUnrealStream stream, UNameTableItem item); + void Deserialize(IUnrealStream stream, UNameTableItem item); + void Serialize(IUnrealStream stream, UImportTableItem item); + void Deserialize(IUnrealStream stream, UImportTableItem item); + void Serialize(IUnrealStream stream, UExportTableItem item); + void Deserialize(IUnrealStream stream, UExportTableItem item); + } +} \ No newline at end of file diff --git a/src/Branch/PackageSerializerBase.cs b/src/Branch/PackageSerializerBase.cs new file mode 100644 index 00000000..19c53711 --- /dev/null +++ b/src/Branch/PackageSerializerBase.cs @@ -0,0 +1,22 @@ +namespace UELib.Branch +{ + public abstract class PackageSerializerBase : IPackageSerializer + { + public virtual void Serialize(IUnrealStream stream, IUnrealSerializableClass obj) + { + obj.Serialize(stream); + } + + public virtual void Deserialize(IUnrealStream stream, IUnrealDeserializableClass obj) + { + obj.Deserialize(stream); + } + + public abstract void Serialize(IUnrealStream stream, UNameTableItem item); + public abstract void Deserialize(IUnrealStream stream, UNameTableItem item); + public abstract void Serialize(IUnrealStream stream, UImportTableItem item); + public abstract void Deserialize(IUnrealStream stream, UImportTableItem item); + public abstract void Serialize(IUnrealStream stream, UExportTableItem item); + public abstract void Deserialize(IUnrealStream stream, UExportTableItem item); + } +} \ No newline at end of file diff --git a/src/Decoding/CryptoDecoder.AA2.cs b/src/Branch/UE2/AA2/CryptoDecoder.AA2.cs similarity index 95% rename from src/Decoding/CryptoDecoder.AA2.cs rename to src/Branch/UE2/AA2/CryptoDecoder.AA2.cs index 5d3a35a6..58df0ed3 100644 --- a/src/Decoding/CryptoDecoder.AA2.cs +++ b/src/Branch/UE2/AA2/CryptoDecoder.AA2.cs @@ -1,8 +1,9 @@ using System.Runtime.CompilerServices; +using UELib.Decoding; -namespace UELib.Decoding +namespace UELib.Branch.UE2.AA2 { - // TODO: Re-implement as a BaseStream wrapper (in UELib 2.0) + // TODO: Re-implement as a BaseStream wrapper public class CryptoDecoderAA2 : IBufferDecoder { public void PreDecode(IUnrealStream stream) diff --git a/src/Branch/UE2/AA2/EngineBranch.AA2.cs b/src/Branch/UE2/AA2/EngineBranch.AA2.cs new file mode 100644 index 00000000..12118827 --- /dev/null +++ b/src/Branch/UE2/AA2/EngineBranch.AA2.cs @@ -0,0 +1,283 @@ +using static UELib.UnrealPackage; +using System.Diagnostics; +using System.IO; +using UELib.Core; +using UELib.Core.Tokens; +using static UELib.Core.UStruct.UByteCodeDecompiler; + +namespace UELib.Branch.UE2.AA2 +{ + public class EngineBranchAA2 : DefaultEngineBranch + { + public EngineBranchAA2(BuildGeneration generation) : base(BuildGeneration.AGP) + { + } + + protected override void SetupSerializer(UnrealPackage linker) + { + if (linker.LicenseeVersion < 33) + { + base.SetupSerializer(linker); + return; + } + + SetupSerializer(); + } + + protected override TokenMap BuildTokenMap(UnrealPackage linker) + { + if (linker.Build == GameBuild.BuildName.AA2_2_6) + { + var tokenMap = new TokenMap + { + { 0x00, typeof(LocalVariableToken) }, + { 0x01, typeof(InstanceVariableToken) }, + { 0x02, typeof(DefaultVariableToken) }, + { 0x03, typeof(BadToken) }, + { 0x04, typeof(JumpToken) }, + { 0x05, typeof(ReturnToken) }, + { 0x06, typeof(SwitchToken) }, + { 0x07, typeof(StopToken) }, + { 0x08, typeof(JumpIfNotToken) }, + { 0x09, typeof(NothingToken) }, + { 0x0A, typeof(LabelTableToken) }, + { 0x0B, typeof(AssertToken) }, + { 0x0C, typeof(CaseToken) }, + { 0x0D, typeof(EatStringToken) }, + { 0x0E, typeof(LetToken) }, + { 0x0F, typeof(GotoLabelToken) }, + { 0x10, typeof(DynamicArrayElementToken) }, + { 0x11, typeof(NewToken) }, + { 0x12, typeof(ClassContextToken) }, + { 0x13, typeof(MetaClassCastToken) }, + { 0x14, typeof(LetBoolToken) }, + { 0x15, typeof(EndFunctionParmsToken) }, + { 0x16, typeof(SkipToken) }, + { 0x17, typeof(BadToken) }, + { 0x18, typeof(ContextToken) }, + { 0x19, typeof(SelfToken) }, + { 0x1A, typeof(FinalFunctionToken) }, + { 0x1B, typeof(ArrayElementToken) }, + { 0x1C, typeof(IntConstToken) }, + { 0x1D, typeof(FloatConstToken) }, + { 0x1E, typeof(StringConstToken) }, + { 0x1F, typeof(VirtualFunctionToken) }, + { 0x20, typeof(IntOneToken) }, + { 0x21, typeof(VectorConstToken) }, + { 0x22, typeof(NameConstToken) }, + { 0x23, typeof(IntZeroToken) }, + { 0x24, typeof(ObjectConstToken) }, + { 0x25, typeof(ByteConstToken) }, + { 0x26, typeof(RotationConstToken) }, + { 0x27, typeof(FalseToken) }, + { 0x28, typeof(TrueToken) }, + { 0x29, typeof(NoObjectToken) }, + { 0x2A, typeof(NativeParameterToken) }, + { 0x2B, typeof(BadToken) }, + { 0x2C, typeof(BoolVariableToken) }, + { 0x2D, typeof(IteratorToken) }, + { 0x2E, typeof(IntConstByteToken) }, + { 0x2F, typeof(DynamicCastToken) }, + { 0x30, typeof(BadToken) }, + { 0x31, typeof(StructCmpNeToken) }, + { 0x32, typeof(UnicodeStringConstToken) }, + { 0x33, typeof(IteratorNextToken) }, + { 0x34, typeof(StructCmpEqToken) }, + { 0x35, typeof(IteratorPopToken) }, + { 0x36, typeof(GlobalFunctionToken) }, + { 0x37, typeof(StructMemberToken) }, + { 0x38, typeof(PrimitiveCastToken) }, + { 0x39, typeof(DynamicArrayLengthToken) }, + { 0x3A, typeof(BadToken) }, + { 0x3B, typeof(BadToken) }, + { 0x3C, typeof(BadToken) }, + { 0x3D, typeof(BadToken) }, + { 0x3E, typeof(BadToken) }, + { 0x3F, typeof(BadToken) }, + { 0x40, typeof(BadToken) }, + { 0x41, typeof(EndOfScriptToken) }, + { 0x42, typeof(DynamicArrayRemoveToken) }, + { 0x43, typeof(DynamicArrayInsertToken) }, + { 0x44, typeof(DelegateFunctionToken) }, + { 0x45, typeof(DebugInfoToken) }, + { 0x46, typeof(LetDelegateToken) }, + { 0x47, typeof(DelegatePropertyToken) }, + { 0x48, typeof(BadToken) }, + { 0x49, typeof(BadToken) }, + { 0x4A, typeof(BadToken) }, + { 0x4B, typeof(BadToken) }, + { 0x4C, typeof(BadToken) }, + { 0x4D, typeof(BadToken) }, + { 0x4E, typeof(BadToken) }, + { 0x4F, typeof(BadToken) }, + { 0x50, typeof(BadToken) }, + { 0x51, typeof(BadToken) }, + { 0x52, typeof(BadToken) }, + { 0x53, typeof(BadToken) }, + { 0x54, typeof(BadToken) }, + { 0x55, typeof(BadToken) }, + { 0x56, typeof(BadToken) }, + { 0x57, typeof(BadToken) }, + { 0x58, typeof(BadToken) }, + { 0x59, typeof(BadToken) } + }; + return tokenMap; + } + + if (linker.LicenseeVersion >= 33) + { + var tokenMap = new TokenMap + { + { 0x00, typeof(LocalVariableToken) }, + { 0x01, typeof(InstanceVariableToken) }, + { 0x02, typeof(DefaultVariableToken) }, + { 0x03, typeof(BadToken) }, + { 0x04, typeof(SwitchToken) }, + { 0x05, typeof(ClassContextToken) }, + { 0x06, typeof(JumpToken) }, + { 0x07, typeof(GotoLabelToken) }, + { 0x08, typeof(VirtualFunctionToken) }, + { 0x09, typeof(IntConstToken) }, + { 0x0A, typeof(JumpIfNotToken) }, + { 0x0B, typeof(LabelTableToken) }, + { 0x0C, typeof(FinalFunctionToken) }, + { 0x0D, typeof(EatStringToken) }, + { 0x0E, typeof(LetToken) }, + { 0x0F, typeof(StopToken) }, + { 0x10, typeof(NewToken) }, + { 0x11, typeof(ContextToken) }, + { 0x12, typeof(MetaClassCastToken) }, + { 0x13, typeof(SkipToken) }, + { 0x14, typeof(SelfToken) }, + { 0x15, typeof(ReturnToken) }, + { 0x16, typeof(EndFunctionParmsToken) }, + { 0x17, typeof(BadToken) }, + { 0x18, typeof(LetBoolToken) }, + { 0x19, typeof(DynamicArrayElementToken) }, + { 0x1A, typeof(AssertToken) }, + { 0x1B, typeof(ByteConstToken) }, + { 0x1C, typeof(NothingToken) }, + { 0x1D, typeof(DelegatePropertyToken) }, + { 0x1E, typeof(IntZeroToken) }, + { 0x1F, typeof(LetDelegateToken) }, + { 0x20, typeof(FalseToken) }, + { 0x21, typeof(ArrayElementToken) }, + { 0x22, typeof(EndOfScriptToken) }, + { 0x23, typeof(TrueToken) }, + { 0x24, typeof(BadToken) }, + { 0x25, typeof(FloatConstToken) }, + { 0x26, typeof(CaseToken) }, + { 0x27, typeof(IntOneToken) }, + { 0x28, typeof(StringConstToken) }, + { 0x29, typeof(NoObjectToken) }, + { 0x2A, typeof(NativeParameterToken) }, + { 0x2B, typeof(BadToken) }, + { 0x2C, typeof(DebugInfoToken) }, + { 0x2D, typeof(StructCmpEqToken) }, + // FIXME: Verify IteratorNext/IteratorPop? + { 0x2E, typeof(IteratorNextToken) }, + { 0x2F, typeof(DynamicArrayRemoveToken) }, + { 0x30, typeof(StructCmpNeToken) }, + { 0x31, typeof(DynamicCastToken) }, + { 0x32, typeof(IteratorToken) }, + { 0x33, typeof(IntConstByteToken) }, + { 0x34, typeof(BoolVariableToken) }, + // FIXME: Verify IteratorNext/IteratorPop? + { 0x35, typeof(IteratorPopToken) }, + { 0x36, typeof(UnicodeStringConstToken) }, + { 0x37, typeof(StructMemberToken) }, + { 0x38, typeof(BadToken) }, + { 0x39, typeof(DelegateFunctionToken) }, + { 0x3A, typeof(BadToken) }, + { 0x3B, typeof(BadToken) }, + { 0x3C, typeof(BadToken) }, + { 0x3D, typeof(BadToken) }, + { 0x3E, typeof(BadToken) }, + { 0x3F, typeof(BadToken) }, + { 0x40, typeof(ObjectConstToken) }, + { 0x41, typeof(NameConstToken) }, + { 0x42, typeof(DynamicArrayLengthToken) }, + { 0x43, typeof(DynamicArrayInsertToken) }, + { 0x44, typeof(PrimitiveCastToken) }, + { 0x45, typeof(GlobalFunctionToken) }, + { 0x46, typeof(VectorConstToken) }, + { 0x47, typeof(RotationConstToken) }, + { 0x48, typeof(BadToken) }, + { 0x49, typeof(BadToken) }, + { 0x4A, typeof(BadToken) }, + { 0x4B, typeof(BadToken) }, + { 0x4C, typeof(BadToken) }, + { 0x4D, typeof(BadToken) }, + { 0x4E, typeof(BadToken) }, + { 0x4F, typeof(BadToken) }, + { 0x50, typeof(BadToken) }, + { 0x51, typeof(BadToken) }, + { 0x52, typeof(BadToken) }, + { 0x53, typeof(BadToken) }, + { 0x54, typeof(BadToken) }, + { 0x55, typeof(BadToken) }, + { 0x56, typeof(BadToken) }, + { 0x57, typeof(BadToken) }, + { 0x58, typeof(BadToken) }, + { 0x59, typeof(BadToken) } + }; + return tokenMap; + } + + return base.BuildTokenMap(linker); + } + + public override void PostDeserializeSummary(UnrealPackage linker, + IUnrealStream stream, + ref PackageFileSummary summary) + { + base.PostDeserializeSummary(linker, stream, ref summary); + + // Note: Never true, AA2 is not a detected build for packages with LicenseeVersion 27 or less + // But we'll preserve this nonetheless + if (stream.LicenseeVersion < 19) return; + + bool isEncrypted = stream.ReadInt32() > 0; + if (isEncrypted) + { + // TODO: Use a stream wrapper instead; but this is blocked by an overly intertwined use of PackageStream. + if (stream.LicenseeVersion >= 33) + { + var decoder = new CryptoDecoderAA2(); + stream.Decoder = decoder; + } + else + { + var decoder = new CryptoDecoderWithKeyAA2(); + stream.Decoder = decoder; + + long nonePosition = summary.NameOffset; + stream.Seek(nonePosition, SeekOrigin.Begin); + byte scrambledNoneLength = stream.ReadByte(); + decoder.Key = scrambledNoneLength; + stream.Seek(nonePosition, SeekOrigin.Begin); + byte unscrambledNoneLength = stream.ReadByte(); + Debug.Assert((unscrambledNoneLength & 0x3F) == 5); + } + } + + // Always one + //int unkCount = stream.ReadInt32(); + //for (var i = 0; i < unkCount; i++) + //{ + // // All zero + // stream.Skip(24); + // // Always identical to the package's GUID + // var guid = stream.ReadGuid(); + //} + + //// Always one + //int unk2Count = stream.ReadInt32(); + //for (var i = 0; i < unk2Count; i++) + //{ + // // All zero + // stream.Skip(12); + //} + } + } +} diff --git a/src/Branch/UE2/AA2/PackageSerializer.AA2.cs b/src/Branch/UE2/AA2/PackageSerializer.AA2.cs new file mode 100644 index 00000000..3adb1e5e --- /dev/null +++ b/src/Branch/UE2/AA2/PackageSerializer.AA2.cs @@ -0,0 +1,93 @@ +using System; +using System.Diagnostics; +using UELib.Decoding; + +namespace UELib.Branch.UE2.AA2 +{ + // Only initialized for packages with LicenseeVersion >= 33 + public class PackageSerializerAA2 : PackageSerializerBase + { + private const int MaxNameLengthUE2 = 64; + + public override void Serialize(IUnrealStream stream, UNameTableItem item) + { + if (stream.Decoder is CryptoDecoderAA2) + throw new NotSupportedException("Can't serialize encrypted name entries"); + + item.Serialize(stream); + } + + // Note: Names are not encrypted in AAA/AAO 2.6 (LicenseeVersion 32) + public override void Deserialize(IUnrealStream stream, UNameTableItem item) + { + if (!(stream.Decoder is CryptoDecoderAA2)) + { + // Fallback to the default implementation + item.Deserialize(stream); + return; + } + + // Thanks to @gildor2, decryption code transpiled from https://github.com/gildor2/UEViewer, + int length = stream.ReadIndex(); + Debug.Assert(length < 0); + int size = -length; + + const byte n = 5; + byte shift = n; + var buffer = new char[size]; + for (var i = 0; i < size; i++) + { + ushort c = stream.ReadUInt16(); + ushort c2 = CryptoCore.RotateRight(c, shift); + Debug.Assert(c2 < byte.MaxValue); + buffer[i] = (char)(byte)c2; + shift = (byte)((c - n) & 0x0F); + } + + var name = new string(buffer, 0, buffer.Length - 1); + Debug.Assert(name.Length <= MaxNameLengthUE2, "Maximum name length exceeded! Possible corrupt or unsupported package."); + // Part of name ? + int number = stream.ReadIndex(); + //Debug.Assert(number == 0, "Unknown value"); + + item.Name = name; + item.Flags = stream.ReadUInt32(); + } + + public override void Serialize(IUnrealStream stream, UImportTableItem item) + { + item.Serialize(stream); + } + + public override void Deserialize(IUnrealStream stream, UImportTableItem item) + { + item.PackageName = stream.ReadNameReference(); + item.ClassName = stream.ReadNameReference(); + byte unkByte = stream.ReadByte(); + Debug.WriteLine(unkByte, "unkByte"); + item.ObjectName = stream.ReadNameReference(); + item.OuterIndex = stream.ReadInt32(); + } + + public override void Serialize(IUnrealStream stream, UExportTableItem item) + { + throw new NotImplementedException(); + } + + public override void Deserialize(IUnrealStream stream, UExportTableItem item) + { + item.SuperIndex = stream.ReadObjectIndex(); + int unkInt = stream.ReadInt32(); + Debug.WriteLine(unkInt, "unkInt"); + item.ClassIndex = stream.ReadObjectIndex(); + item.OuterIndex = stream.ReadInt32(); + item.ObjectFlags = ~stream.ReadUInt32(); + item.ObjectName = stream.ReadNameReference(); + item.SerialSize = stream.ReadIndex(); + if (item.SerialSize > 0) + { + item.SerialOffset = stream.ReadIndex(); + } + } + } +} \ No newline at end of file diff --git a/src/Branch/UE2/DNF/EngineBranch.DNF.cs b/src/Branch/UE2/DNF/EngineBranch.DNF.cs new file mode 100644 index 00000000..1fc4fd6c --- /dev/null +++ b/src/Branch/UE2/DNF/EngineBranch.DNF.cs @@ -0,0 +1,167 @@ +using static UELib.Core.UStruct.UByteCodeDecompiler; +using UELib.Core.Tokens; +using UELib.Branch.UE2.DNF.Tokens; +using UELib.Core; +using UELib.Tokens; + +namespace UELib.Branch.UE2.DNF +{ + [Build(UnrealPackage.GameBuild.BuildName.DNF)] + public class EngineBranchDNF : DefaultEngineBranch + { + public EngineBranchDNF(BuildGeneration generation) : base(generation) + { + } + + protected TokenMap BuildTokenMap(UnrealPackage linker) + { + return new TokenMap(0x80) + { + { 0x00, typeof(LocalVariableToken) }, + { 0x01, typeof(InstanceVariableToken) }, + { 0x02, typeof(DefaultVariableToken) }, + { 0x03, typeof(BadToken) }, + { 0x04, typeof(ReturnToken) }, + { 0x05, typeof(SwitchToken) }, + { 0x06, typeof(JumpToken) }, + { 0x07, typeof(JumpIfNotToken) }, + { 0x08, typeof(StopToken) }, + { 0x09, typeof(AssertToken) }, + { 0x0A, typeof(CaseToken) }, + { 0x0B, typeof(NothingToken) }, + { 0x0C, typeof(LabelTableToken) }, + { 0x0D, typeof(GotoLabelToken) }, + { 0x0E, typeof(EatStringToken) }, + { 0x0F, typeof(LetToken) }, + { 0x10, typeof(DynamicArrayElementToken) }, + { 0x11, typeof(NewToken) }, + { 0x12, typeof(ClassContextToken) }, + { 0x13, typeof(MetaClassCastToken) }, + { 0x14, typeof(LetBoolToken) }, + { 0x15, typeof(BadToken) }, + { 0x16, typeof(EndFunctionParmsToken) }, + { 0x17, typeof(SelfToken) }, + { 0x18, typeof(SkipToken) }, + { 0x19, typeof(ContextToken) }, + { 0x1A, typeof(ArrayElementToken) }, + { 0x1B, typeof(VirtualFunctionToken) }, + { 0x1C, typeof(FinalFunctionToken) }, + { 0x1D, typeof(IntConstToken) }, + { 0x1E, typeof(FloatConstToken) }, + { 0x1F, typeof(StringConstToken) }, + { 0x20, typeof(ObjectConstToken) }, + { 0x21, typeof(NameConstToken) }, + { 0x22, typeof(RotationConstToken) }, + { 0x23, typeof(VectorConstToken) }, + { 0x24, typeof(ByteConstToken) }, + { 0x25, typeof(IntZeroToken) }, + { 0x26, typeof(IntOneToken) }, + { 0x27, typeof(TrueToken) }, + { 0x28, typeof(FalseToken) }, + { 0x29, typeof(NativeParameterToken) }, + { 0x2A, typeof(NoObjectToken) }, + { 0x2B, typeof(BadToken) }, + { 0x2C, typeof(IntConstByteToken) }, + { 0x2D, typeof(BoolVariableToken) }, + { 0x2E, typeof(DynamicCastToken) }, + { 0x2F, typeof(IteratorToken) }, + { 0x30, typeof(IteratorPopToken) }, + { 0x31, typeof(IteratorNextToken) }, + { 0x32, typeof(StructCmpEqToken) }, + { 0x33, typeof(StructCmpNeToken) }, + { 0x34, typeof(UnicodeStringConstToken) }, + { 0x35, typeof(BadToken) }, + { 0x36, typeof(StructMemberToken) }, + { 0x37, typeof(DebugInfoToken) }, + { 0x38, typeof(GlobalFunctionToken) }, + // Primitive casts are downgraded below (we don't want to repeat these because they might be subject to change) + { 0x39, typeof(BadToken) }, + { 0x3A, typeof(BadToken) }, + { 0x3B, typeof(BadToken) }, + { 0x3C, typeof(BadToken) }, + { 0x3D, typeof(BadToken) }, + { 0x3E, typeof(BadToken) }, + { 0x3F, typeof(BadToken) }, + { 0x40, typeof(BadToken) }, + { 0x41, typeof(BadToken) }, + { 0x42, typeof(BadToken) }, + { 0x43, typeof(BadToken) }, + { 0x44, typeof(BadToken) }, + { 0x45, typeof(BadToken) }, + { 0x46, typeof(BadToken) }, + { 0x47, typeof(BadToken) }, + { 0x48, typeof(BadToken) }, + { 0x49, typeof(BadToken) }, + { 0x4A, typeof(BadToken) }, + { 0x4B, typeof(BadToken) }, + { 0x4C, typeof(BadToken) }, + { 0x4D, typeof(BadToken) }, + { 0x4E, typeof(BadToken) }, + { 0x4F, typeof(BadToken) }, + { 0x50, typeof(BadToken) }, + { 0x51, typeof(BadToken) }, + { 0x52, typeof(BadToken) }, + { 0x53, typeof(BadToken) }, + { 0x54, typeof(BadToken) }, + { 0x55, typeof(BadToken) }, + { 0x56, typeof(BadToken) }, + { 0x57, typeof(BadToken) }, + { 0x58, typeof(BadToken) }, + { 0x59, typeof(BadToken) }, + { 0x5A, typeof(DynamicArrayLengthToken) }, + { 0x5B, typeof(DynamicArrayInsertToken) }, + { 0x5C, typeof(DynamicArrayAddToken) }, + { 0x5D, typeof(DynamicArrayRemoveToken) }, + { 0x5E, typeof(DelegateFunctionToken) }, + { 0x5F, typeof(DelegatePropertyToken) }, + // DNF has extended the ExtendNative tokens set from 0x60 to 0x80. + { 0x60, typeof(LetDelegateToken) }, + { 0x61, typeof(VectorConstZeroToken) }, + { 0x62, typeof(VectorConstUnitZToken) }, + { 0x63, typeof(RotConstZeroToken) }, + { 0x64, typeof(IntConstWordToken) }, + { 0x65, typeof(RotConstBytesToken) }, + { 0x66, typeof(DynamicArrayEmptyToken) }, + { 0x67, typeof(BreakpointToken) }, + { 0x68, typeof(BadToken) }, + { 0x69, typeof(RotConstPitchToken) }, + { 0x6A, typeof(RotConstYawToken) }, + { 0x6B, typeof(RotConstRollToken) }, + { 0x6C, typeof(VectorXToken) }, + { 0x6D, typeof(VectorYToken) }, + { 0x6E, typeof(VectorZToken) }, + { 0x6F, typeof(VectorXYToken) }, + { 0x70, typeof(VectorXZToken) }, + { 0x71, typeof(VectorYZToken) }, + { 0x72, typeof(BadToken) }, + { 0x73, typeof(BadToken) }, + { 0x74, typeof(BadToken) }, + { 0x75, typeof(BadToken) }, + { 0x76, typeof(BadToken) }, + { 0x77, typeof(BadToken) }, + { 0x78, typeof(BadToken) }, + { 0x79, typeof(BadToken) }, + { 0x7A, typeof(BadToken) }, + { 0x7B, typeof(BadToken) }, + { 0x7C, typeof(BadToken) }, + { 0x7D, typeof(BadToken) }, + { 0x7E, typeof(BadToken) }, + { 0x7F, typeof(BadToken) }, + }; + } + + protected override void SetupTokenFactory(UnrealPackage linker) + { + var tokenMap = BuildTokenMap(linker); + // DNF uses UE1 casting byte-codes (probably because under the hood the engine was upgraded from UE1 to UE2) + DowngradePrimitiveCasts(tokenMap); + // Undo downgrade, now this raises the question, which byte-code is set for DelegateToString if there is one? + tokenMap[(byte)CastToken.DelegateToString] = typeof(DynamicArrayLengthToken); + SetupTokenFactory( + tokenMap, + TokenFactory.FromPackage(linker.NTLPackage), + 0x80, + 0x90); + } + } +} diff --git a/src/Branch/UE2/DNF/Tokens/BreakpointToken.cs b/src/Branch/UE2/DNF/Tokens/BreakpointToken.cs new file mode 100644 index 00000000..4c90ef00 --- /dev/null +++ b/src/Branch/UE2/DNF/Tokens/BreakpointToken.cs @@ -0,0 +1,15 @@ +using UELib.Core; + +namespace UELib.Branch.UE2.DNF.Tokens +{ + public class BreakpointToken : UStruct.UByteCodeDecompiler.Token + { + public override string Decompile() + { + // TODO: + Decompiler.PreComment = "// Breakpoint"; + Decompiler.MarkSemicolon(); + return "@UnknownSyntax"; + } + } +} \ No newline at end of file diff --git a/src/Branch/UE2/DNF/Tokens/DynamicArrayEmptyToken.cs b/src/Branch/UE2/DNF/Tokens/DynamicArrayEmptyToken.cs new file mode 100644 index 00000000..98bb257a --- /dev/null +++ b/src/Branch/UE2/DNF/Tokens/DynamicArrayEmptyToken.cs @@ -0,0 +1,22 @@ +using UELib.Core; +using UELib.ObjectModel.Annotations; +using UELib.Tokens; + +namespace UELib.Branch.UE2.DNF.Tokens +{ + [ExprToken(ExprToken.DynArrayEmpty)] + public class DynamicArrayEmptyToken : UStruct.UByteCodeDecompiler.DynamicArrayMethodToken + { + public override void Deserialize(IUnrealStream stream) + { + // Array + DeserializeNext(); + } + + public override string Decompile() + { + Decompiler.MarkSemicolon(); + return $"{DecompileNext()}.Empty()"; + } + } +} diff --git a/src/Branch/UE2/DNF/Tokens/IntConstWordToken.cs b/src/Branch/UE2/DNF/Tokens/IntConstWordToken.cs new file mode 100644 index 00000000..5f395b9d --- /dev/null +++ b/src/Branch/UE2/DNF/Tokens/IntConstWordToken.cs @@ -0,0 +1,21 @@ +using UELib.Core; +using UELib.UnrealScript; + +namespace UELib.Branch.UE2.DNF.Tokens +{ + public class IntConstWordToken : UStruct.UByteCodeDecompiler.Token + { + public ushort Value; + + public override void Deserialize(IUnrealStream stream) + { + Value = stream.ReadUInt16(); + Decompiler.AlignSize(sizeof(ushort)); + } + + public override string Decompile() + { + return PropertyDisplay.FormatLiteral(Value); + } + } +} \ No newline at end of file diff --git a/src/Branch/UE2/DNF/Tokens/RotConstBytesToken.cs b/src/Branch/UE2/DNF/Tokens/RotConstBytesToken.cs new file mode 100644 index 00000000..b516b3c2 --- /dev/null +++ b/src/Branch/UE2/DNF/Tokens/RotConstBytesToken.cs @@ -0,0 +1,18 @@ +using UELib.Core; + +namespace UELib.Branch.UE2.DNF.Tokens +{ + public class RotConstBytesToken : UStruct.UByteCodeDecompiler.RotationConstToken + { + public override void Deserialize(IUnrealStream stream) + { + // FIXME: These must have been compressed right? + Rotation.Pitch = stream.ReadByte(); + Decompiler.AlignSize(sizeof(byte)); + Rotation.Roll = stream.ReadByte(); + Decompiler.AlignSize(sizeof(byte)); + Rotation.Yaw = stream.ReadByte(); + Decompiler.AlignSize(sizeof(byte)); + } + } +} \ No newline at end of file diff --git a/src/Branch/UE2/DNF/Tokens/RotConstPitchToken.cs b/src/Branch/UE2/DNF/Tokens/RotConstPitchToken.cs new file mode 100644 index 00000000..888a666c --- /dev/null +++ b/src/Branch/UE2/DNF/Tokens/RotConstPitchToken.cs @@ -0,0 +1,21 @@ +using UELib.Core; +using UELib.UnrealScript; + +namespace UELib.Branch.UE2.DNF.Tokens +{ + public class RotConstPitchToken : UStruct.UByteCodeDecompiler.Token + { + public int Pitch; + + public override void Deserialize(IUnrealStream stream) + { + Pitch = stream.ReadInt32(); + Decompiler.AlignSize(sizeof(int)); + } + + public override string Decompile() + { + return $"rotpitch({PropertyDisplay.FormatLiteral(Pitch)})"; + } + } +} \ No newline at end of file diff --git a/src/Branch/UE2/DNF/Tokens/RotConstRollToken.cs b/src/Branch/UE2/DNF/Tokens/RotConstRollToken.cs new file mode 100644 index 00000000..f6104765 --- /dev/null +++ b/src/Branch/UE2/DNF/Tokens/RotConstRollToken.cs @@ -0,0 +1,21 @@ +using UELib.Core; +using UELib.UnrealScript; + +namespace UELib.Branch.UE2.DNF.Tokens +{ + public class RotConstRollToken : UStruct.UByteCodeDecompiler.Token + { + public int Roll; + + public override void Deserialize(IUnrealStream stream) + { + Roll = stream.ReadInt32(); + Decompiler.AlignSize(sizeof(int)); + } + + public override string Decompile() + { + return $"rotroll({PropertyDisplay.FormatLiteral(Roll)})"; + } + } +} \ No newline at end of file diff --git a/src/Branch/UE2/DNF/Tokens/RotConstYawToken.cs b/src/Branch/UE2/DNF/Tokens/RotConstYawToken.cs new file mode 100644 index 00000000..2f76cdea --- /dev/null +++ b/src/Branch/UE2/DNF/Tokens/RotConstYawToken.cs @@ -0,0 +1,21 @@ +using UELib.Core; +using UELib.UnrealScript; + +namespace UELib.Branch.UE2.DNF.Tokens +{ + public class RotConstYawToken : UStruct.UByteCodeDecompiler.Token + { + public int Yaw; + + public override void Deserialize(IUnrealStream stream) + { + Yaw = stream.ReadInt32(); + Decompiler.AlignSize(sizeof(int)); + } + + public override string Decompile() + { + return $"rotyaw({PropertyDisplay.FormatLiteral(Yaw)})"; + } + } +} \ No newline at end of file diff --git a/src/Branch/UE2/DNF/Tokens/RotConstZeroToken.cs b/src/Branch/UE2/DNF/Tokens/RotConstZeroToken.cs new file mode 100644 index 00000000..e3556b80 --- /dev/null +++ b/src/Branch/UE2/DNF/Tokens/RotConstZeroToken.cs @@ -0,0 +1,12 @@ +using UELib.Core; + +namespace UELib.Branch.UE2.DNF.Tokens +{ + public class RotConstZeroToken : UStruct.UByteCodeDecompiler.Token + { + public override string Decompile() + { + return "rot(0, 0, 0)"; + } + } +} \ No newline at end of file diff --git a/src/Branch/UE2/DNF/Tokens/VectorConstUnitZToken.cs b/src/Branch/UE2/DNF/Tokens/VectorConstUnitZToken.cs new file mode 100644 index 00000000..b134382c --- /dev/null +++ b/src/Branch/UE2/DNF/Tokens/VectorConstUnitZToken.cs @@ -0,0 +1,13 @@ +using UELib.Core; + +namespace UELib.Branch.UE2.DNF.Tokens +{ + public class VectorConstUnitZToken : UStruct.UByteCodeDecompiler.Token + { + public override string Decompile() + { + // TODO + return "@UnknownSyntax"; + } + } +} \ No newline at end of file diff --git a/src/Branch/UE2/DNF/Tokens/VectorConstZeroToken.cs b/src/Branch/UE2/DNF/Tokens/VectorConstZeroToken.cs new file mode 100644 index 00000000..bbe1c685 --- /dev/null +++ b/src/Branch/UE2/DNF/Tokens/VectorConstZeroToken.cs @@ -0,0 +1,12 @@ +using UELib.Core; + +namespace UELib.Branch.UE2.DNF.Tokens +{ + public class VectorConstZeroToken : UStruct.UByteCodeDecompiler.Token + { + public override string Decompile() + { + return "vect(0, 0, 0)"; + } + } +} \ No newline at end of file diff --git a/src/Branch/UE2/DNF/Tokens/VectorXToken.cs b/src/Branch/UE2/DNF/Tokens/VectorXToken.cs new file mode 100644 index 00000000..1d42bb03 --- /dev/null +++ b/src/Branch/UE2/DNF/Tokens/VectorXToken.cs @@ -0,0 +1,21 @@ +using UELib.Core; +using UELib.UnrealScript; + +namespace UELib.Branch.UE2.DNF.Tokens +{ + public class VectorXToken : UStruct.UByteCodeDecompiler.Token + { + public float X; + + public override void Deserialize(IUnrealStream stream) + { + X = stream.ReadFloat(); + Decompiler.AlignSize(sizeof(float)); + } + + public override string Decompile() + { + return $"vectx({PropertyDisplay.FormatLiteral(X)})"; + } + } +} \ No newline at end of file diff --git a/src/Branch/UE2/DNF/Tokens/VectorXYToken.cs b/src/Branch/UE2/DNF/Tokens/VectorXYToken.cs new file mode 100644 index 00000000..ebf6bbfb --- /dev/null +++ b/src/Branch/UE2/DNF/Tokens/VectorXYToken.cs @@ -0,0 +1,24 @@ +using UELib.Core; +using UELib.UnrealScript; + +namespace UELib.Branch.UE2.DNF.Tokens +{ + public class VectorXYToken : UStruct.UByteCodeDecompiler.Token + { + public float X, Y; + + public override void Deserialize(IUnrealStream stream) + { + X = stream.ReadFloat(); + Decompiler.AlignSize(sizeof(float)); + + Y = stream.ReadFloat(); + Decompiler.AlignSize(sizeof(float)); + } + + public override string Decompile() + { + return $"vectxy({PropertyDisplay.FormatLiteral(X)}, {PropertyDisplay.FormatLiteral(Y)})"; + } + } +} \ No newline at end of file diff --git a/src/Branch/UE2/DNF/Tokens/VectorXZToken.cs b/src/Branch/UE2/DNF/Tokens/VectorXZToken.cs new file mode 100644 index 00000000..5394d60d --- /dev/null +++ b/src/Branch/UE2/DNF/Tokens/VectorXZToken.cs @@ -0,0 +1,24 @@ +using UELib.Core; +using UELib.UnrealScript; + +namespace UELib.Branch.UE2.DNF.Tokens +{ + public class VectorXZToken : UStruct.UByteCodeDecompiler.Token + { + public float X, Z; + + public override void Deserialize(IUnrealStream stream) + { + X = stream.ReadFloat(); + Decompiler.AlignSize(sizeof(float)); + + Z = stream.ReadFloat(); + Decompiler.AlignSize(sizeof(float)); + } + + public override string Decompile() + { + return $"vectxz({PropertyDisplay.FormatLiteral(X)}, {PropertyDisplay.FormatLiteral(Z)})"; + } + } +} \ No newline at end of file diff --git a/src/Branch/UE2/DNF/Tokens/VectorYToken.cs b/src/Branch/UE2/DNF/Tokens/VectorYToken.cs new file mode 100644 index 00000000..2bab958f --- /dev/null +++ b/src/Branch/UE2/DNF/Tokens/VectorYToken.cs @@ -0,0 +1,21 @@ +using UELib.Core; +using UELib.UnrealScript; + +namespace UELib.Branch.UE2.DNF.Tokens +{ + public class VectorYToken : UStruct.UByteCodeDecompiler.Token + { + public float Y; + + public override void Deserialize(IUnrealStream stream) + { + Y = stream.ReadFloat(); + Decompiler.AlignSize(sizeof(float)); + } + + public override string Decompile() + { + return $"vecty({PropertyDisplay.FormatLiteral(Y)})"; + } + } +} \ No newline at end of file diff --git a/src/Branch/UE2/DNF/Tokens/VectorYZToken.cs b/src/Branch/UE2/DNF/Tokens/VectorYZToken.cs new file mode 100644 index 00000000..adc44aad --- /dev/null +++ b/src/Branch/UE2/DNF/Tokens/VectorYZToken.cs @@ -0,0 +1,24 @@ +using UELib.Core; +using UELib.UnrealScript; + +namespace UELib.Branch.UE2.DNF.Tokens +{ + public class VectorYZToken : UStruct.UByteCodeDecompiler.Token + { + public float Y, Z; + + public override void Deserialize(IUnrealStream stream) + { + Y = stream.ReadFloat(); + Decompiler.AlignSize(sizeof(float)); + + Z = stream.ReadFloat(); + Decompiler.AlignSize(sizeof(float)); + } + + public override string Decompile() + { + return $"vectyz({PropertyDisplay.FormatLiteral(Y)}, {PropertyDisplay.FormatLiteral(Z)})"; + } + } +} \ No newline at end of file diff --git a/src/Branch/UE2/DNF/Tokens/VectorZToken.cs b/src/Branch/UE2/DNF/Tokens/VectorZToken.cs new file mode 100644 index 00000000..b5b56f62 --- /dev/null +++ b/src/Branch/UE2/DNF/Tokens/VectorZToken.cs @@ -0,0 +1,21 @@ +using UELib.Core; +using UELib.UnrealScript; + +namespace UELib.Branch.UE2.DNF.Tokens +{ + public class VectorZToken : UStruct.UByteCodeDecompiler.Token + { + public float Z; + + public override void Deserialize(IUnrealStream stream) + { + Z = stream.ReadFloat(); + Decompiler.AlignSize(sizeof(float)); + } + + public override string Decompile() + { + return $"vectz({PropertyDisplay.FormatLiteral(Z)})"; + } + } +} \ No newline at end of file diff --git a/src/Branch/UE2/DVS/EngineBranch.DVS.cs b/src/Branch/UE2/DVS/EngineBranch.DVS.cs new file mode 100644 index 00000000..f81296b1 --- /dev/null +++ b/src/Branch/UE2/DVS/EngineBranch.DVS.cs @@ -0,0 +1,123 @@ +using UELib.Branch.UE2.DNF.Tokens; +using UELib.Branch.UE2.DVS.Tokens; +using UELib.Core; +using UELib.Core.Tokens; +using UELib.ObjectModel.Annotations; +using UELib.Tokens; + +namespace UELib.Branch.UE2.DVS +{ + public class EngineBranchDVS : DefaultEngineBranch + { + public EngineBranchDVS(BuildGeneration generation) : base(BuildGeneration.UE2) + { + } + + protected override TokenMap BuildTokenMap(UnrealPackage linker) + { + var tokenMap = base.BuildTokenMap(linker); + tokenMap[0x05] = typeof(SwitchToken); + tokenMap[0x12] = typeof(ClassContextToken); + tokenMap[0x19] = typeof(ContextToken); + tokenMap[0x1B] = typeof(VirtualFunctionToken); + tokenMap[0x1C] = typeof(FinalFunctionToken); + tokenMap[0x38] = typeof(GlobalFunctionToken); + tokenMap[0x43] = typeof(DelegateFunctionToken); + tokenMap[0x46] = typeof(DynamicArrayEmptyToken); + tokenMap[0x47] = typeof(UStruct.UByteCodeDecompiler.DynamicArraySortToken); + tokenMap[0x48] = typeof(UStruct.UByteCodeDecompiler.ConditionalToken); + tokenMap[0x49] = typeof(ColorConstToken); + + // cast token, 0x5B StructToString + + return tokenMap; + } + } + + [ExprToken(ExprToken.Switch)] + public sealed class SwitchToken : UStruct.UByteCodeDecompiler.SwitchToken + { + // DVS: Missing PropertyType + public override void Deserialize(IUnrealStream stream) => DeserializeNext(); + } + + [ExprToken(ExprToken.FinalFunction)] + public sealed class FinalFunctionToken : UStruct.UByteCodeDecompiler.FinalFunctionToken + { + protected override void DeserializeCall(IUnrealStream stream) + { + uint skipSize = stream.ReadUInt16(); + Decompiler.AlignSize(sizeof(ushort)); + + base.DeserializeCall(stream); + } + } + + [ExprToken(ExprToken.VirtualFunction)] + public sealed class VirtualFunctionToken : UStruct.UByteCodeDecompiler.VirtualFunctionToken + { + protected override void DeserializeCall(IUnrealStream stream) + { + uint skipSize = stream.ReadUInt16(); + Decompiler.AlignSize(sizeof(ushort)); + + base.DeserializeCall(stream); + } + } + + [ExprToken(ExprToken.GlobalFunction)] + public sealed class GlobalFunctionToken : UStruct.UByteCodeDecompiler.GlobalFunctionToken + { + protected override void DeserializeCall(IUnrealStream stream) + { + uint skipSize = stream.ReadUInt16(); + Decompiler.AlignSize(sizeof(ushort)); + + base.DeserializeCall(stream); + } + } + + [ExprToken(ExprToken.DelegateFunction)] + public sealed class DelegateFunctionToken : UStruct.UByteCodeDecompiler.DelegateFunctionToken + { + protected override void DeserializeCall(IUnrealStream stream) + { + uint skipSize = stream.ReadUInt16(); + Decompiler.AlignSize(sizeof(ushort)); + + base.DeserializeCall(stream); + } + } + + [ExprToken(ExprToken.Context)] + public sealed class ContextToken : UStruct.UByteCodeDecompiler.ContextToken + { + public override void Deserialize(IUnrealStream stream) + { + // A.? + DeserializeNext(); + + uint skipSize = stream.ReadUInt16(); + Decompiler.AlignSize(sizeof(ushort)); + + // ?.B + DeserializeNext(); + } + } + + [ExprToken(ExprToken.ClassContext)] + public sealed class ClassContextToken : UStruct.UByteCodeDecompiler.ClassContextToken + { + public override void Deserialize(IUnrealStream stream) + { + // A.? + DeserializeNext(); + + uint skipSize = stream.ReadUInt16(); + Decompiler.AlignSize(sizeof(ushort)); + + // ?.B + DeserializeNext(); + } + } +} diff --git a/src/Branch/UE2/DVS/Tokens/ColorConstToken.cs b/src/Branch/UE2/DVS/Tokens/ColorConstToken.cs new file mode 100644 index 00000000..01799a78 --- /dev/null +++ b/src/Branch/UE2/DVS/Tokens/ColorConstToken.cs @@ -0,0 +1,23 @@ +using UELib.Core; +using UELib.ObjectModel.Annotations; +using UELib.Tokens; + +namespace UELib.Branch.UE2.DVS.Tokens +{ + [ExprToken(ExprToken.StructConst)] + public sealed class ColorConstToken : UStruct.UByteCodeDecompiler.StructConstToken + { + public UColor Color; + + public override void Deserialize(IUnrealStream stream) + { + stream.ReadStruct(out Color); + Decompiler.AlignSize(4); + } + + public override string Decompile() + { + return $"col({Color.R},{Color.G},{Color.B},{Color.A})"; + } + } +} diff --git a/src/Branch/UE2/VG/Tokens/LogFunctionToken.cs b/src/Branch/UE2/VG/Tokens/LogFunctionToken.cs new file mode 100644 index 00000000..0d0683ca --- /dev/null +++ b/src/Branch/UE2/VG/Tokens/LogFunctionToken.cs @@ -0,0 +1,23 @@ +using UELib.Core; +using UELib.ObjectModel.Annotations; +using UELib.Tokens; + +namespace UELib.Branch.UE2.VG.Tokens +{ + [ExprToken(ExprToken.NativeFunction)] + public class LogFunctionToken : UStruct.UByteCodeDecompiler.FunctionToken + { + public override void Deserialize(IUnrealStream stream) + { + // NothingToken(0x0B) twice + DeserializeCall(stream); + } + + public override string Decompile() + { + Decompiler.MarkSemicolon(); + // FIXME: Reverse-order of params? + return DecompileCall("log"); + } + } +} diff --git a/src/Branch/UE3/APB/EngineBranch.APB.cs b/src/Branch/UE3/APB/EngineBranch.APB.cs new file mode 100644 index 00000000..393b5000 --- /dev/null +++ b/src/Branch/UE3/APB/EngineBranch.APB.cs @@ -0,0 +1,29 @@ +using UELib.Core.Tokens; +using static UELib.Core.UStruct.UByteCodeDecompiler; + +namespace UELib.Branch.UE3.APB +{ + public class EngineBranchAPB : DefaultEngineBranch + { + public EngineBranchAPB(BuildGeneration generation) : base(generation) + { + } + + protected override TokenMap BuildTokenMap(UnrealPackage linker) + { + if (linker.LicenseeVersion < 32) return base.BuildTokenMap(linker); + + // FIXME: Incomplete + var tokenMap = new TokenMap + { + { 0x00, typeof(ReturnToken) }, + { 0x04, typeof(LocalVariableToken) }, + { 0x06, typeof(JumpIfNotToken) }, + { 0x07, typeof(JumpToken) }, + { 0x0A, typeof(NothingToken) }, + { 0x0B, typeof(CaseToken) } + }; + return tokenMap; + } + } +} \ No newline at end of file diff --git a/src/Branch/UE3/BL2/Tokens/LocalVariableToken.cs b/src/Branch/UE3/BL2/Tokens/LocalVariableToken.cs new file mode 100644 index 00000000..d43f1ee1 --- /dev/null +++ b/src/Branch/UE3/BL2/Tokens/LocalVariableToken.cs @@ -0,0 +1,24 @@ +using UELib.Core; +using UELib.Core.Tokens; +using UELib.ObjectModel.Annotations; +using UELib.Tokens; + +namespace UELib.Branch.UE3.BL2.Tokens +{ + [ExprToken(ExprToken.LocalVariable)] + public class LocalVariableToken : UStruct.UByteCodeDecompiler.Token + { + public int LocalIndex; + + public override void Deserialize(IUnrealStream stream) + { + LocalIndex = stream.ReadInt32(); + Decompiler.AlignSize(sizeof(int)); + } + + public override string Decompile() + { + return TokenFactory.CreateGeneratedName($"LOCAL_{typeof(T).Name}_{LocalIndex}"); + } + } +} diff --git a/src/Branch/UE3/DD2/EngineBranch.DD2.cs b/src/Branch/UE3/DD2/EngineBranch.DD2.cs new file mode 100644 index 00000000..3e13f95e --- /dev/null +++ b/src/Branch/UE3/DD2/EngineBranch.DD2.cs @@ -0,0 +1,29 @@ +namespace UELib.Branch.UE3.DD2 +{ + [Build(UnrealPackage.GameBuild.BuildName.DD2)] + public class EngineBranchDD2 : DefaultEngineBranch + { + public EngineBranchDD2(BuildGeneration generation) : base(generation) + { + } + + public override void PostDeserializePackage(UnrealPackage linker, IUnrealStream stream) + { + base.PostDeserializePackage(linker, stream); + int position = stream.Package.Summary.HeaderSize; + var exports = stream.Package.Exports; + foreach (var exp in exports) + { + // Just in-case. + if (exp.SerialOffset != 0) + { + position += exp.SerialOffset; + continue; + } + + exp.SerialOffset = position; + position += exp.SerialSize; + } + } + } +} \ No newline at end of file diff --git a/src/Branch/UE3/MOH/EngineBranch.MOH.cs b/src/Branch/UE3/MOH/EngineBranch.MOH.cs new file mode 100644 index 00000000..e48caf36 --- /dev/null +++ b/src/Branch/UE3/MOH/EngineBranch.MOH.cs @@ -0,0 +1,37 @@ +using System.Linq; +using UELib.Core.Tokens; +using static UELib.Core.UStruct.UByteCodeDecompiler; + +namespace UELib.Branch.UE3.MOH +{ + public class EngineBranchMOH : DefaultEngineBranch + { + public EngineBranchMOH(BuildGeneration generation) : base(generation) + { + } + + protected override TokenMap BuildTokenMap(UnrealPackage linker) + { + var tokenMap = base.BuildTokenMap(linker); + // FIXME: Incomplete + var newTokenMap = new TokenMap + { + { 0x0C, typeof(EmptyParmToken) }, + { 0x1D, typeof(FinalFunctionToken) }, + { 0x16, typeof(EndOfScriptToken) }, + { 0x18, typeof(StringConstToken) }, + { 0x23, typeof(EndFunctionParmsToken) }, + { 0x28, typeof(NothingToken) }, + { 0x2C, typeof(LetToken) }, + { 0x31, typeof(LocalVariableToken) }, + { 0x34, typeof(JumpIfNotToken) }, + { 0x35, typeof(ReturnToken) }, + { 0x37, typeof(ReturnNothingToken) }, + { 0x3F, typeof(PrimitiveCastToken) }, + { 0x46, typeof(StructMemberToken) }, + { 0x4A, typeof(NativeParameterToken) } + }; + return (TokenMap)tokenMap.Concat(newTokenMap); + } + } +} \ No newline at end of file diff --git a/src/Branch/UE3/RSS/EngineBranch.RSS.cs b/src/Branch/UE3/RSS/EngineBranch.RSS.cs new file mode 100644 index 00000000..0d6fb7e9 --- /dev/null +++ b/src/Branch/UE3/RSS/EngineBranch.RSS.cs @@ -0,0 +1,22 @@ +using UELib.Branch.UE3.RSS.Tokens; +using UELib.Core.Tokens; + +namespace UELib.Branch.UE3.RSS +{ + public class EngineBranchRSS : DefaultEngineBranch + { + public EngineBranchRSS(BuildGeneration generation) : base(BuildGeneration.RSS) + { + } + + protected override TokenMap BuildTokenMap(UnrealPackage linker) + { + var tokenMap = base.BuildTokenMap(linker); + if (linker.Build == UnrealPackage.GameBuild.BuildName.Batman4) + { + tokenMap[0x2B] = typeof(NameConstNoNumberToken); // FIXME: NameConst but without the Int32 number at the end + } + return tokenMap; + } + } +} \ No newline at end of file diff --git a/src/Branch/UE3/RSS/Tokens/NameConstNoNumberToken.cs b/src/Branch/UE3/RSS/Tokens/NameConstNoNumberToken.cs index 80c06e4a..31b37ccd 100644 --- a/src/Branch/UE3/RSS/Tokens/NameConstNoNumberToken.cs +++ b/src/Branch/UE3/RSS/Tokens/NameConstNoNumberToken.cs @@ -1,7 +1,10 @@ using UELib.Core; +using UELib.ObjectModel.Annotations; +using UELib.Tokens; namespace UELib.Branch.UE3.RSS.Tokens { + [ExprToken(ExprToken.NameConst)] public class NameConstNoNumberToken : UStruct.UByteCodeDecompiler.NameConstToken { public override void Deserialize(IUnrealStream stream) @@ -9,4 +12,4 @@ public override void Deserialize(IUnrealStream stream) Name = ReadNameNoNumber(stream); } } -} \ No newline at end of file +} diff --git a/src/Branch/UE4/EngineBranch.UE4.cs b/src/Branch/UE4/EngineBranch.UE4.cs new file mode 100644 index 00000000..7d549af5 --- /dev/null +++ b/src/Branch/UE4/EngineBranch.UE4.cs @@ -0,0 +1,74 @@ +using System; +using UELib.Core.Tokens; +using UELib.Flags; +using static UELib.Core.UStruct.UByteCodeDecompiler; + +namespace UELib.Branch.UE4 +{ + /// + /// An EngineBranch to assist with the parsing of UE4 packages, this way we can easily separate UE4 specific changes away from the default UE3 implementation. + /// The branch is selected based on whether a UE4Version is set to a value greater than 0. + /// + public class EngineBranchUE4 : EngineBranch + { + [Flags] + public enum PackageFlagsUE4 : uint + { + /// + /// Runtime-only;not-serialized + /// + [Obsolete] NewlyCreated = 0x00000001, + + EditorOnly = 0x00000040U, + + /// + /// Whether the package has been cooked. + /// + Cooked = 0x00000200U, + + UnversionedProperties = 0x00002000U, + ReloadingForCooker = 0x40000000U, + FilterEditorOnly = 0x80000000U, + } + + public EngineBranchUE4() : base(BuildGeneration.UE4) + { + } + + /// + /// We re-map all PackageFlags because they are no longer a match with those of UE3 or older. + /// + public override void Setup(UnrealPackage linker) + { + SetupEnumPackageFlags(linker); + EnumFlagsMap.Add(typeof(PackageFlag), PackageFlags); + } + + protected virtual void SetupEnumPackageFlags(UnrealPackage linker) + { + PackageFlags[(int)Flags.PackageFlag.ClientOptional] = + (uint)DefaultEngineBranch.PackageFlagsDefault.ClientOptional; + PackageFlags[(int)Flags.PackageFlag.ServerSideOnly] = + (uint)DefaultEngineBranch.PackageFlagsDefault.ServerSideOnly; + PackageFlags[(int)Flags.PackageFlag.EditorOnly] = (uint)PackageFlagsUE4.EditorOnly; + PackageFlags[(int)Flags.PackageFlag.Cooked] = (uint)PackageFlagsUE4.Cooked; + PackageFlags[(int)Flags.PackageFlag.UnversionedProperties] = (uint)PackageFlagsUE4.UnversionedProperties; + PackageFlags[(int)Flags.PackageFlag.ReloadingForCooker] = (uint)PackageFlagsUE4.ReloadingForCooker; + PackageFlags[(int)Flags.PackageFlag.FilterEditorOnly] = (uint)PackageFlagsUE4.FilterEditorOnly; + } + + protected override void SetupSerializer(UnrealPackage linker) + { + SetupSerializer(); + } + + protected override TokenMap BuildTokenMap(UnrealPackage linker) + { + var tokenMap = new TokenMap + { + { 0x00, typeof(LocalVariableToken) } + }; + return tokenMap; + } + } +} \ No newline at end of file diff --git a/src/Branch/UE4/PackageSerializer.UE4.cs b/src/Branch/UE4/PackageSerializer.UE4.cs new file mode 100644 index 00000000..eb0b6693 --- /dev/null +++ b/src/Branch/UE4/PackageSerializer.UE4.cs @@ -0,0 +1,100 @@ +using System.Diagnostics; +using UELib.Core; + +namespace UELib.Branch.UE4 +{ + public class PackageSerializerUE4 : IPackageSerializer + { + private const int MaxNameLengthUE4 = 1024; + + public void Serialize(IUnrealStream stream, IUnrealSerializableClass obj) + { + obj.Serialize(stream); + } + + public void Deserialize(IUnrealStream stream, IUnrealDeserializableClass obj) + { + obj.Deserialize(stream); + } + + public void Serialize(IUnrealStream stream, UNameTableItem item) + { + stream.Write(item.Name); + + // TODO: Re-calculate these for situations where the name may have been modified... + if (stream.UE4Version < 504) return; + stream.Write(item.NonCasePreservingHash); + stream.Write(item.CasePreservingHash); + } + + public void Deserialize(IUnrealStream stream, UNameTableItem item) + { + item.Name = stream.ReadText(); + Debug.Assert(item.Name.Length <= MaxNameLengthUE4, "Maximum name length exceeded! Possible corrupt or unsupported package."); + + if (stream.UE4Version < 504) return; + item.NonCasePreservingHash = stream.ReadUInt16(); + item.CasePreservingHash = stream.ReadUInt16(); + } + + public void Serialize(IUnrealStream stream, UImportTableItem item) + { + item.Serialize(stream); + } + + public void Deserialize(IUnrealStream stream, UImportTableItem item) + { + item.Deserialize(stream); + } + + public void Serialize(IUnrealStream stream, UExportTableItem item) + { + item.Serialize(stream); + } + + public void Deserialize(IUnrealStream stream, UExportTableItem item) + { + item.ClassIndex = stream.ReadInt32(); + item.SuperIndex = stream.ReadInt32(); + if (stream.UE4Version >= 508) item.TemplateIndex = stream.ReadInt32(); + item.OuterIndex = stream.ReadInt32(); + item.ObjectName = stream.ReadNameReference(); + + if (stream.UE4Version < 142) item.ArchetypeIndex = stream.ReadInt32(); + + item.ObjectFlags = stream.ReadUInt32(); + + if (stream.UE4Version >= 511) + { + item.SerialSize = (int)stream.ReadInt64(); + item.SerialOffset = (int)stream.ReadInt64(); + } + else + { + item.SerialSize = stream.ReadInt32(); + item.SerialOffset = stream.ReadInt32(); + } + + item.IsForcedExport = stream.ReadInt32() > 0; + item.IsNotForServer = stream.ReadInt32() > 0; + item.IsNotForClient = stream.ReadInt32() > 0; + if (stream.UE4Version < 196) + { + stream.ReadArray(out UArray generationNetObjectCount); + } + + stream.ReadStruct(out item.PackageGuid); + item.PackageFlags = stream.ReadUInt32(); + if (stream.UE4Version >= 365) item.IsNotForEditorGame = stream.ReadInt32() > 0; + if (stream.UE4Version >= 485) item.IsAsset = stream.ReadInt32() > 0; + if (stream.UE4Version >= 507) + { + int firstExportDependency = stream.ReadInt32(); + int serializationBeforeSerializationDependencies = stream.ReadInt32(); + int createBeforeSerializationDependencies = stream.ReadInt32(); + int serializationBeforeCreateDependencies = stream.ReadInt32(); + int createBeforeCreateDependencies = stream.ReadInt32(); + } + } + } +} \ No newline at end of file diff --git a/src/ByteCodeDecompiler.cs b/src/ByteCodeDecompiler.cs index 9e18dc27..0d9c0bd8 100644 --- a/src/ByteCodeDecompiler.cs +++ b/src/ByteCodeDecompiler.cs @@ -1,11 +1,11 @@ -//#define SUPPRESS_BOOLINTEXPLOIT - -using System; +using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Runtime.CompilerServices; using UELib.Annotations; +using UELib.Branch; +using UELib.Core.Tokens; using UELib.Flags; using UELib.Tokens; @@ -17,22 +17,12 @@ namespace UELib.Core public partial class UStruct { - /// - /// Decompiles the bytecodes from the 'Owner' - /// public partial class UByteCodeDecompiler : IUnrealDecompilable { - /// - /// The Struct that contains the bytecode that we have to deserialize and decompile! - /// private readonly UStruct _Container; - - /// - /// Pointer to the ObjectStream buffer of 'Owner' - /// - private UObjectStream Buffer => _Container.Buffer; - - private UnrealPackage Package => _Container.Package; + private UObjectStream _Buffer; + private readonly UnrealPackage _Package; + private readonly TokenFactory _TokenFactory; /// /// A collection of deserialized tokens, in their correspondence stream order. @@ -53,22 +43,27 @@ public partial class UByteCodeDecompiler : IUnrealDecompilable public UByteCodeDecompiler(UStruct container) { _Container = container; + + _Package = container.Package; + Debug.Assert(_Package != null); + _TokenFactory = container.GetTokenFactory(); + Debug.Assert(_TokenFactory != null); + SetupMemorySizes(); - SetupByteCodeMap(); } #region Deserialize /// - /// The current simulated-memory-aligned position in @Buffer. + /// The current in memory position relative to the first byte-token. /// - private int CodePosition { get; set; } + private int ScriptPosition { get; set; } /// - /// Size of FName in memory (int Index, (> 500) int Number). + /// Size of FName in memory (int Index, (>= 343) int Number). /// private byte _NameMemorySize = sizeof(int); - + /// /// Size of a pointer to an UObject in memory. /// 32bit, 64bit as of version 587 (even on 32bit platforms) @@ -78,389 +73,38 @@ public UByteCodeDecompiler(UStruct container) private void SetupMemorySizes() { #if BIOSHOCK - if (Package.Build == UnrealPackage.GameBuild.BuildName.BioShock) + if (_Package.Build == UnrealPackage.GameBuild.BuildName.BioShock) { _NameMemorySize = sizeof(int) + sizeof(int); return; } #endif - const short vNameSizeTo8 = 500; - if (Buffer.Version >= vNameSizeTo8) _NameMemorySize = sizeof(int) + sizeof(int); + const uint vNameSizeTo8 = (uint)PackageObjectLegacyVersion.NumberAddedToName; + if (_Package.Version >= vNameSizeTo8) _NameMemorySize = sizeof(int) + sizeof(int); #if TERA // Tera's reported version is false (partial upgrade?) - if (Package.Build == UnrealPackage.GameBuild.BuildName.Tera) return; + if (_Package.Build == UnrealPackage.GameBuild.BuildName.Tera) return; #endif const short vObjectSizeTo8 = 587; - if (Buffer.Version >= vObjectSizeTo8) _ObjectMemorySize = sizeof(long); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void AlignSize(int size) - { - CodePosition += size; + if (_Package.Version >= vObjectSizeTo8) _ObjectMemorySize = sizeof(long); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void AlignNameSize() + internal void AlignSize(int size) { - CodePosition += _NameMemorySize; + ScriptPosition += size; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void AlignObjectSize() - { - CodePosition += _ObjectMemorySize; - } - - // TODO: Retrieve the byte-codes from a NTL file instead. - [CanBeNull] private Dictionary _ByteCodeMap; -#if AA2 - private readonly Dictionary ByteCodeMap_BuildAa2_8 = new Dictionary - { - { 0x00, (byte)ExprToken.LocalVariable }, - { 0x01, (byte)ExprToken.InstanceVariable }, - { 0x02, (byte)ExprToken.DefaultVariable }, - { 0x03, (byte)ExprToken.Unused }, - { 0x04, (byte)ExprToken.Switch }, - { 0x05, (byte)ExprToken.ClassContext }, - { 0x06, (byte)ExprToken.Jump }, - { 0x07, (byte)ExprToken.GotoLabel }, - { 0x08, (byte)ExprToken.VirtualFunction }, - { 0x09, (byte)ExprToken.IntConst }, - { 0x0A, (byte)ExprToken.JumpIfNot }, - { 0x0B, (byte)ExprToken.LabelTable }, - { 0x0C, (byte)ExprToken.FinalFunction }, - { 0x0D, (byte)ExprToken.EatString }, - { 0x0E, (byte)ExprToken.Let }, - { 0x0F, (byte)ExprToken.Stop }, - { 0x10, (byte)ExprToken.New }, - { 0x11, (byte)ExprToken.Context }, - { 0x12, (byte)ExprToken.MetaCast }, - { 0x13, (byte)ExprToken.Skip }, - { 0x14, (byte)ExprToken.Self }, - { 0x15, (byte)ExprToken.Return }, - { 0x16, (byte)ExprToken.EndFunctionParms }, - { 0x17, (byte)ExprToken.Unused }, - { 0x18, (byte)ExprToken.LetBool }, - { 0x19, (byte)ExprToken.DynArrayElement }, - { 0x1A, (byte)ExprToken.Assert }, - { 0x1B, (byte)ExprToken.ByteConst }, - { 0x1C, (byte)ExprToken.Nothing }, - { 0x1D, (byte)ExprToken.DelegateProperty }, - { 0x1E, (byte)ExprToken.IntZero }, - { 0x1F, (byte)ExprToken.LetDelegate }, - { 0x20, (byte)ExprToken.False }, - { 0x21, (byte)ExprToken.ArrayElement }, - { 0x22, (byte)ExprToken.EndOfScript }, - { 0x23, (byte)ExprToken.True }, - { 0x24, (byte)ExprToken.Unused }, - { 0x25, (byte)ExprToken.FloatConst }, - { 0x26, (byte)ExprToken.Case }, - { 0x27, (byte)ExprToken.IntOne }, - { 0x28, (byte)ExprToken.StringConst }, - { 0x29, (byte)ExprToken.NoObject }, - { 0x2A, (byte)ExprToken.NativeParm }, - { 0x2B, (byte)ExprToken.Unused }, - { 0x2C, (byte)ExprToken.DebugInfo }, - { 0x2D, (byte)ExprToken.StructCmpEq }, - // FIXME: Verify IteratorNext/IteratorPop? - { 0x2E, (byte)ExprToken.IteratorNext }, - { 0x2F, (byte)ExprToken.DynArrayRemove }, - { 0x30, (byte)ExprToken.StructCmpNE }, - { 0x31, (byte)ExprToken.DynamicCast }, - { 0x32, (byte)ExprToken.Iterator }, - { 0x33, (byte)ExprToken.IntConstByte }, - { 0x34, (byte)ExprToken.BoolVariable }, - // FIXME: Verify IteratorNext/IteratorPop? - { 0x35, (byte)ExprToken.IteratorPop }, - { 0x36, (byte)ExprToken.UniStringConst }, - { 0x37, (byte)ExprToken.StructMember }, - { 0x38, (byte)ExprToken.Unused }, - { 0x39, (byte)ExprToken.DelegateFunction }, - { 0x3A, (byte)ExprToken.Unused }, - { 0x3B, (byte)ExprToken.Unused }, - { 0x3C, (byte)ExprToken.Unused }, - { 0x3D, (byte)ExprToken.Unused }, - { 0x3E, (byte)ExprToken.Unused }, - { 0x3F, (byte)ExprToken.Unused }, - { 0x40, (byte)ExprToken.ObjectConst }, - { 0x41, (byte)ExprToken.NameConst }, - { 0x42, (byte)ExprToken.DynArrayLength }, - { 0x43, (byte)ExprToken.DynArrayInsert }, - { 0x44, (byte)ExprToken.PrimitiveCast }, - { 0x45, (byte)ExprToken.GlobalFunction }, - { 0x46, (byte)ExprToken.VectorConst }, - { 0x47, (byte)ExprToken.RotationConst }, - { 0x48, (byte)ExprToken.Unused }, - { 0x49, (byte)ExprToken.Unused }, - { 0x4A, (byte)ExprToken.Unused }, - { 0x4B, (byte)ExprToken.Unused }, - { 0x4C, (byte)ExprToken.Unused }, - { 0x4D, (byte)ExprToken.Unused }, - { 0x4E, (byte)ExprToken.Unused }, - { 0x4F, (byte)ExprToken.Unused }, - { 0x50, (byte)ExprToken.Unused }, - { 0x51, (byte)ExprToken.Unused }, - { 0x52, (byte)ExprToken.Unused }, - { 0x53, (byte)ExprToken.Unused }, - { 0x54, (byte)ExprToken.Unused }, - { 0x55, (byte)ExprToken.Unused }, - { 0x56, (byte)ExprToken.Unused }, - { 0x57, (byte)ExprToken.Unused }, - { 0x58, (byte)ExprToken.Unused }, - { 0x59, (byte)ExprToken.Unused } - }; - - /// - /// The shifted byte-code map for AAA 2.6 - /// - private readonly Dictionary ByteCodeMap_BuildAa2_6 = new Dictionary - { - { 0x00, (byte)ExprToken.LocalVariable }, - { 0x01, (byte)ExprToken.InstanceVariable }, - { 0x02, (byte)ExprToken.DefaultVariable }, - { 0x03, (byte)ExprToken.Unused }, - { 0x04, (byte)ExprToken.Jump }, - { 0x05, (byte)ExprToken.Return }, - { 0x06, (byte)ExprToken.Switch }, - { 0x07, (byte)ExprToken.Stop }, - { 0x08, (byte)ExprToken.JumpIfNot }, - { 0x09, (byte)ExprToken.Nothing }, - { 0x0A, (byte)ExprToken.LabelTable }, - { 0x0B, (byte)ExprToken.Assert }, - { 0x0C, (byte)ExprToken.Case }, - { 0x0D, (byte)ExprToken.EatString }, - { 0x0E, (byte)ExprToken.Let }, - { 0x0F, (byte)ExprToken.GotoLabel }, - { 0x10, (byte)ExprToken.DynArrayElement }, - { 0x11, (byte)ExprToken.New }, - { 0x12, (byte)ExprToken.ClassContext }, - { 0x13, (byte)ExprToken.MetaCast }, - { 0x14, (byte)ExprToken.LetBool }, - { 0x15, (byte)ExprToken.EndFunctionParms }, - { 0x16, (byte)ExprToken.Skip }, - { 0x17, (byte)ExprToken.Unused }, - { 0x18, (byte)ExprToken.Context }, - { 0x19, (byte)ExprToken.Self }, - { 0x1A, (byte)ExprToken.FinalFunction }, - { 0x1B, (byte)ExprToken.ArrayElement }, - { 0x1C, (byte)ExprToken.IntConst }, - { 0x1D, (byte)ExprToken.FloatConst }, - { 0x1E, (byte)ExprToken.StringConst }, - { 0x1F, (byte)ExprToken.VirtualFunction }, - { 0x20, (byte)ExprToken.IntOne }, - { 0x21, (byte)ExprToken.VectorConst }, - { 0x22, (byte)ExprToken.NameConst }, - { 0x23, (byte)ExprToken.IntZero }, - { 0x24, (byte)ExprToken.ObjectConst }, - { 0x25, (byte)ExprToken.ByteConst }, - { 0x26, (byte)ExprToken.RotationConst }, - { 0x27, (byte)ExprToken.False }, - { 0x28, (byte)ExprToken.True }, - { 0x29, (byte)ExprToken.NoObject }, - { 0x2A, (byte)ExprToken.NativeParm }, - { 0x2B, (byte)ExprToken.Unused }, - { 0x2C, (byte)ExprToken.BoolVariable }, - { 0x2D, (byte)ExprToken.Iterator }, - { 0x2E, (byte)ExprToken.IntConstByte }, - { 0x2F, (byte)ExprToken.DynamicCast }, - { 0x30, (byte)ExprToken.Unused }, - { 0x31, (byte)ExprToken.StructCmpNE }, - { 0x32, (byte)ExprToken.UniStringConst }, - { 0x33, (byte)ExprToken.IteratorNext }, - { 0x34, (byte)ExprToken.StructCmpEq }, - { 0x35, (byte)ExprToken.IteratorPop }, - { 0x36, (byte)ExprToken.GlobalFunction }, - { 0x37, (byte)ExprToken.StructMember }, - { 0x38, (byte)ExprToken.PrimitiveCast }, - { 0x39, (byte)ExprToken.DynArrayLength }, - { 0x3A, (byte)ExprToken.Unused }, - { 0x3B, (byte)ExprToken.Unused }, - { 0x3C, (byte)ExprToken.Unused }, - { 0x3D, (byte)ExprToken.Unused }, - { 0x3E, (byte)ExprToken.Unused }, - { 0x3F, (byte)ExprToken.Unused }, - { 0x40, (byte)ExprToken.Unused }, - { 0x41, (byte)ExprToken.EndOfScript }, - { 0x42, (byte)ExprToken.DynArrayRemove }, - { 0x43, (byte)ExprToken.DynArrayInsert }, - { 0x44, (byte)ExprToken.DelegateFunction }, - { 0x45, (byte)ExprToken.DebugInfo }, - { 0x46, (byte)ExprToken.LetDelegate }, - { 0x47, (byte)ExprToken.DelegateProperty }, - { 0x48, (byte)ExprToken.Unused }, - { 0x49, (byte)ExprToken.Unused }, - { 0x4A, (byte)ExprToken.Unused }, - { 0x4B, (byte)ExprToken.Unused }, - { 0x4C, (byte)ExprToken.Unused }, - { 0x4D, (byte)ExprToken.Unused }, - { 0x4E, (byte)ExprToken.Unused }, - { 0x4F, (byte)ExprToken.Unused }, - { 0x50, (byte)ExprToken.Unused }, - { 0x51, (byte)ExprToken.Unused }, - { 0x52, (byte)ExprToken.Unused }, - { 0x53, (byte)ExprToken.Unused }, - { 0x54, (byte)ExprToken.Unused }, - { 0x55, (byte)ExprToken.Unused }, - { 0x56, (byte)ExprToken.Unused }, - { 0x57, (byte)ExprToken.Unused }, - { 0x58, (byte)ExprToken.Unused }, - { 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 - { - { (byte)ExprToken.Return, (byte)ExprToken.LocalVariable }, - { (byte)ExprToken.LocalVariable, (byte)ExprToken.Return }, - { (byte)ExprToken.Jump, (byte)ExprToken.JumpIfNot }, - { (byte)ExprToken.JumpIfNot, (byte)ExprToken.Jump }, - { (byte)ExprToken.Case, (byte)ExprToken.Nothing }, - { (byte)ExprToken.Nothing, (byte)ExprToken.Case }, - //{ 0x48, (byte)ExprToken.OutVariable }, - //{ 0x53, (byte)ExprToken.EndOfScript } - }; -#endif -#if BIOSHOCK - private static readonly Dictionary ByteCodeMap_BuildBs = new Dictionary - { - //{ (byte)ExprToken.OutVariable, (byte)ExprToken.LogFunction } - }; -#endif - private void SetupByteCodeMap() + internal void AlignNameSize() { -#if AA2 - if (Package.Build == UnrealPackage.GameBuild.BuildName.AA2) - { - if (Package.LicenseeVersion >= 33) - { - _ByteCodeMap = ByteCodeMap_BuildAa2_8; - } - else - { - // FIXME: The byte-code shifted as of V2.6, but there is no way to tell which game-version the package was compiled with. - // This hacky-solution also doesn't handle UState nor UClass cases. - // This can be solved by moving the byte-code maps to their own NTL files. - - // Flags - //sizeof(uint) + - // Oper - //sizeof(byte) - // NativeToken - //sizeof(ushort) + - // EndOfScript ExprToken - //sizeof(byte), - long functionBacktrackLength = -8; - if (_Container is UFunction function && function.HasFunctionFlag(Flags.FunctionFlags.Net)) - // RepOffset - functionBacktrackLength -= sizeof(ushort); - - Buffer.StartPeek(); - Buffer.Seek(functionBacktrackLength, SeekOrigin.End); - byte valueOfEndOfScriptToken = Buffer.ReadByte(); - Buffer.EndPeek(); - - // Shifted? - if (valueOfEndOfScriptToken != (byte)ExprToken.FunctionEnd) - _ByteCodeMap = ByteCodeMap_BuildAa2_6; - } - - 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) - _ByteCodeMap = ByteCodeMap_BuildApb; -#endif -#if BIOSHOCK - if (Package.Build == UnrealPackage.GameBuild.BuildName.BioShock) - { - _ByteCodeMap = ByteCodeMap_BuildBs; - } -#endif -#if MOH - if (Package.Build == UnrealPackage.GameBuild.BuildName.MOH) - { - // TODO: Incomplete byte-code map - _ByteCodeMap = new Dictionary - { - { 0x0C, (byte)ExprToken.EmptyParmValue }, - { 0x1D, (byte)ExprToken.FinalFunction }, - { 0x16, (byte)ExprToken.EndOfScript }, - { 0x18, (byte)ExprToken.StringConst }, - { 0x23, (byte)ExprToken.EndFunctionParms }, - { 0x28, (byte)ExprToken.Nothing }, - { 0x2C, (byte)ExprToken.Let }, - { 0x31, (byte)ExprToken.LocalVariable }, - { 0x34, (byte)ExprToken.JumpIfNot }, - // Incremented by 1 to adjust to the UE3 shift - { 0x36, (byte)ExprToken.Return }, - { 0x38, (byte)ExprToken.ReturnNothing }, - { 0x40, (byte)ExprToken.PrimitiveCast }, - { 0x47, (byte)ExprToken.StructMember }, - { 0x4B, (byte)ExprToken.NativeParm }, - //{ 0x4F, (byte)ExprToken.BoolVariable } - }; - } -#endif + ScriptPosition += _NameMemorySize; } - /// - /// Fix the values of UE1/UE2 tokens to match the UE3 token values. - /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - private byte FixToken(byte tokenCode) + internal void AlignObjectSize() { -#if UE3 - // Adjust UE2 tokens to UE3 - // TODO: Use ByteCodeMap - if (Package.Version >= 184 - && - ( - tokenCode >= (byte)ExprToken.RangeConst && tokenCode < (byte)ExprToken.ReturnNothing - || - tokenCode > (byte)ExprToken.NoDelegate && tokenCode < (byte)ExprToken.ExtendedNative) - ) ++tokenCode; -#endif - // TODO: Map directly to a Token type instead of a byte-code. - if (_ByteCodeMap != null) - return _ByteCodeMap.TryGetValue(tokenCode, out byte newTokenCode) - ? newTokenCode - : tokenCode; -#if UE1 - if (Package.Version < 62) - { - switch (tokenCode) - { - //case (byte)ExprToken.LetBool: - // return (byte)ExprToken.BeginFunction; - - case (byte)ExprToken.EndParmValue: - return (byte)ExprToken.EatReturnValue; - } - } -#endif - return tokenCode; + ScriptPosition += _ObjectMemorySize; } private bool _WasDeserialized; @@ -471,31 +115,30 @@ public void Deserialize() return; _WasDeserialized = true; + CurrentTokenIndex = -1; + DeserializedTokens = new List(); + _Labels = new List(); + ScriptPosition = 0; try { _Container.EnsureBuffer(); - Buffer.Seek(_Container.ScriptOffset, SeekOrigin.Begin); - CodePosition = 0; - int codeSize = _Container.ByteScriptSize; - - CurrentTokenIndex = -1; - DeserializedTokens = new List(); - _Labels = new List(); - - while (CodePosition < codeSize) + _Buffer = _Container.Buffer; + _Buffer.Seek(_Container.ScriptOffset, SeekOrigin.Begin); + int scriptSize = _Container.ByteScriptSize; + while (ScriptPosition < scriptSize) try { DeserializeNext(); } catch (EndOfStreamException error) { - Console.WriteLine("Couldn't backup from this error! Decompiling aborted!"); + Console.Error.WriteLine("Couldn't backup from this error! Decompiling aborted!"); break; } catch (SystemException e) { Console.WriteLine("Object:" + _Container.Name); - Console.WriteLine("Failed to deserialize token at position:" + CodePosition); + Console.WriteLine("Failed to deserialize token at position:" + ScriptPosition); Console.WriteLine("Exception:" + e.Message); Console.WriteLine("Stack:" + e.StackTrace); } @@ -506,845 +149,78 @@ public void Deserialize() } } - [MethodImpl(MethodImplOptions.AggressiveInlining)] private void DeserializeDebugToken() { // Sometimes we may end up at the end of a script // -- and by coincidence pickup a DebugInfo byte-code outside of the script-boundary. - if (CodePosition == _Container.ByteScriptSize) + if (ScriptPosition+sizeof(byte)+sizeof(int) >= _Container.ByteScriptSize) return; + + long p = _Buffer.Position; + byte opCode = _Buffer.ReadByte(); + + // Let's avoid converting native calls to a token type ;D + if (opCode >= _TokenFactory.ExtendedNative) { + _Buffer.Position = p; return; } - - Buffer.StartPeek(); - byte tokenCode = FixToken(Buffer.ReadByte()); - Buffer.EndPeek(); - - if (tokenCode == (byte)ExprToken.DebugInfo) DeserializeNext(); - } - private NativeFunctionToken CreateNativeToken(ushort nativeIndex) - { - var nativeTableItem = _Container.Package.NTLPackage?.FindTableItem(nativeIndex) ?? new NativeTableItem + int version = 0; + + var tokenType = _TokenFactory.GetTokenTypeFromOpCode(opCode); + if (tokenType == typeof(DebugInfoToken)) { - Type = FunctionType.Function, - Name = $"__NFUN_{nativeIndex}__", - ByteToken = nativeIndex - }; - return new NativeFunctionToken + // Sometimes we may catch a false positive, + // e.g. A FinalFunction within an Iterator may expect a debug token and by mere coincidence match the Iterator's CodeOffset. + // So let's verify the next 4 bytes too. + version = _Buffer.ReadInt32(); + } + + _Buffer.Position = p; + if (version == 100) { - NativeItem = nativeTableItem - }; + // Expecting a DebugInfo token. + DeserializeNext(); + } } - private Token DeserializeNext(byte tokenCode = byte.MaxValue) + private Token DeserializeNextOpCodeToToken() { - int tokenPosition = CodePosition; - if (tokenCode == byte.MaxValue) - { - tokenCode = Buffer.ReadByte(); - AlignSize(sizeof(byte)); - } + byte opCode = _Buffer.ReadByte(); + AlignSize(sizeof(byte)); - byte serializedByte = tokenCode; - Token token = null; - if (tokenCode >= (byte)ExprToken.ExtendedNative) + if (opCode < _TokenFactory.ExtendedNative) return _TokenFactory.CreateToken(opCode); + if (opCode >= _TokenFactory.FirstNative) { - if (tokenCode >= (byte)ExprToken.FirstNative) - { - token = CreateNativeToken(tokenCode); - } - else - { - byte extendedByte = Buffer.ReadByte(); - AlignSize(sizeof(byte)); - - var nativeIndex = (ushort)(((tokenCode - (byte)ExprToken.ExtendedNative) << 8) | extendedByte); - Debug.Assert(nativeIndex < (ushort)ExprToken.MaxNative); - token = CreateNativeToken(nativeIndex); - } + return _TokenFactory.CreateNativeToken(opCode); } - else - { - tokenCode = FixToken(tokenCode); - switch (tokenCode) - { - #region Cast - - case (byte)ExprToken.DynamicCast: - token = new DynamicCastToken(); - break; - - case (byte)ExprToken.MetaCast: - token = new MetaCastToken(); - break; - - case (byte)ExprToken.InterfaceCast: - if (Buffer.Version < PrimitveCastVersion) // UE1 - token = new IntToStringToken(); - else - token = new InterfaceCastToken(); - - break; - - // Redefined, can be RotatorToVector!(UE1) - case (byte)ExprToken.PrimitiveCast: - if (Buffer.Version < PrimitveCastVersion) // UE1 - { - token = new RotatorToVectorToken(); - } - else // UE2+ - { - // Next byte represents the CastToken! - tokenCode = Buffer.ReadByte(); - AlignSize(sizeof(byte)); - token = DeserializeCastToken(tokenCode); - } - - break; - - #endregion - - #region Context - case (byte)ExprToken.ClassContext: - token = new ClassContextToken(); - break; - - case (byte)ExprToken.InterfaceContext: - if (Buffer.Version < PrimitveCastVersion) - token = new ByteToStringToken(); - else - token = new InterfaceContextToken(); - - break; - - case (byte)ExprToken.Context: - token = new ContextToken(); - break; + byte opCodeExtension = _Buffer.ReadByte(); + AlignSize(sizeof(byte)); - case (byte)ExprToken.StructMember: - token = new StructMemberToken(); - break; - - #endregion - - #region Assigns - - case (byte)ExprToken.Let: - token = new LetToken(); - break; - - case (byte)ExprToken.LetBool: -#if UE1 - if (Buffer.Version < 62) - { - token = new BeginFunctionToken(); - break; - } -#endif - token = new LetBoolToken(); - break; - - case (byte)ExprToken.EndParmValue: -#if UNREAL2 - // FIXME: Not per se Unreal 2 specific, might be an old UE2 relict, however I cannot attest this token in UT99, RS3, nor UT2004. - if (Buffer.Package.Build == UnrealPackage.GameBuild.BuildName.Unreal2 || - Buffer.Package.Build == UnrealPackage.GameBuild.BuildName.Unreal2XMP) - { - token = new LineNumberToken(); - break; - } -#endif - token = new EndParmValueToken(); - break; - - // Redefined, can be FloatToBool!(UE1) - case (byte)ExprToken.LetDelegate: - if (Buffer.Version < PrimitveCastVersion) - token = new FloatToBoolToken(); - else - token = new LetDelegateToken(); - - break; - - // Redefined, can be NameToBool!(UE1) - case (byte)ExprToken.Conditional: - token = new ConditionalToken(); - break; - - case (byte)ExprToken.Eval - : // case (byte)ExprToken.DynArrayFindStruct: case (byte)ExprToken.Conditional: - if (Buffer.Version < PrimitveCastVersion) - token = new NameToBoolToken(); - else if (Buffer.Version >= 300) - token = new DynamicArrayFindStructToken(); - else - token = new ConditionalToken(); - - break; - - #endregion - - #region Jumps - - case (byte)ExprToken.Return: - token = new ReturnToken(); - break; - - case (byte)ExprToken.ReturnNothing: - if (Buffer.Version < PrimitveCastVersion) - token = new ByteToIntToken(); - // Definitely existed since GoW(490) - else if (Buffer.Version > 420 && DeserializedTokens.Count > 0 && - !(DeserializedTokens[DeserializedTokens.Count - 1] is - ReturnToken)) // Should only be done if the last token wasn't Return - token = new DynamicArrayInsertToken(); - else - token = new ReturnNothingToken(); - - break; - - case (byte)ExprToken.GotoLabel: - token = new GoToLabelToken(); - break; - - case (byte)ExprToken.Jump: - token = new JumpToken(); - break; - - case (byte)ExprToken.JumpIfNot: - token = new JumpIfNotToken(); - break; - - case (byte)ExprToken.Switch: - token = new SwitchToken(); - break; - - case (byte)ExprToken.Case: - token = new CaseToken(); - break; - - case (byte)ExprToken.DynArrayIterator: - if (Buffer.Version < PrimitveCastVersion) - token = new RotatorToStringToken(); - else - token = new ArrayIteratorToken(); - - break; - - case (byte)ExprToken.Iterator: - token = new IteratorToken(); - break; - - case (byte)ExprToken.IteratorNext: - token = new IteratorNextToken(); - break; - - case (byte)ExprToken.IteratorPop: - token = new IteratorPopToken(); - break; - - case (byte)ExprToken.FilterEditorOnly: - token = new FilterEditorOnlyToken(); - break; - - #endregion - - #region Variables - - case (byte)ExprToken.NativeParm: - token = new NativeParameterToken(); - break; - - // Referenced variables that are from this function e.g. Local and params - case (byte)ExprToken.InstanceVariable: - token = new InstanceVariableToken(); - break; - - case (byte)ExprToken.LocalVariable: - token = new LocalVariableToken(); - break; - - case (byte)ExprToken.StateVariable: - token = new StateVariableToken(); - break; - - // Referenced variables that are default - case (byte)ExprToken.UndefinedVariable: -#if BORDERLANDS2 - if (_Container.Package.Build == UnrealPackage.GameBuild.BuildName.Borderlands2) - { - token = new DynamicVariableToken(); - break; - } -#endif - token = new UndefinedVariableToken(); - break; - - case (byte)ExprToken.DefaultVariable: - token = new DefaultVariableToken(); - break; - - // UE3+ - case (byte)ExprToken.OutVariable: -#if BIOSHOCK - if (Package.Build == UnrealPackage.GameBuild.BuildName.BioShock) - { - token = new LogFunctionToken(); - break; - } -#endif - token = new OutVariableToken(); - break; - - case (byte)ExprToken.BoolVariable: - token = new BoolVariableToken(); - break; - - // Redefined, can be FloatToInt!(UE1) - case (byte)ExprToken.DelegateProperty: - if (Buffer.Version < PrimitveCastVersion) - token = new FloatToIntToken(); - else - token = new DelegatePropertyToken(); - - break; - - case (byte)ExprToken.DefaultParmValue: - if (Buffer.Version < PrimitveCastVersion) // StringToInt - token = new StringToIntToken(); - else - token = new DefaultParameterToken(); - - break; - - #endregion - - #region Misc - - // Redefined, can be BoolToFloat!(UE1) - case (byte)ExprToken.DebugInfo: - if (Buffer.Version < PrimitveCastVersion) - token = new BoolToFloatToken(); - else - token = new DebugInfoToken(); - - break; - - case (byte)ExprToken.Nothing: - token = new NothingToken(); - break; - - case (byte)ExprToken.EndFunctionParms: - token = new EndFunctionParmsToken(); - break; - - case (byte)ExprToken.IntZero: - token = new IntZeroToken(); - break; - - case (byte)ExprToken.IntOne: - token = new IntOneToken(); - break; - - case (byte)ExprToken.True: - token = new TrueToken(); - break; - - case (byte)ExprToken.False: - token = new FalseToken(); - break; - - case (byte)ExprToken.NoDelegate: - if (Buffer.Version < PrimitveCastVersion) - token = new IntToFloatToken(); - else - token = new NoDelegateToken(); - - break; - - // No value passed to an optional parameter. - case (byte)ExprToken.EmptyParmValue: - token = new NoParmToken(); - break; - - case (byte)ExprToken.NoObject: - token = new NoObjectToken(); - break; - - case (byte)ExprToken.Self: - token = new SelfToken(); - break; - - // End of state code. - case (byte)ExprToken.Stop: - token = new StopToken(); - break; - - case (byte)ExprToken.Assert: - token = new AssertToken(); - break; - - case (byte)ExprToken.LabelTable: - token = new LabelTableToken(); - break; - - case (byte)ExprToken.EndOfScript: //CastToken.BoolToString: - if (Buffer.Version < PrimitveCastVersion) - token = new BoolToStringToken(); - else - token = new EndOfScriptToken(); - - break; - - case (byte)ExprToken.Skip: - token = new SkipToken(); - break; - - case (byte)ExprToken.StructCmpEq: - token = new StructCmpEqToken(); - break; - - case (byte)ExprToken.StructCmpNE: - token = new StructCmpNeToken(); - break; - - case (byte)ExprToken.DelegateCmpEq: - token = new DelegateCmpEqToken(); - break; - - case (byte)ExprToken.DelegateFunctionCmpEq: - if (Buffer.Version < PrimitveCastVersion) - token = new IntToBoolToken(); - else - token = new DelegateFunctionCmpEqToken(); - - break; - - case (byte)ExprToken.DelegateCmpNE: - token = new DelegateCmpNEToken(); - break; - - case (byte)ExprToken.DelegateFunctionCmpNE: - if (Buffer.Version < PrimitveCastVersion) - token = new IntToBoolToken(); - else - token = new DelegateFunctionCmpNEToken(); - - break; - - case (byte)ExprToken.InstanceDelegate: - token = new InstanceDelegateToken(); - break; - - case (byte)ExprToken.EatReturnValue: - token = new EatReturnValueToken(); - break; - - case (byte)ExprToken.New: - token = new NewToken(); - break; - - case (byte)ExprToken.FunctionEnd: - if (Buffer.Version < 300) - token = new EndOfScriptToken(); - else - token = new DynamicArrayFindToken(); - - break; - - case (byte)ExprToken.VarInt: - case (byte)ExprToken.VarFloat: - case (byte)ExprToken.VarByte: - case (byte)ExprToken.VarBool: - //case (byte)ExprToken.VarObject: // See UndefinedVariable - token = new DynamicVariableToken(); - break; -#if UE1 - case (byte)ExprToken.CastStringSize: - // FIXME: Version, just a safe guess. - if (Buffer.Version >= 70) - { - token = new UnresolvedToken(); - break; - } - token = new CastStringSizeToken(); - break; -#endif - #endregion - - #region Constants - - case (byte)ExprToken.IntConst: - token = new IntConstToken(); - break; - - case (byte)ExprToken.ByteConst: - token = new ByteConstToken(); - break; - - case (byte)ExprToken.IntConstByte: - token = new IntConstByteToken(); - break; - - case (byte)ExprToken.FloatConst: - token = new FloatConstToken(); - break; - - // ClassConst? - case (byte)ExprToken.ObjectConst: - token = new ObjectConstToken(); - break; - - case (byte)ExprToken.NameConst: - token = new NameConstToken(); - break; - - case (byte)ExprToken.StringConst: - token = new StringConstToken(); - break; - - case (byte)ExprToken.UniStringConst: - token = new UniStringConstToken(); - break; - - case (byte)ExprToken.RotationConst: - token = new RotationConstToken(); - break; - - case (byte)ExprToken.VectorConst: - token = new VectorConstToken(); - break; - - case (byte)ExprToken.RangeConst: - token = new RangeConstToken(); - break; - - #endregion - - #region Functions - - case (byte)ExprToken.FinalFunction: - token = new FinalFunctionToken(); - break; - - case (byte)ExprToken.VirtualFunction: - token = new VirtualFunctionToken(); - break; - - case (byte)ExprToken.GlobalFunction: - token = new GlobalFunctionToken(); - break; - - // Redefined, can be FloatToByte!(UE1) - case (byte)ExprToken.DelegateFunction: - if (Buffer.Version < PrimitveCastVersion) - token = new FloatToByteToken(); - else - token = new DelegateFunctionToken(); - - break; - - #endregion - - #region Arrays - - case (byte)ExprToken.ArrayElement: - token = new ArrayElementToken(); - break; - - case (byte)ExprToken.DynArrayElement: - token = new DynamicArrayElementToken(); - break; - - case (byte)ExprToken.DynArrayLength: - token = new DynamicArrayLengthToken(); - break; - - case (byte)ExprToken.DynArrayInsert: - if (Buffer.Version < PrimitveCastVersion) - token = new BoolToByteToken(); - else - token = new DynamicArrayInsertToken(); - - break; - - case (byte)ExprToken.DynArrayInsertItem: - if (Buffer.Version < PrimitveCastVersion) - token = new VectorToStringToken(); - else - token = new DynamicArrayInsertItemToken(); - - break; - - // Redefined, can be BoolToInt!(UE1) - case (byte)ExprToken.DynArrayRemove: - if (Buffer.Version < PrimitveCastVersion) - token = new BoolToIntToken(); - else - token = new DynamicArrayRemoveToken(); - - break; - - case (byte)ExprToken.DynArrayRemoveItem: - if (Buffer.Version < PrimitveCastVersion) - token = new NameToStringToken(); - else - token = new DynamicArrayRemoveItemToken(); - - break; - - case (byte)ExprToken.DynArrayAdd: - if (Buffer.Version < PrimitveCastVersion) - token = new FloatToStringToken(); - else - token = new DynamicArrayAddToken(); - - break; - - case (byte)ExprToken.DynArrayAddItem: - if (Buffer.Version < PrimitveCastVersion) - token = new ObjectToStringToken(); - else - token = new DynamicArrayAddItemToken(); - - break; - - case (byte)ExprToken.DynArraySort: - token = new DynamicArraySortToken(); - break; - - // See FunctionEnd and Eval - /*case (byte)ExprToken.DynArrayFind: - break; - - case (byte)ExprToken.DynArrayFindStruct: - break;*/ - - #endregion - - default: - { - #region Casts - - if (Buffer.Version < PrimitveCastVersion) - // No other token was matched. Check if it matches any of the CastTokens - // We don't just use PrimitiveCast detection due compatible with UE1 games - token = DeserializeCastToken(tokenCode); - - break; - - #endregion - } - } - } -#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; - - case 0x50: - token = new Bm4ContextToken(); - break; - } - } -#endif - if (token == null) - { - token = new UnresolvedToken(); - } - AddToken(token, serializedByte, tokenPosition); - return token; + var nativeIndex = (ushort)(((opCode - _TokenFactory.ExtendedNative) << 8) | opCodeExtension); + Debug.Assert(nativeIndex < (ushort)ExprToken.MaxNative); + return _TokenFactory.CreateNativeToken(nativeIndex); } - private void AddToken(Token token, byte tokenCode, int tokenPosition) + private Token DeserializeNext() { + int scriptPosition = ScriptPosition; + var token = DeserializeNextOpCodeToToken(); + Debug.Assert(token != null); + DeserializedTokens.Add(token); token.Decompiler = this; - token.RepresentToken = tokenCode; - token.Position = (uint)tokenPosition; // + (uint)Owner._ScriptOffset; - token.StoragePosition = (uint)Buffer.Position - (uint)_Container.ScriptOffset - 1; - token.Deserialize(Buffer); - // Includes all sizes of followed tokens as well! e.g. i = i + 1; is summed here but not i = i +1; (not>>)i ++; - token.Size = (ushort)(CodePosition - tokenPosition); - token.StorageSize = - (ushort)(Buffer.Position - _Container.ScriptOffset - token.StoragePosition); + token.Position = scriptPosition; + token.StoragePosition = (int)(_Buffer.Position - _Container.ScriptOffset - 1); + token.Deserialize(_Buffer); + token.Size = (short)(ScriptPosition - scriptPosition); + token.StorageSize = (short)(_Buffer.Position - _Container.ScriptOffset - token.StoragePosition); token.PostDeserialized(); - } - - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")] - private Token DeserializeCastToken(byte castToken) - { - Token token; - switch ((Tokens.CastToken)castToken) - { - case Tokens.CastToken.StringToRotator: - token = new StringToRotatorToken(); - break; - - case Tokens.CastToken.VectorToRotator: - token = new VectorToRotatorToken(); - break; - - case Tokens.CastToken.StringToVector: - token = new StringToVectorToken(); - break; - - case Tokens.CastToken.RotatorToVector: - token = new RotatorToVectorToken(); - break; - - case Tokens.CastToken.IntToFloat: - token = new IntToFloatToken(); - break; - - case Tokens.CastToken.StringToFloat: - token = new StringToFloatToken(); - break; - - case Tokens.CastToken.BoolToFloat: - token = new BoolToFloatToken(); - break; - - case Tokens.CastToken.StringToInt: - token = new StringToIntToken(); - break; - - case Tokens.CastToken.FloatToInt: - token = new FloatToIntToken(); - break; - - case Tokens.CastToken.BoolToInt: - token = new BoolToIntToken(); - break; - - case Tokens.CastToken.RotatorToBool: - token = new RotatorToBoolToken(); - break; - - case Tokens.CastToken.VectorToBool: - token = new VectorToBoolToken(); - break; - - case Tokens.CastToken.StringToBool: - token = new StringToBoolToken(); - break; - - case Tokens.CastToken.ByteToBool: - token = new ByteToBoolToken(); - break; - - case Tokens.CastToken.FloatToBool: - token = new FloatToBoolToken(); - break; - - case Tokens.CastToken.NameToBool: - token = new NameToBoolToken(); - break; - - case Tokens.CastToken.ObjectToBool: - token = new ObjectToBoolToken(); - break; - - case Tokens.CastToken.IntToBool: - token = new IntToBoolToken(); - break; - - case Tokens.CastToken.StringToByte: - token = new StringToByteToken(); - break; - - case Tokens.CastToken.FloatToByte: - token = new FloatToByteToken(); - break; - - case Tokens.CastToken.BoolToByte: - token = new BoolToByteToken(); - break; - - case Tokens.CastToken.ByteToString: - token = new ByteToStringToken(); - break; - - case Tokens.CastToken.IntToString: - token = new IntToStringToken(); - break; - - case Tokens.CastToken.BoolToString: - token = new BoolToStringToken(); - break; - - case Tokens.CastToken.FloatToString: - token = new FloatToStringToken(); - break; - - case Tokens.CastToken.NameToString: - token = new NameToStringToken(); - break; - - case Tokens.CastToken.VectorToString: - token = new VectorToStringToken(); - break; - - case Tokens.CastToken.RotatorToString: - token = new RotatorToStringToken(); - break; - - case Tokens.CastToken.StringToName: - token = new StringToNameToken(); - break; - - case Tokens.CastToken.ByteToInt: - token = new ByteToIntToken(); - break; - - case Tokens.CastToken.IntToByte: - token = new IntToByteToken(); - break; - - case Tokens.CastToken.ByteToFloat: - token = new ByteToFloatToken(); - break; - - case Tokens.CastToken.ObjectToString: - token = new ObjectToStringToken(); - break; - - case Tokens.CastToken.InterfaceToString: - token = new InterfaceToStringToken(); - break; - - case Tokens.CastToken.InterfaceToBool: - token = new InterfaceToBoolToken(); - break; - - case Tokens.CastToken.InterfaceToObject: - token = new InterfaceToObjectToken(); - break; - - case Tokens.CastToken.ObjectToInterface: - token = new ObjectToInterfaceToken(); - break; - - case Tokens.CastToken.DelegateToString: - token = new DelegateToStringToken(); - break; - - default: - token = new UnresolvedCastToken(); - break; - } - + return token; } - -#endregion + #endregion #if DECOMPILE @@ -1373,7 +249,7 @@ public enum NestType : byte /// /// Position of this Nest (CodePosition) /// - public uint Position; + public int Position; public NestType Type; public Token Creator; @@ -1426,14 +302,14 @@ public override string Decompile() public readonly List Nests = new List(); - public void AddNest(Nest.NestType type, uint position, uint endPosition, Token creator = null) + public void AddNest(Nest.NestType type, int position, int endPosition, Token creator = null) { creator = creator ?? Decompiler.CurrentToken; Nests.Add(new NestBegin { Position = position, Type = type, Creator = creator }); Nests.Add(new NestEnd { Position = endPosition, Type = type, Creator = creator }); } - public NestBegin AddNestBegin(Nest.NestType type, uint position, Token creator = null) + public NestBegin AddNestBegin(Nest.NestType type, int position, Token creator = null) { var n = new NestBegin { Position = position, Type = type }; Nests.Add(n); @@ -1441,7 +317,7 @@ public NestBegin AddNestBegin(Nest.NestType type, uint position, Token creator = return n; } - public NestEnd AddNestEnd(Nest.NestType type, uint position, Token creator = null) + public NestEnd AddNestEnd(Nest.NestType type, int position, Token creator = null) { var n = new NestEnd { Position = position, Type = type }; Nests.Add(n); @@ -1449,7 +325,7 @@ public NestEnd AddNestEnd(Nest.NestType type, uint position, Token creator = nul return n; } - public bool TryAddNestEnd(Nest.NestType type, uint pos) + public bool TryAddNestEnd(Nest.NestType type, int pos) { foreach (var nest in Decompiler._Nester.Nests) if (nest.Type == type && nest.Position == pos) @@ -1489,21 +365,10 @@ public void InitDecompile() _Nester = new NestManager { Decompiler = this }; CurrentTokenIndex = -1; - CodePosition = 0; + ScriptPosition = 0; FieldToken.LastField = null; - // TODO: Corrigate detection and version. - DefaultParameterToken._NextParamIndex = 0; - if (Package.Version > 300) - { - var func = _Container as UFunction; - if (func?.Params != null) - DefaultParameterToken._NextParamIndex = func.Params.FindIndex( - p => p.HasPropertyFlag(Flags.PropertyFlagsLO.OptionalParm) - ); - } - // Reset these, in case of a loop in the Decompile function that did not finish due exception errors! _IsWithinClassContext = false; _CanAddSemicolon = false; @@ -1566,6 +431,11 @@ public Token TokenAt(ushort codeOffset) public string PreComment; public string PostComment; + public void MarkSemicolon() + { + _CanAddSemicolon = true; + } + public string Decompile() { // Make sure that everything is deserialized! @@ -1585,7 +455,43 @@ public string Decompile() var spewOutput = false; var tokenEndIndex = 0; Token lastStatementToken = null; +#if !DEBUG_HIDDENTOKENS + if (_Container is UFunction func + && func.HasOptionalParamData() + && DeserializedTokens.Count > 0) + { + CurrentTokenIndex = 0; + foreach (var parm in func.Params) + { + if (!parm.HasPropertyFlag(PropertyFlagsLO.OptionalParm)) + continue; + + // Skip NothingToken (No default value) and DefaultParameterToken (up to EndParmValueToken) + switch (CurrentToken) + { + case NothingToken _: + ++CurrentTokenIndex; // NothingToken + break; + + case DefaultParameterToken _: + { + do + { + ++CurrentTokenIndex; + } while (!(CurrentToken is EndParmValueToken)); + ++CurrentTokenIndex; // EndParmValueToken + break; + } + + default: + // Can be true e.g a function with optionals but no default values + //Debug.Fail($"Unexpected token for optional parameter {parm.GetOuterGroup()}"); + break; + } + } + } +#endif while (CurrentTokenIndex + 1 < DeserializedTokens.Count) { //Decompile chain========== @@ -1832,7 +738,7 @@ private static string FormatTabs(string nonTabbedText) public static string FormatTokenInfo(Token token) { Debug.Assert(token != null); - return $"{token.GetType().Name} (0x{token.RepresentToken:X2})"; + return $"{token.GetType().Name} (0x{token.OpCode:X2})"; } private string FormatAndDecompileTokens(int beginIndex, int endIndex) @@ -1988,4 +894,4 @@ public string Disassemble() #endif } } -} \ No newline at end of file +} diff --git a/src/Core/Classes/Props/UArrayProperty.cs b/src/Core/Classes/Props/UArrayProperty.cs index ccb9d25b..04692a85 100644 --- a/src/Core/Classes/Props/UArrayProperty.cs +++ b/src/Core/Classes/Props/UArrayProperty.cs @@ -25,9 +25,8 @@ public UArrayProperty() protected override void Deserialize() { base.Deserialize(); - - int innerIndex = _Buffer.ReadObjectIndex(); - InnerProperty = (UProperty)GetIndexObject(innerIndex); + + InnerProperty = _Buffer.ReadObject(); } /// @@ -50,4 +49,4 @@ public override string GetFriendlyInnerType() : "@NULL"; } } -} \ No newline at end of file +} diff --git a/src/Core/Classes/Props/UByteProperty.cs b/src/Core/Classes/Props/UByteProperty.cs index 0a7099d7..d921154d 100644 --- a/src/Core/Classes/Props/UByteProperty.cs +++ b/src/Core/Classes/Props/UByteProperty.cs @@ -10,7 +10,7 @@ public class UByteProperty : UProperty { #region Serialized Members - public UEnum EnumObject; + public UEnum Enum; #endregion @@ -26,14 +26,20 @@ protected override void Deserialize() { base.Deserialize(); - EnumObject = _Buffer.ReadObject(); + Enum = _Buffer.ReadObject(); + Record(nameof(Enum), Enum); } public override string GetFriendlyType() { - return EnumObject != null - ? EnumObject.GetOuterGroup() - : "byte"; + if (Enum != null) + { + // The compiler doesn't understand any non-UClass qualified identifiers. + return Enum.Outer is UClass + ? $"{Enum.Outer.Name}.{Enum.Name}" + : Enum.Name; + } + return "byte"; } } -} \ No newline at end of file +} diff --git a/src/Core/Classes/Props/UClassProperty.cs b/src/Core/Classes/Props/UClassProperty.cs index 9829e945..0aa03aad 100644 --- a/src/Core/Classes/Props/UClassProperty.cs +++ b/src/Core/Classes/Props/UClassProperty.cs @@ -14,7 +14,7 @@ public class UClassProperty : UObjectProperty #region Serialized Members // MetaClass - public UClass ClassObject; + public UClass MetaClass; #endregion @@ -30,16 +30,16 @@ protected override void Deserialize() { base.Deserialize(); - int classIndex = _Buffer.ReadObjectIndex(); - ClassObject = (UClass)GetIndexObject(classIndex); + MetaClass = _Buffer.ReadObject(); + Record(nameof(MetaClass), MetaClass); } /// public override string GetFriendlyType() { - if (ClassObject != null) + if (MetaClass != null) { - return (string.Compare(ClassObject.Name, "Object", StringComparison.OrdinalIgnoreCase) == 0) + return (string.Compare(MetaClass.Name, "Object", StringComparison.OrdinalIgnoreCase) == 0) ? Object.GetFriendlyType() : ($"class<{GetFriendlyInnerType()}>"); } @@ -49,7 +49,7 @@ public override string GetFriendlyType() public override string GetFriendlyInnerType() { - return ClassObject != null ? ClassObject.GetFriendlyType() : "@NULL"; + return MetaClass != null ? MetaClass.GetFriendlyType() : "@NULL"; } } -} \ No newline at end of file +} diff --git a/src/Core/Classes/Props/UDelegateProperty.cs b/src/Core/Classes/Props/UDelegateProperty.cs index 6f0e265c..5b88aa85 100644 --- a/src/Core/Classes/Props/UDelegateProperty.cs +++ b/src/Core/Classes/Props/UDelegateProperty.cs @@ -12,8 +12,8 @@ public class UDelegateProperty : UProperty { #region Serialized Members - public UObject FunctionObject; - public UObject DelegateObject; + public UFunction Function; + public UFunction Delegate; #endregion @@ -29,10 +29,24 @@ protected override void Deserialize() { base.Deserialize(); - FunctionObject = GetIndexObject(_Buffer.ReadObjectIndex()); - if (Package.Version > 184) + Function = _Buffer.ReadObject(); + Record(nameof(Function), Function); + // FIXME: Version 128-178 + if (_Buffer.Version <= 184) { - DelegateObject = GetIndexObject(_Buffer.ReadObjectIndex()); + return; + } + + // FIXME: Version 374-491; Delegate source type changed from Name to Object + if (_Buffer.Version <= 375) + { + var source = _Buffer.ReadNameReference(); + Record(nameof(source), source); + } + else + { + Delegate = _Buffer.ReadObject(); + Record(nameof(Delegate), Delegate); } } @@ -44,7 +58,7 @@ public override string GetFriendlyType() public override string GetFriendlyInnerType() { - return FunctionObject != null ? FunctionObject.GetFriendlyType() : "@NULL"; + return Function != null ? Function.GetFriendlyType() : "@NULL"; } } -} \ No newline at end of file +} diff --git a/src/Core/Classes/Props/UFixedArrayProperty.cs b/src/Core/Classes/Props/UFixedArrayProperty.cs index 1c95451a..17f1e3db 100644 --- a/src/Core/Classes/Props/UFixedArrayProperty.cs +++ b/src/Core/Classes/Props/UFixedArrayProperty.cs @@ -10,9 +10,8 @@ public class UFixedArrayProperty : UProperty { #region Serialized Members - public UProperty InnerObject; - - public int Count { get; private set; } + public UProperty InnerProperty; + public int Count; #endregion @@ -29,16 +28,17 @@ protected override void Deserialize() { base.Deserialize(); - int innerIndex = _Buffer.ReadObjectIndex(); - InnerObject = (UProperty)GetIndexObject(innerIndex); + InnerProperty = _Buffer.ReadObject(); + Record(nameof(InnerProperty), InnerProperty); + Count = _Buffer.ReadIndex(); + Record(nameof(Count), Count); } /// public override string GetFriendlyType() { - // Just move to decompiling? - return $"{base.GetFriendlyType()}[{Count}]"; + return $"{InnerProperty.GetFriendlyType()}[{Count}]"; } } -} \ No newline at end of file +} diff --git a/src/Core/Classes/Props/UInterfaceProperty.cs b/src/Core/Classes/Props/UInterfaceProperty.cs index 25c0b229..53ea16c8 100644 --- a/src/Core/Classes/Props/UInterfaceProperty.cs +++ b/src/Core/Classes/Props/UInterfaceProperty.cs @@ -12,8 +12,7 @@ public class UInterfaceProperty : UProperty { #region Serialized Members - public UClass InterfaceObject; - //public UInterfaceProperty InterfaceType = null; + public UClass InterfaceClass; #endregion @@ -29,17 +28,14 @@ protected override void Deserialize() { base.Deserialize(); - int index = _Buffer.ReadObjectIndex(); - InterfaceObject = (UClass)GetIndexObject(index); - - //Index = _Buffer.ReadObjectIndex(); - //_InterfaceType = (UInterfaceProperty)GetIndexObject( Index ); + InterfaceClass = _Buffer.ReadObject(); + Record(nameof(InterfaceClass), InterfaceClass); } /// public override string GetFriendlyType() { - return InterfaceObject != null ? InterfaceObject.GetFriendlyType() : "@NULL"; + return InterfaceClass != null ? InterfaceClass.GetFriendlyType() : "@NULL"; } } -} \ No newline at end of file +} diff --git a/src/Core/Classes/Props/UMapProperty.cs b/src/Core/Classes/Props/UMapProperty.cs index 4fc18e81..f13f5b41 100644 --- a/src/Core/Classes/Props/UMapProperty.cs +++ b/src/Core/Classes/Props/UMapProperty.cs @@ -4,16 +4,14 @@ namespace UELib.Core { /// /// Dynamic Map Property - /// - /// Obsolete /// [UnrealRegisterClass] public class UMapProperty : UProperty { #region Serialized Members - private int _Key; - private int _Value; + public UProperty KeyProperty; + public UProperty ValueProperty; #endregion @@ -29,14 +27,22 @@ protected override void Deserialize() { base.Deserialize(); - _Key = _Buffer.ReadObjectIndex(); - _Value = _Buffer.ReadObjectIndex(); + KeyProperty = _Buffer.ReadObject(); + Record(nameof(KeyProperty), KeyProperty); + + ValueProperty = _Buffer.ReadObject(); + Record(nameof(ValueProperty), ValueProperty); } /// public override string GetFriendlyType() { - return $"map<{_Key}, {_Value}>"; + if (KeyProperty == null || ValueProperty == null) + { + return "map{VOID,VOID}"; + } + + return $"map<{KeyProperty.GetFriendlyType()}, {ValueProperty.GetFriendlyType()}>"; } } -} \ No newline at end of file +} diff --git a/src/Core/Classes/Props/UObjectProperty.cs b/src/Core/Classes/Props/UObjectProperty.cs index ecdd8c72..86cbeadf 100644 --- a/src/Core/Classes/Props/UObjectProperty.cs +++ b/src/Core/Classes/Props/UObjectProperty.cs @@ -10,7 +10,7 @@ public class UObjectProperty : UProperty { #region Serialized Members - public UObject Object { get; private set; } + public UObject Object; #endregion @@ -26,8 +26,7 @@ protected override void Deserialize() { base.Deserialize(); - int objectIndex = _Buffer.ReadObjectIndex(); - Object = GetIndexObject(objectIndex); + Object = _Buffer.ReadObject(); Record(nameof(Object), Object); } @@ -37,4 +36,4 @@ public override string GetFriendlyType() return Object != null ? Object.GetFriendlyType() : "@NULL"; } } -} \ No newline at end of file +} diff --git a/src/Core/Classes/Props/UProperty.cs b/src/Core/Classes/Props/UProperty.cs index d693582f..b416e317 100644 --- a/src/Core/Classes/Props/UProperty.cs +++ b/src/Core/Classes/Props/UProperty.cs @@ -19,7 +19,7 @@ public partial class UProperty : UField, IUnrealNetObject #region Serialized Members - public ushort ArrayDim { get; private set; } + public int ArrayDim { get; private set; } public ushort ElementSize { get; private set; } @@ -31,11 +31,12 @@ public partial class UProperty : UField, IUnrealNetObject [CanBeNull] public UName CategoryName; - [Obsolete("See CategoryName")] - public int CategoryIndex { get; } + [Obsolete("See CategoryName")] public int CategoryIndex { get; } [CanBeNull] public UEnum ArrayEnum { get; private set; } + [CanBeNull] public UName RepNotifyFuncName; + public ushort RepOffset { get; private set; } public bool RepReliable => HasPropertyFlag(PropertyFlagsLO.Net); @@ -71,50 +72,72 @@ public UProperty() protected override void Deserialize() { base.Deserialize(); -#if XIII - if (Package.Build == UnrealPackage.GameBuild.BuildName.XIII) +#if SPLINTERCELL + if (Package.Build == UnrealPackage.GameBuild.BuildName.SC1 && + _Buffer.LicenseeVersion >= 15) { - ArrayDim = _Buffer.ReadUShort(); - Record("ArrayDim", ArrayDim); - goto skipInfo; + ArrayDim = _Buffer.ReadUInt16(); + Record(nameof(ArrayDim), ArrayDim); + + PropertyFlags = _Buffer.ReadUInt32(); + Record(nameof(PropertyFlags), PropertyFlags); + + _Buffer.Read(out CategoryName); + Record(nameof(CategoryName), CategoryName); + + return; } #endif #if AA2 - if (Package.Build == UnrealPackage.GameBuild.BuildName.AA2 && Package.LicenseeVersion > 7) + if (Package.Build == BuildGeneration.AGP && + _Buffer.LicenseeVersion >= 8) { // Always 26125 (hardcoded in the assembly) - uint unknown = _Buffer.ReadUInt32(); - Record("Unknown:AA2", unknown); + uint aa2FixedPack = _Buffer.ReadUInt32(); + Record(nameof(aa2FixedPack), aa2FixedPack); } #endif - int info = _Buffer.ReadInt32(); - Record("ArrayDim&ElementSize", info); - ArrayDim = (ushort)(info & 0x0000FFFFU); - Debug.Assert(ArrayDim <= 2048, "Bad array dim"); - ElementSize = (ushort)(info >> 16); - skipInfo: +#if XIII || DNF + if (Package.Build == UnrealPackage.GameBuild.BuildName.XIII || + Package.Build == UnrealPackage.GameBuild.BuildName.DNF) + { + ArrayDim = _Buffer.ReadInt16(); + Record(nameof(ArrayDim), ArrayDim); + goto skipArrayDim; + } +#endif + ArrayDim = _Buffer.ReadInt32(); + Record(nameof(ArrayDim), ArrayDim); + ElementSize = (ushort)(ArrayDim >> 16); + skipArrayDim: + // Just to verify if this is in use at all. + Debug.Assert(ElementSize == 0, $"ElementSize: {ElementSize}"); + // 2048 is the max allowed dimension in the UnrealScript compiler, however some licensees have extended this to a much higher size. + //Debug.Assert( + // (ArrayDim & 0x0000FFFFU) > 0 && (ArrayDim & 0x0000FFFFU) <= 2048, + // $"Bad array dimension {ArrayDim & 0x0000FFFFU} for property ${GetReferencePath()}"); PropertyFlags = Package.Version >= 220 ? _Buffer.ReadUInt64() : _Buffer.ReadUInt32(); - Record("PropertyFlags", PropertyFlags); + Record(nameof(PropertyFlags), PropertyFlags); #if BATMAN if (Package.Build == BuildGeneration.RSS && - Package.LicenseeVersion >= 101) + _Buffer.LicenseeVersion >= 101) { PropertyFlags = (PropertyFlags & 0xFFFF0000) >> 24; - Record("PropertyFlags", (PropertyFlagsLO)PropertyFlags); + Record(nameof(PropertyFlags), (PropertyFlagsLO)PropertyFlags); } #endif #if XCOM2 if (Package.Build == UnrealPackage.GameBuild.BuildName.XCOM2WotC) { ConfigName = _Buffer.ReadNameReference(); - Record("ConfigName", ConfigName); + Record(nameof(ConfigName), ConfigName); } #endif #if THIEF_DS || DEUSEX_IW - if (Package.Build == BuildGeneration.Thief) + if (Package.Build == BuildGeneration.Flesh) { // Property flags like CustomEditor, CustomViewer, ThiefProp, DeusExProp, NoTextExport, NoTravel uint deusFlags = _Buffer.ReadUInt32(); @@ -123,32 +146,41 @@ protected override void Deserialize() #endif if (!Package.IsConsoleCooked()) { - CategoryName = _Buffer.ReadNameReference(); - Record(nameof(CategoryName), CategoryName); - - if (Package.Version > 400) + // FIXME: UE4 version + if (_Buffer.UE4Version < 160) { - ArrayEnum = GetIndexObject(_Buffer.ReadObjectIndex()) as UEnum; - Record("ArrayEnum", ArrayEnum); + CategoryName = _Buffer.ReadNameReference(); + Record(nameof(CategoryName), CategoryName); } - else + + if (_Buffer.Version > 400) { -#if THIEF_DS || DEUSEX_IW - if (Package.Build == BuildGeneration.Thief) - { - short deusInheritedOrRuntimeInstiantiated = _Buffer.ReadInt16(); - Record(nameof(deusInheritedOrRuntimeInstiantiated), deusInheritedOrRuntimeInstiantiated); - short deusUnkInt16= _Buffer.ReadInt16(); - Record(nameof(deusUnkInt16), deusUnkInt16); - } -#endif + ArrayEnum = _Buffer.ReadObject(); + Record(nameof(ArrayEnum), ArrayEnum); } } +#if THIEF_DS || DEUSEX_IW + if (Package.Build == BuildGeneration.Flesh) + { + short deusInheritedOrRuntimeInstiantiated = _Buffer.ReadInt16(); + Record(nameof(deusInheritedOrRuntimeInstiantiated), deusInheritedOrRuntimeInstiantiated); + short deusUnkInt16 = _Buffer.ReadInt16(); + Record(nameof(deusUnkInt16), deusUnkInt16); + } +#endif +#if UE4 + if (_Buffer.UE4Version > 0) + { + RepNotifyFuncName = _Buffer.ReadNameReference(); + Record(nameof(RepNotifyFuncName), RepNotifyFuncName); + return; + } +#endif if (HasPropertyFlag(PropertyFlagsLO.Net)) { RepOffset = _Buffer.ReadUShort(); - Record("RepOffset", RepOffset); + Record(nameof(RepOffset), RepOffset); } #if VENGEANCE if (Package.Build == BuildGeneration.Vengeance) @@ -159,13 +191,44 @@ protected override void Deserialize() Record(nameof(vengeanceEditDisplay), vengeanceEditDisplay); } #endif - // Appears to be a UE2X feature, it is not present in UE2 builds with no custom LicenseeVersion +#if DNF + if (Package.Build == UnrealPackage.GameBuild.BuildName.DNF) + { + if (HasPropertyFlag(0x800000)) + { + EditorDataText = _Buffer.ReadText(); + Record(nameof(EditorDataText), EditorDataText); + } + + // Same flag as EditorData, but this may merely be a coincidence, see above + if (_Buffer.Version >= 118 && HasPropertyFlag(0x2000000)) + { + // a.k.a NetUpdateName ;) + RepNotifyFuncName = _Buffer.ReadNameReference(); + Record(nameof(RepNotifyFuncName), RepNotifyFuncName); + } + + return; + } +#endif + // Appears to be a UE2.5 feature, it is not present in UE2 builds with no custom LicenseeVersion // Albeit DeusEx indicates otherwise? - if ((HasPropertyFlag(PropertyFlagsLO.EditorData) && (Package.Build == BuildGeneration.UE2_5 || Package.Build == BuildGeneration.Thief)) + if ((HasPropertyFlag(PropertyFlagsLO.EditorData) && + (Package.Build == BuildGeneration.UE2_5 + || Package.Build == BuildGeneration.AGP + || Package.Build == BuildGeneration.Flesh)) // No property flag - || Package.Build == BuildGeneration.Vengeance) + || Package.Build == BuildGeneration.Vengeance +#if LSGAME + || (Package.Build == UnrealPackage.GameBuild.BuildName.LSGame && + Package.LicenseeVersion >= 3) +#endif +#if DEVASTATION + || Package.Build == UnrealPackage.GameBuild.BuildName.Devastation +#endif + ) { - // May represent a tooltip/comment in some games. + // May represent a tooltip/comment in some games. Usually in the form of a quoted string, sometimes as a double-flash comment or both. EditorDataText = _Buffer.ReadText(); Record(nameof(EditorDataText), EditorDataText); } @@ -175,12 +238,12 @@ protected override void Deserialize() if (_Buffer.Version < 157) { throw new NotSupportedException("< 157 Spellborn packages are not supported"); - + if (133 < _Buffer.Version) { // idk } - + if (134 < _Buffer.Version) { int unk32 = _Buffer.ReadInt32(); @@ -205,6 +268,11 @@ protected override bool CanDisposeBuffer() #region Methods + public bool HasPropertyFlag(uint flag) + { + return ((uint)PropertyFlags & flag) != 0; + } + public bool HasPropertyFlag(PropertyFlagsLO flag) { return ((uint)(PropertyFlags & 0x00000000FFFFFFFFU) & (uint)flag) != 0; @@ -227,4 +295,4 @@ public virtual string GetFriendlyInnerType() #endregion } -} \ No newline at end of file +} diff --git a/src/Core/Classes/Props/UPropertyDecompiler.cs b/src/Core/Classes/Props/UPropertyDecompiler.cs index b5173501..4e14c173 100644 --- a/src/Core/Classes/Props/UPropertyDecompiler.cs +++ b/src/Core/Classes/Props/UPropertyDecompiler.cs @@ -11,28 +11,7 @@ public partial class UProperty // Called before the var () is printed. public virtual string PreDecompile() { - string tooltipValue = null; - MetaData?.Tags.TryGetValue("ToolTip", out tooltipValue); - if (tooltipValue == null) - { - return string.Empty; - } - - var comment = $"{UDecompilingState.Tabs}/** "; - // Multiline comment? - if (tooltipValue.IndexOf('\n') != -1) - { - comment += - " \r\n" + - $"{UDecompilingState.Tabs} *{tooltipValue.Replace("\n", "\n" + UDecompilingState.Tabs + " *")}" + - $"\r\n{UDecompilingState.Tabs}"; - } - else - { - comment += tooltipValue; - } - - return $"{comment} */\r\n"; + return FormatTooltipMetaData(); } public override string Decompile() @@ -43,7 +22,13 @@ public override string Decompile() + DecompileEditorData() + DecompileMeta(); } - + + // Post semicolon ";". + public virtual string PostDecompile() + { + return default; + } + // FIXME: Rewrite without performing this many string copies, however this part of the decompilation process is not crucial. private string DecompileEditorData() { @@ -477,6 +462,40 @@ public string FormatFlags() output += "input "; copyFlags &= ~(ulong)Flags.PropertyFlagsLO.Input; } +#if DNF + if (Package.Build == UnrealPackage.GameBuild.BuildName.DNF) + { + if (HasPropertyFlag(0x1000000)) + { + output += "nontrans "; + copyFlags &= ~(uint)0x1000000; + + } + if (HasPropertyFlag(0x8000000)) + { + output += "nocompress "; + copyFlags &= ~(uint)0x8000000; + } + + if (HasPropertyFlag(0x2000000)) + { + output += $"netupdate({RepNotifyFuncName}) "; + copyFlags &= ~(uint)0x2000000; + } + + if (HasPropertyFlag(0x4000000)) + { + output += "state "; + copyFlags &= ~(uint)0x4000000; + } + + if (HasPropertyFlag(0x100000)) + { + output += "anim "; + copyFlags &= ~(uint)0x100000; + } + } +#endif } // Local's may never output any of their implied flags! diff --git a/src/Core/Classes/Props/UStringProperty.cs b/src/Core/Classes/Props/UStringProperty.cs index 0644b513..f066424c 100644 --- a/src/Core/Classes/Props/UStringProperty.cs +++ b/src/Core/Classes/Props/UStringProperty.cs @@ -25,12 +25,13 @@ protected override void Deserialize() base.Deserialize(); Size = _Buffer.ReadInt32(); + Record(nameof(Size), Size); } /// public override string GetFriendlyType() { - return "string[" + Size + "]"; + return $"string[{Size}]"; } } -} \ No newline at end of file +} diff --git a/src/Core/Classes/Props/UStructProperty.cs b/src/Core/Classes/Props/UStructProperty.cs index f13b18f5..f42a2ef5 100644 --- a/src/Core/Classes/Props/UStructProperty.cs +++ b/src/Core/Classes/Props/UStructProperty.cs @@ -10,7 +10,7 @@ public class UStructProperty : UProperty { #region Serialized Members - public UStruct StructObject; + public UStruct Struct; #endregion @@ -26,13 +26,14 @@ protected override void Deserialize() { base.Deserialize(); - StructObject = (UStruct)GetIndexObject(_Buffer.ReadObjectIndex()); + Struct = _Buffer.ReadObject(); + Record(nameof(Struct), Struct); } /// public override string GetFriendlyType() { - return StructObject != null ? StructObject.GetFriendlyType() : "@NULL"; + return Struct != null ? Struct.GetFriendlyType() : "@NULL"; } } -} \ No newline at end of file +} diff --git a/src/Core/Classes/UClass.cs b/src/Core/Classes/UClass.cs index 40538a02..723aa3fb 100644 --- a/src/Core/Classes/UClass.cs +++ b/src/Core/Classes/UClass.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Diagnostics; using UELib.Annotations; +using UELib.Branch; using UELib.Flags; namespace UELib.Core @@ -10,26 +11,39 @@ namespace UELib.Core /// Represents a unreal class. /// [UnrealRegisterClass] - public partial class UClass : UState + public partial class UClass : UState, IUnrealExportable { + /// + /// Implements FDependency. + /// + /// A legacy dependency struct that was used for incremental compilation (UnrealEd). + /// public struct Dependency : IUnrealSerializableClass { - public int Class { get; private set; } + [NotNull] public UClass Class; + public bool IsDeep; + public uint ScriptTextCRC; public void Serialize(IUnrealStream stream) { - // TODO: Implement code + stream.Write(Class); + stream.Write(IsDeep); + stream.Write(ScriptTextCRC); } public void Deserialize(IUnrealStream stream) { - Class = stream.ReadObjectIndex(); - - // Deep - stream.ReadInt32(); - - // ScriptTextCRC - stream.ReadUInt32(); + Class = stream.ReadObject(); +#if DNF + // No specified version + if (stream.Package.Build == UnrealPackage.GameBuild.BuildName.DNF) + { + goto skipDeep; + } +#endif + IsDeep = stream.ReadBool(); + skipDeep: + ScriptTextCRC = stream.ReadUInt32(); } } @@ -37,7 +51,7 @@ public void Deserialize(IUnrealStream stream) private ulong ClassFlags { get; set; } - public Guid ClassGuid; + public UGuid ClassGuid; public UClass Within { get; private set; } public UName ConfigName { get; private set; } [CanBeNull] public UName DLLBindName; @@ -111,8 +125,9 @@ public void Deserialize(IUnrealStream stream) // TODO: Clean this mess up... protected override void Deserialize() { -#if UNREAL2 - if (Package.Build == UnrealPackage.GameBuild.BuildName.Unreal2) +#if UNREAL2 || DEVASTATION + if (Package.Build == UnrealPackage.GameBuild.BuildName.Unreal2 || + Package.Build == UnrealPackage.GameBuild.BuildName.Devastation) { _Buffer.ReadArray(out UArray u2NetProperties); Record(nameof(u2NetProperties), u2NetProperties); @@ -121,38 +136,42 @@ protected override void Deserialize() base.Deserialize(); #if VENGEANCE if (Package.Build == BuildGeneration.Vengeance && - Package.LicenseeVersion >= 36) + _Buffer.LicenseeVersion >= 36) { var header = (2, 0); VengeanceDeserializeHeader(_Buffer, ref header); } #endif - if (Package.Version < 62) + if (_Buffer.Version < 62) { int classRecordSize = _Buffer.ReadInt32(); Record(nameof(classRecordSize), classRecordSize); } #if AA2 - if (Package.Build == UnrealPackage.GameBuild.BuildName.AA2) + if (Package.Build == BuildGeneration.AGP) { uint unknownUInt32 = _Buffer.ReadUInt32(); Record("Unknown:AA2", unknownUInt32); } +#endif +#if UE4 + if (_Buffer.UE4Version > 0) + { + _Buffer.ReadMap(out FuncMap); + Record(nameof(FuncMap), FuncMap); + } #endif ClassFlags = _Buffer.ReadUInt32(); Record(nameof(ClassFlags), (ClassFlags)ClassFlags); #if SPELLBORN if (Package.Build == UnrealPackage.GameBuild.BuildName.Spellborn) { - _Buffer.ReadArray(out ClassDependencies); - Record(nameof(ClassDependencies), ClassDependencies); - PackageImports = DeserializeGroup(nameof(PackageImports)); - goto skipTo61Stuff; + goto skipClassGuid; } #endif - if (Package.Version >= 276) + if (_Buffer.Version >= 276) { - if (Package.Version < 547) + if (_Buffer.Version < 547) { byte unknownByte = _Buffer.ReadByte(); Record("ClassGuidReplacement???", unknownByte); @@ -160,19 +179,19 @@ protected override void Deserialize() } else { - ClassGuid = _Buffer.ReadGuid(); + _Buffer.ReadStruct(out ClassGuid); Record(nameof(ClassGuid), ClassGuid); } - if (Package.Version < 248) + skipClassGuid: + if (_Buffer.Version < 248) { _Buffer.ReadArray(out ClassDependencies); Record(nameof(ClassDependencies), ClassDependencies); PackageImports = DeserializeGroup(nameof(PackageImports)); } - skipTo61Stuff: - if (Package.Version >= 62) + if (_Buffer.Version >= 62) { // Class Name Extends Super.Name Within _WithinIndex // Config(_ConfigIndex); @@ -180,48 +199,79 @@ protected override void Deserialize() Record(nameof(Within), Within); ConfigName = _Buffer.ReadNameReference(); Record(nameof(ConfigName), ConfigName); +#if DNF + if (_Buffer.Package.Build == UnrealPackage.GameBuild.BuildName.DNF && + _Buffer.Version >= 102) + { + DeserializeHideCategories(); + if (_Buffer.Version >= 137) + { + _Buffer.ReadArray(out UArray dnfTags); + Record(nameof(dnfTags), dnfTags); + } + if (_Buffer.Version >= 113) + { + // Unknown purpose, used to set a global variable to 0 (GIsATablesInitialized_exref) if it reads 0. + bool dnfBool = _Buffer.ReadBool(); + Record(nameof(dnfBool), dnfBool); + + // FBitArray data, not sure if this behavior is correct, always 0. + int dnfBitArrayLength = _Buffer.ReadInt32(); + _Buffer.Skip(dnfBitArrayLength); + Record(nameof(dnfBitArrayLength), dnfBitArrayLength); + } + + goto scriptProperties; + } +#endif const int vHideCategoriesOldOrder = 539; - bool isHideCategoriesOldOrder = Package.Version <= vHideCategoriesOldOrder + bool isHideCategoriesOldOrder = _Buffer.Version <= vHideCategoriesOldOrder #if TERA || Package.Build == UnrealPackage.GameBuild.BuildName.Tera +#endif +#if TRANSFORMERS + || Package.Build == BuildGeneration.HMS #endif ; // +HideCategories - if (Package.Version >= 99) + if (_Buffer.Version >= 99) { // TODO: Corrigate Version - if (Package.Version >= 220) + if (_Buffer.Version >= 220) { // TODO: Corrigate Version - if ((isHideCategoriesOldOrder && !Package.IsConsoleCooked() && - !Package.Build.Flags.HasFlag(BuildFlags.XenonCooked)) -#if TRANSFORMERS - || Package.Build == UnrealPackage.GameBuild.BuildName.Transformers -#endif - ) + if (isHideCategoriesOldOrder && !Package.IsConsoleCooked() && + !Package.Build.Flags.HasFlag(BuildFlags.XenonCooked) && + _Buffer.UE4Version < 117) DeserializeHideCategories(); // Seems to have been removed in transformer packages - DeserializeComponentsMap(); - - // RoboBlitz(369) - // TODO: Corrigate Version - if (Package.Version >= 369) DeserializeInterfaces(); + if (_Buffer.UE4Version < 118) DeserializeComponentsMap(); } + // RoboBlitz(369) + // TODO: Corrigate Version + if (_Buffer.Version >= VInterfaceClass) DeserializeInterfaces(); +#if UE4 + if (_Buffer.UE4Version > 0) + { + var classGeneratedBy = _Buffer.ReadObject(); + Record(nameof(classGeneratedBy), classGeneratedBy); + } +#endif if (!Package.IsConsoleCooked() && !Package.Build.Flags.HasFlag(BuildFlags.XenonCooked)) { - if (Package.Version >= 603 + if (_Buffer.Version >= 603 && _Buffer.UE4Version < 113 #if TERA - && Package.Build != UnrealPackage.GameBuild.BuildName.Tera + && Package.Build != UnrealPackage.GameBuild.BuildName.Tera #endif ) DontSortCategories = DeserializeGroup("DontSortCategories"); // FIXME: Added in v99, removed in ~220? - if (Package.Version < 220 || !isHideCategoriesOldOrder) + if (_Buffer.Version < 220 || !isHideCategoriesOldOrder) { DeserializeHideCategories(); #if SPELLBORN @@ -234,129 +284,152 @@ protected override void Deserialize() } // +AutoExpandCategories - if (Package.Version >= 185) + if (_Buffer.Version >= 185) { // 490:GoW1, 576:CrimeCraft if (!HasClassFlag(Flags.ClassFlags.CollapseCategories) - || Package.Version <= vHideCategoriesOldOrder || Package.Version >= 576) + || _Buffer.Version <= vHideCategoriesOldOrder || _Buffer.Version >= 576) AutoExpandCategories = DeserializeGroup("AutoExpandCategories"); - #if TRANSFORMERS - if (Package.Build == UnrealPackage.GameBuild.BuildName.Transformers) + if (Package.Build == BuildGeneration.HMS) { - var constructorsCount = _Buffer.ReadInt32(); - Record("Constructors.Count", constructorsCount); - if (constructorsCount >= 0) - { - int numBytes = constructorsCount * 4; - AssertEOS(numBytes, "Constructors"); - _Buffer.Skip(numBytes); - } + _Buffer.ReadArray(out UArray hmsConstructors); + Record(nameof(hmsConstructors), hmsConstructors); } #endif + } - if (Package.Version > 670) - { - AutoCollapseCategories = DeserializeGroup("AutoCollapseCategories"); + if (_Buffer.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 -#endif - ) - { - // bForceScriptOrder - ForceScriptOrder = _Buffer.ReadInt32() > 0; - Record(nameof(ForceScriptOrder), ForceScriptOrder); -#if DISHONORED - if (Package.Build == UnrealPackage.GameBuild.BuildName.Dishonored) - { - var unknownName = _Buffer.ReadNameReference(); - Record("Unknown:Dishonored", unknownName); + // 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 + } - NativeClassName = _Buffer.ReadText(); - Record(nameof(NativeClassName), NativeClassName); - goto skipEditorContent; - } + if (_Buffer.Version >= (uint)PackageObjectLegacyVersion.ForceScriptOrderAddedToUClass +#if BIOSHOCK + // Partially upgraded + && Package.Build != UnrealPackage.GameBuild.BuildName.Bioshock_Infinite #endif -#if BATMAN - if (_Buffer.Package.Build == BuildGeneration.RSS && - _Buffer.Package.LicenseeVersion >= 95) - { - uint bm4_v174 = _Buffer.ReadUInt32(); - Record(nameof(bm4_v174), bm4_v174); - } -#endif - if (Package.Version >= UnrealPackage.VCLASSGROUP) - { - ClassGroups = DeserializeGroup("ClassGroups"); - } - - // No version check in batman??? - if (Package.Version >= 813) - { - NativeClassName = _Buffer.ReadText(); - Record(nameof(NativeClassName), NativeClassName); - } - } + ) + { + // bForceScriptOrder + ForceScriptOrder = _Buffer.ReadBool(); + Record(nameof(ForceScriptOrder), ForceScriptOrder); + } +#if DD2 + // DD2 doesn't use a LicenseeVersion, maybe a merged standard feature (bForceScriptOrder?). + if (Package.Build == UnrealPackage.GameBuild.BuildName.DD2 && _Buffer.Version >= 688) + { + int dd2UnkInt32 = _Buffer.ReadInt32(); + Record(nameof(dd2UnkInt32), dd2UnkInt32); + } +#endif +#if DISHONORED + if (Package.Build == UnrealPackage.GameBuild.BuildName.Dishonored) + { + var unknownName = _Buffer.ReadNameReference(); + Record("Unknown:Dishonored", unknownName); + } +#endif + if (_Buffer.Version >= UnrealPackage.VCLASSGROUP) + { +#if DISHONORED + if (Package.Build == UnrealPackage.GameBuild.BuildName.Dishonored) + { + NativeClassName = _Buffer.ReadText(); + Record(nameof(NativeClassName), NativeClassName); + goto skipClassGroups; } +#endif + ClassGroups = DeserializeGroup("ClassGroups"); + if (_Buffer.Version >= 813) + { + NativeClassName = _Buffer.ReadText(); + Record(nameof(NativeClassName), NativeClassName); + } + } +#if DISHONORED + skipClassGroups: ; +#endif - // FIXME: Found first in(V:655, DLLBind?), Definitely not in APB and GoW 2 - // TODO: Corrigate Version - if (Package.Version > 575 && Package.Version < 673 + // FIXME: Found first in(V:655, DLLBind?), Definitely not in APB and GoW 2 + // TODO: Corrigate Version + if (_Buffer.Version > 575 && _Buffer.Version < 673 #if TERA - && Package.Build != UnrealPackage.GameBuild.BuildName.Tera + && Package.Build != UnrealPackage.GameBuild.BuildName.Tera #endif - ) - { - int unknownInt32 = _Buffer.ReadInt32(); - Record("Unknown", unknownInt32); -#if SINGULARITY - if (Package.Build == UnrealPackage.GameBuild.BuildName.Singularity) _Buffer.Skip(8); +#if TRANSFORMERS + && Package.Build != BuildGeneration.HMS #endif + ) + { + int unknownInt32 = _Buffer.ReadInt32(); + Record("Unknown", unknownInt32); +#if SINGULARITY + if (Package.Build == UnrealPackage.GameBuild.BuildName.Singularity) + { + _Buffer.Skip(8); + _Buffer.ConformRecordPosition(); } +#endif } } - - skipEditorContent: - if (Package.Version >= UnrealPackage.VDLLBIND) +#if BATMAN + if (Package.Build == BuildGeneration.RSS) { - if (!Package.Build.Flags.HasFlag(BuildFlags.NoDLLBind)) + _Buffer.Skip(sizeof(int)); + if (Package.Build == UnrealPackage.GameBuild.BuildName.Batman4) { - DLLBindName = _Buffer.ReadNameReference(); - Record(nameof(DLLBindName), DLLBindName); + _Buffer.Skip(sizeof(int)); } + + _Buffer.ConformRecordPosition(); + } +#endif + if (_Buffer.Version >= UnrealPackage.VDLLBIND && _Buffer.UE4Version < 117) + { + DLLBindName = _Buffer.ReadNameReference(); + Record(nameof(DLLBindName), DLLBindName); + } #if REMEMBERME - if (Package.Build == UnrealPackage.GameBuild.BuildName.RememberMe) - { - var unknownName = _Buffer.ReadNameReference(); - Record("Unknown:RememberMe", unknownName); - } + if (Package.Build == UnrealPackage.GameBuild.BuildName.RememberMe) + { + var unknownName = _Buffer.ReadNameReference(); + Record("Unknown:RememberMe", unknownName); + } #endif #if DISHONORED - if (Package.Build == UnrealPackage.GameBuild.BuildName.Dishonored) - ClassGroups = DeserializeGroup("ClassGroups"); + if (Package.Build == UnrealPackage.GameBuild.BuildName.Dishonored) + ClassGroups = DeserializeGroup("ClassGroups"); #endif #if BORDERLANDS2 - if (Package.Build == UnrealPackage.GameBuild.BuildName.Borderlands2) - { - byte unknownByte = _Buffer.ReadByte(); - Record("Unknown:Borderlands2", unknownByte); - } -#endif + if (Package.Build == UnrealPackage.GameBuild.BuildName.Borderlands2 || + Package.Build == UnrealPackage.GameBuild.BuildName.Battleborn) + { + byte unknownByte = _Buffer.ReadByte(); + Record("Unknown:Borderlands2", unknownByte); } +#endif } } +#if UNDYING + if (Package.Build == UnrealPackage.GameBuild.BuildName.Undying && + _Buffer.Version >= 70) + { + _Buffer.Read(out uint classCRC); // v4a8 + Record(nameof(classCRC), classCRC); + } +#endif #if THIEF_DS || DeusEx_IW - if (Package.Build == BuildGeneration.Thief) + if (Package.Build == BuildGeneration.Flesh) { string thiefClassVisibleName = _Buffer.ReadText(); Record(nameof(thiefClassVisibleName), thiefClassVisibleName); @@ -365,10 +438,7 @@ protected override void Deserialize() if (!string.IsNullOrEmpty(thiefClassVisibleName) && Package.Build == UnrealPackage.GameBuild.BuildName.Thief_DS) { - var nameEntry = new UNameTableItem() - { - Name = thiefClassVisibleName - }; + var nameEntry = new UNameTableItem() { Name = thiefClassVisibleName }; NameTable.Name = nameEntry; } } @@ -376,65 +446,65 @@ protected override void Deserialize() #if VENGEANCE if (Package.Build == BuildGeneration.Vengeance) { - if (Package.LicenseeVersion >= 2) + if (_Buffer.LicenseeVersion >= 2) { ulong unkInt64 = _Buffer.ReadUInt64(); Record("Unknown:Vengeance", unkInt64); } - if (Package.LicenseeVersion >= 3) + if (_Buffer.LicenseeVersion >= 3) { ulong unkInt64 = _Buffer.ReadUInt64(); Record("Unknown:Vengeance", unkInt64); } - if (Package.LicenseeVersion >= 2) + if (_Buffer.LicenseeVersion >= 2) { string vengeanceDefaultPropertiesText = _Buffer.ReadText(); Record(nameof(vengeanceDefaultPropertiesText), vengeanceDefaultPropertiesText); } - if (Package.LicenseeVersion >= 6) + if (_Buffer.LicenseeVersion >= 6) { string vengeanceClassFilePath = _Buffer.ReadText(); Record(nameof(vengeanceClassFilePath), vengeanceClassFilePath); } - if (Package.LicenseeVersion >= 12) + if (_Buffer.LicenseeVersion >= 12) { UArray names; _Buffer.ReadArray(out names); Record("Unknown:Vengeance", names); } - if (Package.LicenseeVersion >= 15) + if (_Buffer.LicenseeVersion >= 15) { _Buffer.ReadArray(out Vengeance_Implements); Record(nameof(Vengeance_Implements), Vengeance_Implements); } - if (Package.LicenseeVersion >= 20) + if (_Buffer.LicenseeVersion >= 20) { UArray unk; _Buffer.ReadArray(out unk); Record("Unknown:Vengeance", unk); } - if (Package.LicenseeVersion >= 32) + if (_Buffer.LicenseeVersion >= 32) { UArray unk; _Buffer.ReadArray(out unk); Record("Unknown:Vengeance", unk); } - if (Package.LicenseeVersion >= 28) + if (_Buffer.LicenseeVersion >= 28) { UArray unk; _Buffer.ReadArray(out unk); Record("Unknown:Vengeance", unk); } - if (Package.LicenseeVersion >= 30) + if (_Buffer.LicenseeVersion >= 30) { int unkInt32A = _Buffer.ReadInt32(); Record("Unknown:Vengeance", unkInt32A); @@ -451,10 +521,19 @@ protected override void Deserialize() } } #endif - // In later UE3 builds, defaultproperties are stored in separated objects named DEFAULT_namehere, - // TODO: Corrigate Version - if (Package.Version >= 322) +#if UE4 + if (_Buffer.UE4Version > 0) { + string dummy = _Buffer.ReadName(); + Record("dummy", dummy); + bool isCooked = _Buffer.ReadBool(); + Record("isCooked", isCooked); + } +#endif + scriptProperties: + if (_Buffer.Version >= (uint)PackageObjectLegacyVersion.DisplacedScriptPropertiesWithClassDefaultObject) + { + // DEFAULT_ClassName Default = _Buffer.ReadObject(); Record(nameof(Default), Default); } @@ -481,6 +560,13 @@ private void DeserializeInterfaces() int typeIndex = _Buffer.ReadInt32(); Record("Implemented.TypeIndex", typeIndex); ImplementedInterfaces.Add(interfaceIndex); +#if UE4 + if (_Buffer.UE4Version > 0) + { + var isImplementedByK2 = _Buffer.ReadInt32() > 0; + Record("Implemented.isImplementedByK2", isImplementedByK2); + } +#endif } } @@ -500,6 +586,7 @@ private void DeserializeComponentsMap() int numBytes = componentsCount * 12; AssertEOS(numBytes, "Components"); _Buffer.Skip(numBytes); + _Buffer.ConformRecordPosition(); } protected override void FindChildren() @@ -564,4 +651,14 @@ public bool IsClassWithin() #endregion } -} \ No newline at end of file + + [UnrealRegisterClass] + public class UBlueprint : UField + { + } + + [UnrealRegisterClass] + public class UBlueprintGeneratedClass : UClass + { + } +} diff --git a/src/Core/Classes/UClassDecompiler.cs b/src/Core/Classes/UClassDecompiler.cs index d0335451..4530b7e5 100644 --- a/src/Core/Classes/UClassDecompiler.cs +++ b/src/Core/Classes/UClassDecompiler.cs @@ -2,8 +2,8 @@ using System.Text; using System; using System.Collections.Generic; +using System.IO; using System.Linq; -using System.Reflection; namespace UELib.Core { @@ -64,34 +64,7 @@ public partial class UClass */ public override string Decompile() { - var assembly = Assembly.GetAssembly(GetType()); - string content = string.Format - ( - "/*******************************************************************************" + - "\r\n " + - "* {3} generated by {0} using {2}." + - "\r\n " + - "* {0} {1}" + - "\r\n " + - "* http://eliotvu.com" + - "\r\n " + - "*" + - "\r\n " + - "* All rights belong to their respective owners." + - "\r\n " + - "*******************************************************************************/\r\n", - assembly.GetName().Name, - ((AssemblyCopyrightAttribute)assembly.GetCustomAttributes(typeof(AssemblyCopyrightAttribute), false)[0]) - .Copyright, -#if Forms - System.Windows.Forms.Application.ProductName, -#else - "UELib", -#endif - GetOuterGroup() - ); - - content += FormatHeader() + + string content = FormatHeader() + FormatCPPText() + FormatConstants() + FormatEnums() + @@ -105,69 +78,25 @@ public override string Decompile() return content; } + [Obsolete("Deprecated", true)] public string GetDependencies() { - if (ClassDependencies == null) - return string.Empty; - - var output = string.Empty; - foreach (var dep in ClassDependencies) - { - var obj = GetIndexObject(dep.Class); - if (obj != null) - { - output += $" *\t{obj.GetClassName()} {obj.GetOuterGroup()}\r\n"; - } - } - - return output.Length != 0 ? "Class Dependencies:\r\n" + output + " *" : string.Empty; + throw new NotImplementedException(); } + [Obsolete("Deprecated", true)] private string GetImports() { - if (PackageImports == null) - return string.Empty; - - var output = string.Empty; - foreach (int packageImport in PackageImports) - { - output += " *\t" + Package.Names[packageImport].Name + "\r\n"; - /*for( int j = 1; j < (i + i) && (j + (i + i)) < PackageImportsList.Count; ++ j ) - { - Output += " *\t\t\t" + Owner.NameTableList[PackageImportsList[i + j]].Name + "\r\n"; - } - i += i;*/ - } - - return output.Length != 0 ? "Package Imports:\r\n" + output + " *" : string.Empty; + throw new NotImplementedException(); } + [Obsolete("Deprecated", true)] public string GetStats() { - var output = string.Empty; - - if (Constants != null && Constants.Count > 0) - output += " *\tConstants:" + Constants.Count + "\r\n"; - - if (Enums != null && Enums.Count > 0) - output += " *\tEnums:" + Enums.Count + "\r\n"; - - if (Structs != null && Structs.Count > 0) - output += " *\tStructs:" + Structs.Count + "\r\n"; - - if (Variables != null && Variables.Count > 0) - output += " *\tProperties:" + Variables.Count + "\r\n"; - - if (Functions != null && Functions.Count > 0) - output += " *\tFunctions:" + Functions.Count + "\r\n"; - - if (States != null && States.Count > 0) - output += " *\tStates:" + States.Count + "\r\n"; - - return output.Length != 0 ? "Stats:\r\n" + output + " *" : string.Empty; + throw new NotImplementedException(); } - protected override string FormatHeader() + public override string FormatHeader() { string output = (IsClassInterface() ? "interface " : "class ") + Name; string metaData = DecompileMeta(); @@ -402,29 +331,51 @@ private string FormatFlags() output += $"\r\n\tdllbind({DLLBindName})"; } - if (ClassDependencies != null) + //if (ClassDependencies != null) foreach (var dependency in ClassDependencies) + //{ + // var obj = dependency.Class; + // if (obj != null && (int)obj > (int)this && obj != Super) + // { + // output += $"\r\n\tdependson({obj.Name})"; + // } + //} + + output += FormatNameGroup("dontsortcategories", DontSortCategories); + output += FormatNameGroup("hidecategories", HideCategories); + // TODO: Decompile ShowCategories (but this is not possible without traversing the super chain) + +#if DNF + if (Package.Build == UnrealPackage.GameBuild.BuildName.DNF) { - var dependsOn = new List(); - foreach (var dependency in ClassDependencies) + // FIXME: Store this data in UClass + //output += FormatNameGroup("tags", new List()); + // ...UnTags + + // Maybe dnfBool? + //if (HasClassFlag(0x1000)) + //{ + // output += "\r\n\tobsolete"; + //} + if (HasClassFlag(0x2000000)) { - if (dependsOn.Exists(dep => dep == dependency.Class)) - { - continue; - } + output += "\r\n\tnativedestructor"; + } - var obj = (UClass)GetIndexObject(dependency.Class); - // Only exports and those who are further than this class - if (obj != null && (int)obj > (int)this) + if (HasClassFlag(0x1000000)) + { + output += "\r\n\tnotlistable"; + } + else + { + var parentClass = (UClass)Super; + if (parentClass != null && parentClass.HasClassFlag(0x1000000)) { - output += $"\r\n\tdependson({obj.Name})"; + output += "\r\n\tlistable"; } - - dependsOn.Add(dependency.Class); } } +#endif - output += FormatNameGroup("dontsortcategories", DontSortCategories); - output += FormatNameGroup("hidecategories", HideCategories); output += FormatNameGroup("classgroup", ClassGroups); output += FormatNameGroup("autoexpandcategories", AutoExpandCategories); output += FormatNameGroup("autocollapsecategories", AutoCollapseCategories); @@ -510,6 +461,10 @@ public string FormatReplication() { statementCode = ByteCodeManager.CurrentToken.Decompile(); } + catch (EndOfStreamException) + { + throw; + } catch (Exception e) { statementCode = $"/* An exception occurred while decompiling condition ({e}) */"; @@ -546,6 +501,10 @@ public string FormatReplication() output.Append("\r\n"); } } + catch (EndOfStreamException) + { + break; + } catch (Exception e) { output.AppendFormat("/* An exception occurred while decompiling a statement! ({0}) */", e); @@ -570,6 +529,20 @@ private string FormatStates() return output; } + + public IEnumerable ExportableExtensions => new List { "uc" }; + + public bool CanExport() + { + return (int)this > 0; + } + + public void SerializeExport(string desiredExportExtension, Stream exportStream) + { + string data = Decompile(); + var stream = new StreamWriter(exportStream); + stream.Write(data); + } } } -#endif \ No newline at end of file +#endif diff --git a/src/Core/Classes/UComponent.cs b/src/Core/Classes/UComponent.cs index 90dec3c3..ad03d8f5 100644 --- a/src/Core/Classes/UComponent.cs +++ b/src/Core/Classes/UComponent.cs @@ -1,8 +1,14 @@ -namespace UELib.Core +using UELib.Branch; + +namespace UELib.Core { [UnrealRegisterClass] + [BuildGeneration(BuildGeneration.UE3)] public class UComponent : UObject { + public UClass TemplateOwnerClass; + public UName TemplateName; + public UComponent() { ShouldDeserializeOnDemand = true; diff --git a/src/Core/Classes/UConst.cs b/src/Core/Classes/UConst.cs index efa8d66b..efec699a 100644 --- a/src/Core/Classes/UConst.cs +++ b/src/Core/Classes/UConst.cs @@ -11,7 +11,7 @@ public partial class UConst : UField /// /// Constant Value /// - public string Value { get; private set; } + public string Value { get; set; } #endregion diff --git a/src/Core/Classes/UDefaultProperty.cs b/src/Core/Classes/UDefaultProperty.cs index c38fe6cb..de35b040 100644 --- a/src/Core/Classes/UDefaultProperty.cs +++ b/src/Core/Classes/UDefaultProperty.cs @@ -1,96 +1,213 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.IO; using System.Linq; +using System.Runtime.InteropServices; using UELib.Annotations; -using UELib.Core.Types; +using UELib.Branch; using UELib.Types; using UELib.UnrealScript; namespace UELib.Core { /// - /// [Default]Properties values deserializer. + /// [Default]Properties values deserializer. /// public sealed class UDefaultProperty : IUnrealDecompilable { [Flags] public enum DeserializeFlags : byte { - None = 0x00, - WithinStruct = 0x01, - WithinArray = 0x02, - Complex = WithinStruct | WithinArray, + None = 0x00, + WithinStruct = 0x01, + WithinArray = 0x02 } + private const byte InfoTypeMask = 0x0F; + private const byte InfoSizeMask = 0x70; + private const byte InfoArrayIndexMask = 0x80; + private const byte DoNotAppendName = 0x01; private const byte ReplaceNameMarker = 0x02; - private const int V3 = 220; - // FIXME: Wrong version, naive approach - private const int VAtomicStructs = V3; - private const int VEnumName = 633; - private const int VBoolSizeToOne = 673; - - private IUnrealStream _Buffer => _Container.Buffer; - private readonly UObject _Container; private UStruct _Outer; private bool _RecordingEnabled = true; - internal long _BeginOffset { get; set; } - private long _ValueOffset { get; set; } + private UObjectRecordStream _Buffer => (UObjectRecordStream)_Container.Buffer; - private byte _TempFlags { get; set; } + internal long _TagPosition { get; set; } + internal long _PropertyValuePosition { get; set; } - #region Serialized Members + private byte _TempFlags { get; set; } /// - /// Name of the UProperty. - /// - /// get and private remain to maintain compatibility with UE Explorer + /// The deserialized and decompiled output. + /// Serves as a temporary workaround, don't rely on it. /// [PublicAPI] - public UName Name { get; private set; } + public string Value { get; private set; } - /// - /// Name of the UStruct. If type equals StructProperty. - /// - [PublicAPI] [CanBeNull] public UName ItemName; + public string Decompile() + { + _TempFlags = 0x00; + string value; + _Container.EnsureBuffer(); + try + { + value = DeserializeValue(); + } + catch (Exception ex) + { + Console.Error.WriteLine($"Exception thrown: {ex} in {nameof(Decompile)}"); + value = $"/* ERROR: {ex.GetType()} */"; + } + finally + { + _Container.MaybeDisposeBuffer(); + } + + // Array or Inlined object + if ((_TempFlags & DoNotAppendName) != 0) + // The tag handles the name etc on its own. + { + return value; + } + + string name = Name; + if (Type == PropertyType.DelegateProperty && _Container.Package.Version >= 100) + { + // Re-point the compiler-generated delegate property to the actual delegate function's name + // e.g. __%delegateName%__Delegate -> %delegateName% + if (name.EndsWith("__Delegate")) + { + string normalizedDelegateName = name.Substring(2, name.Length - 12); + name = $"{normalizedDelegateName}"; + } + } + + string expr = name; + return ArrayIndex > 0 + ? $"{expr}[{ArrayIndex}]={value}" + : $"{expr}={value}"; + } + + [CanBeNull] + private T FindProperty(out UStruct outer) + where T : UProperty + { + UProperty property = null; + outer = _Outer ?? (UStruct)_Container.Class; + Debug.Assert(outer != null, nameof(outer) + " != null"); + foreach (var super in outer.EnumerateSuper(outer)) + { + foreach (var field in super + .EnumerateFields() + .OfType()) + { + // FIXME: UName + if (field.Table.ObjectName != Name) + { + continue; + } + + property = field; + outer = super; + break; + } + + if (property == null) + { + continue; + } + + switch (property.Type) + { + case PropertyType.StructProperty: + outer = ((UStructProperty)property).Struct; + break; + + case PropertyType.ArrayProperty: + var arrayField = (UArrayProperty)property; + Debug.Assert(arrayField != null, "arrayField != null"); + var arrayInnerField = arrayField.InnerProperty; + if (arrayInnerField.Type == PropertyType.StructProperty) + { + outer = ((UStructProperty)arrayInnerField).Struct; + } + + break; + } + break; + } + + return (T)property; + } + + [Conditional("BINARYMETADATA")] + private void Record(string varName, object varObject = null) + { + if (_RecordingEnabled) + { + _Container.Record(varName, varObject); + } + } + + #region Serialized Members /// - /// Name of the UEnum. If Type equals ByteProperty. + /// Name of the UProperty. /// - [PublicAPI] [CanBeNull] public UName EnumName; + public UName Name; /// - /// See PropertyType enum in UnrealFlags.cs + /// See PropertyType enum in UnrealFlags.cs /// - [PublicAPI] public PropertyType Type; + public PropertyType Type; + + [StructLayout(LayoutKind.Explicit, Size = 8)] + private struct PropertyTypeData + { + /// + /// Name of the UStruct. If is StructProperty. + /// + [FieldOffset(0)] public UName StructName; // Formerly known as "ItemName" + + /// + /// Name of the UEnum. If is ByteProperty. + /// + [FieldOffset(0)] public UName EnumName; + + /// + /// Name of the UArray's inner type. If is ArrayProperty. + /// + [FieldOffset(0)] public UName InnerTypeName; + } + + private PropertyTypeData _TypeData; + + [CanBeNull] public UName StructName => _TypeData.StructName; + [CanBeNull] public UName EnumName => _TypeData.EnumName; + [CanBeNull] public UName InnerTypeName => _TypeData.InnerTypeName; /// - /// The stream size of this DefaultProperty. + /// The size in bytes of this tag's value. /// - private int Size { get; set; } + public int Size; /// - /// Whether this property is part of an static array, and the index into it + /// The element index of this tag e.g. consider this static array: "var Object Elements[2];" + /// This defines a static array of 2 elements which would have two tags with this field being the index into that + /// array. /// public int ArrayIndex = -1; /// - /// Value of the UBoolProperty. If Type equals BoolProperty. + /// Value of the UBoolProperty. If Type equals BoolProperty. /// public bool? BoolValue; - /// - /// The deserialized and decompiled output. - /// - /// Serves as a temporary workaround, don't rely on it. - /// - [PublicAPI] - public string Value { get; private set; } - #endregion #region Constructors @@ -134,50 +251,50 @@ private int DeserializePackedSize(byte sizePack) } } - private const byte ArrayIndexMask = 0x80; - private int DeserializeTagArrayIndexUE1() { int arrayIndex; -#if BINARYMETADATA - long startPos = _Buffer.Position; -#endif byte b = _Buffer.ReadByte(); - if ((b & ArrayIndexMask) == 0) + if ((b & InfoArrayIndexMask) == 0) + { arrayIndex = b; - else if ((b & 0xC0) == ArrayIndexMask) - arrayIndex = ((b & 0x7F) << 8) + _Buffer.ReadByte(); + } + else if ((b & 0xC0) == InfoArrayIndexMask) + { + byte c = _Buffer.ReadByte(); + arrayIndex = ((b & 0x7F) << 8) + c; + } else - arrayIndex = ((b & 0x3F) << 24) - + (_Buffer.ReadByte() << 16) - + (_Buffer.ReadByte() << 8) - + _Buffer.ReadByte(); -#if BINARYMETADATA - _Buffer.LastPosition = startPos; -#endif + { + byte c = _Buffer.ReadByte(); + byte d = _Buffer.ReadByte(); + byte e = _Buffer.ReadByte(); + arrayIndex = ((b & 0x3F) << 24) + (c << 16) + (d << 8) + e; + } return arrayIndex; } /// True if there are more property tags. public bool Deserialize() { - _BeginOffset = _Buffer.Position; + _TagPosition = _Buffer.Position; if (DeserializeNextTag()) { return false; } - _ValueOffset = _Buffer.Position; + _PropertyValuePosition = _Buffer.Position; try { - DeserializeValue(); + Value = DeserializeValue(); _RecordingEnabled = false; } finally { // Even if something goes wrong, we can still skip everything and safely deserialize the next property if any! // Note: In some builds @Size is not serialized - _Buffer.Position = _ValueOffset + Size; + _Buffer.Position = _PropertyValuePosition + Size; + _Buffer.ConformRecordPosition(); } return true; @@ -186,10 +303,15 @@ public bool Deserialize() /// True if this is the last tag. private bool DeserializeNextTag() { - if (_Buffer.Version < V3) return DeserializeTagUE1(); + if (_Buffer.Version < (uint)PackageObjectLegacyVersion.UE3) + { + return DeserializeTagUE1(); + } #if BATMAN if (_Buffer.Package.Build == BuildGeneration.RSS) + { return DeserializeTagByOffset(); + } #endif return DeserializeTagUE3(); } @@ -199,39 +321,48 @@ private bool DeserializeTagUE1() { Name = _Buffer.ReadNameReference(); Record(nameof(Name), Name); - if (Name.IsNone()) return true; - - const byte typeMask = 0x0F; - const byte sizeMask = 0x70; + if (Name.IsNone()) + { + return true; + } - // Packed byte byte info = _Buffer.ReadByte(); - Record( - $"Info(Type={(PropertyType)(byte)(info & typeMask)}," + - $"SizeMask=0x{(byte)(info & sizeMask):X2}," + - $"ArrayIndexMask=0x{info & ArrayIndexMask:X2})", - info - ); - - Type = (PropertyType)(byte)(info & typeMask); - if (Type == PropertyType.StructProperty) + Record(nameof(info), info); + + Type = (PropertyType)(byte)(info & InfoTypeMask); + switch (Type) { - ItemName = _Buffer.ReadNameReference(); - Record(nameof(ItemName), ItemName); + case PropertyType.StructProperty: + _Buffer.Read(out _TypeData.StructName); + Record(nameof(_TypeData.StructName), _TypeData.StructName); + break; + + case PropertyType.ArrayProperty: + { +#if DNF + if (_Buffer.Package.Build == UnrealPackage.GameBuild.BuildName.DNF && + _Buffer.Version >= 124) + { + _Buffer.Read(out _TypeData.InnerTypeName); + Record(nameof(_TypeData.InnerTypeName), _TypeData.InnerTypeName); + } +#endif + break; + } } - Size = DeserializePackedSize((byte)(info & sizeMask)); + Size = DeserializePackedSize((byte)(info & InfoSizeMask)); Record(nameof(Size), Size); // TypeData switch (Type) { case PropertyType.BoolProperty: - BoolValue = (info & ArrayIndexMask) != 0; + BoolValue = (info & InfoArrayIndexMask) != 0; break; default: - if ((info & ArrayIndexMask) != 0) + if ((info & InfoArrayIndexMask) != 0) { ArrayIndex = DeserializeTagArrayIndexUE1(); Record(nameof(ArrayIndex), ArrayIndex); @@ -248,7 +379,10 @@ private bool DeserializeTagUE3() { Name = _Buffer.ReadNameReference(); Record(nameof(Name), Name); - if (Name.IsNone()) return true; + if (Name.IsNone()) + { + return true; + } string typeName = _Buffer.ReadName(); Record(nameof(typeName), typeName); @@ -269,7 +403,10 @@ private bool DeserializeTagByOffset() { Type = (PropertyType)_Buffer.ReadInt16(); Record(nameof(Type), Type.ToString()); - if (Type == PropertyType.None) return true; + if (Type == PropertyType.None) + { + return true; + } if (_Buffer.Package.Build != UnrealPackage.GameBuild.BuildName.Batman3MP) { @@ -291,9 +428,10 @@ private bool DeserializeTagByOffset() Type == PropertyType.StructProperty || Type == PropertyType.Vector || Type == PropertyType.Rotator || - (Type == PropertyType.BoolProperty && _Buffer.Package.Build == UnrealPackage.GameBuild.BuildName.Batman4)) + (Type == PropertyType.BoolProperty && + _Buffer.Package.Build == UnrealPackage.GameBuild.BuildName.Batman4)) { - switch(Type) + switch (Type) { case PropertyType.Vector: case PropertyType.Rotator: @@ -318,6 +456,7 @@ private bool DeserializeTagByOffset() Size = sizeof(byte); break; } + Name = new UName($"self[0x{offset:X3}]"); DeserializeTypeDataUE3(); return false; @@ -342,32 +481,49 @@ private void DeserializeTypeDataUE3() switch (Type) { case PropertyType.StructProperty: - ItemName = _Buffer.ReadNameReference(); - Record(nameof(ItemName), ItemName); + _Buffer.Read(out _TypeData.StructName); + Record(nameof(_TypeData.StructName), _TypeData.StructName); +#if UE4 + if (_Buffer.UE4Version >= 441) + { + _Buffer.Skip(16); + _Buffer.ConformRecordPosition(); + } +#endif break; case PropertyType.ByteProperty: - if (_Buffer.Version >= VEnumName) + if (_Buffer.Version >= (uint)PackageObjectLegacyVersion.EnumNameAddedToBytePropertyTag) { - EnumName = _Buffer.ReadNameReference(); - Record(nameof(EnumName), EnumName); + _Buffer.Read(out _TypeData.EnumName); + Record(nameof(_TypeData.EnumName), _TypeData.EnumName); } break; case PropertyType.BoolProperty: - BoolValue = _Buffer.Version >= VBoolSizeToOne + BoolValue = _Buffer.Version >= (uint)PackageObjectLegacyVersion.BoolValueToByteForBoolPropertyTag ? _Buffer.ReadByte() > 0 : _Buffer.ReadInt32() > 0; Record(nameof(BoolValue), BoolValue); break; + + case PropertyType.ArrayProperty: +#if UE4 + // FIXME: UE4 version + if (_Buffer.UE4Version > 220) + { + _Buffer.Read(out _TypeData.InnerTypeName); + Record(nameof(_TypeData.InnerTypeName), _TypeData.InnerTypeName); + } +#endif + break; } } /// - /// Deserialize the value of this UPropertyTag instance. - /// - /// Note: + /// Deserialize the value of this UPropertyTag instance. + /// Note: /// Only call after the whole package has been deserialized! /// /// The deserialized value if any. @@ -377,22 +533,43 @@ public string DeserializeValue(DeserializeFlags deserializeFlags = DeserializeFl if (_Buffer == null) { _Container.EnsureBuffer(); - if (_Buffer == null) throw new DeserializationException("_Buffer is not initialized!"); + if (_Buffer == null) + { + throw new DeserializationException("_Buffer is not initialized!"); + } } - _Buffer.Seek(_ValueOffset, System.IO.SeekOrigin.Begin); + _Buffer.Seek(_PropertyValuePosition, SeekOrigin.Begin); + return TryDeserializeDefaultPropertyValue(Type, ref deserializeFlags); + } + + private string TryDeserializeDefaultPropertyValue(PropertyType type, ref DeserializeFlags deserializeFlags) + { try { - return DeserializeDefaultPropertyValue(Type, ref deserializeFlags); + return DeserializeDefaultPropertyValue(type, ref deserializeFlags); } - catch (DeserializationException e) + catch (EndOfStreamException e) { - return e.ToString(); + // Abort decompilation + throw; } + catch (Exception ex) + { + Console.Error.WriteLine($"\r\n> PropertyTag value deserialization error for {_Container.GetPath()}.{Name}" + + $"\r\n Exception: {ex}"); + + return $"/* ERROR: {ex.GetType()} */"; + } + } + + private void AssertFastSerialize(IUnrealArchive archive) + { + Debug.Assert(archive.Version >= (uint)PackageObjectLegacyVersion.FastSerializeStructs); } /// - /// Deserialize a default property value of a specified type. + /// Deserialize a default property value of a specified type. /// /// Kind of type to try deserialize. /// The deserialized value if any. @@ -400,385 +577,469 @@ private string DeserializeDefaultPropertyValue(PropertyType type, ref Deserializ { var orgOuter = _Outer; var propertyValue = string.Empty; - try + + // Deserialize Value + switch (type) { - // Deserialize Value - switch (type) + case PropertyType.BoolProperty: { - case PropertyType.BoolProperty: + bool value; + if (Size == 0) { Debug.Assert(BoolValue != null, nameof(BoolValue) + " != null"); - bool value = BoolValue.Value; - if (Size == 1 && _Buffer.Version < V3) - { - value = _Buffer.ReadByte() > 0; - Record(nameof(value), value); - } - - propertyValue = value ? "true" : "false"; - break; + value = BoolValue.Value; } - - case PropertyType.StrProperty: + else { - string value = _Buffer.ReadText(); - Record(nameof(value), value); - propertyValue = PropertyDisplay.FormatLiteral(value); - break; + value = _Buffer.ReadByte() > 0; } - case PropertyType.NameProperty: - { - var value = _Buffer.ReadNameReference(); - Record(nameof(value), value); - propertyValue = value; - break; - } + Record(nameof(value), value); + propertyValue = value ? "true" : "false"; + break; + } - case PropertyType.IntProperty: - { - int value = _Buffer.ReadInt32(); - Record(nameof(value), value); - propertyValue = PropertyDisplay.FormatLiteral(value); - break; - } + case PropertyType.StrProperty: + { + string value = _Buffer.ReadText(); + Record(nameof(value), value); + propertyValue = PropertyDisplay.FormatLiteral(value); + break; + } + case PropertyType.NameProperty: + { + var value = _Buffer.ReadNameReference(); + Record(nameof(value), value); + propertyValue = $"\"{value}\""; + break; + } + + case PropertyType.IntProperty: + { + int value = _Buffer.ReadInt32(); + Record(nameof(value), value); + propertyValue = PropertyDisplay.FormatLiteral(value); + break; + } #if BIOSHOCK - case PropertyType.QwordProperty: - { - long value = _Buffer.ReadInt64(); - Record(nameof(value), value); - propertyValue = PropertyDisplay.FormatLiteral(value); - break; - } + case PropertyType.QwordProperty: + { + long value = _Buffer.ReadInt64(); + Record(nameof(value), value); + propertyValue = PropertyDisplay.FormatLiteral(value); + break; + } - case PropertyType.XWeakReferenceProperty: - propertyValue = "/* XWeakReference: (?=" + _Buffer.ReadName() + ",?=" + _Buffer.ReadName() + - ",?=" + _Buffer.ReadByte() + ",?=" + _Buffer.ReadName() + ") */"; - break; + case PropertyType.XWeakReferenceProperty: + propertyValue = "/* XWeakReference: (?=" + _Buffer.ReadName() + ",?=" + _Buffer.ReadName() + + ",?=" + _Buffer.ReadByte() + ",?=" + _Buffer.ReadName() + ") */"; + break; #endif + case PropertyType.FloatProperty: + { + float value = _Buffer.ReadFloat(); + Record(nameof(value), value); + propertyValue = PropertyDisplay.FormatLiteral(value); + break; + } - case PropertyType.FloatProperty: + case PropertyType.ByteProperty: + { + if (_Buffer.Version >= (uint)PackageObjectLegacyVersion.EnumTagNameAddedToBytePropertyTag && + Size == 8) + { + string enumTagName = _Buffer.ReadName(); + Record(nameof(enumTagName), enumTagName); + propertyValue = _Buffer.Version >= + (uint)PackageObjectLegacyVersion.EnumNameAddedToBytePropertyTag + ? $"{_TypeData.EnumName}.{enumTagName}" + : enumTagName; + } + else { - float value = _Buffer.ReadFloat(); + byte value = _Buffer.ReadByte(); Record(nameof(value), value); propertyValue = PropertyDisplay.FormatLiteral(value); - break; } - - case PropertyType.ByteProperty: - { - if (_Buffer.Version >= V3 && Size == 8) - { - string value = _Buffer.ReadName(); - Record(nameof(value), value); - propertyValue = value; - if (_Buffer.Version >= VEnumName) propertyValue = $"{EnumName}.{propertyValue}"; - } - else - { - byte value = _Buffer.ReadByte(); - Record(nameof(value), value); - propertyValue = PropertyDisplay.FormatLiteral(value); - } - break; - } + break; + } - case PropertyType.InterfaceProperty: - case PropertyType.ComponentProperty: - case PropertyType.ObjectProperty: + case PropertyType.InterfaceProperty: + { + var interfaceClass = _Buffer.ReadObject(); + Record(nameof(interfaceClass), interfaceClass); + propertyValue = PropertyDisplay.FormatLiteral(interfaceClass); + break; + } + + case PropertyType.ComponentProperty: + case PropertyType.ObjectProperty: + { + var constantObject = _Buffer.ReadObject(); + Record(nameof(constantObject), constantObject); + if (constantObject != null) { - var constantObject = _Buffer.ReadObject(); - Record(nameof(constantObject), constantObject); - if (constantObject != null) + var inline = false; + // If true, object is an archetype or subobject. + if (constantObject.Outer == _Container && + (deserializeFlags & DeserializeFlags.WithinStruct) == 0) { - var inline = false; - // If true, object is an archetype or subobject. - if (constantObject.Outer == _Container && - (deserializeFlags & DeserializeFlags.WithinStruct) == 0) + // Unknown objects are only deserialized on demand. + constantObject.BeginDeserializing(); + if (constantObject.Properties != null && constantObject.Properties.Count > 0) { - // Unknown objects are only deserialized on demand. - constantObject.BeginDeserializing(); - if (constantObject.Properties != null && constantObject.Properties.Count > 0) + inline = true; + propertyValue = constantObject.Decompile() + "\r\n" + UDecompilingState.Tabs; + + _TempFlags |= DoNotAppendName; + if ((deserializeFlags & DeserializeFlags.WithinArray) != 0) { - inline = true; - propertyValue = constantObject.Decompile() + "\r\n" + UDecompilingState.Tabs; - - _TempFlags |= DoNotAppendName; - if ((deserializeFlags & DeserializeFlags.WithinArray) != 0) - { - _TempFlags |= ReplaceNameMarker; - propertyValue += $"%ARRAYNAME%={constantObject.Name}"; - } - else - { - propertyValue += $"{Name}={constantObject.Name}"; - } + _TempFlags |= ReplaceNameMarker; + propertyValue += $"%ARRAYNAME%={constantObject.Name}"; + } + else + { + propertyValue += $"{Name}={constantObject.Name}"; } } - - if (!inline) - // =CLASS'Package.Group(s)+.Name' - propertyValue = $"{constantObject.GetClassName()}\'{constantObject.GetOuterGroup()}\'"; } - else + + if (!inline) + // =CLASS'Package.Group(s)+.Name' { - // =none - propertyValue = "none"; + propertyValue = PropertyDisplay.FormatLiteral(constantObject); } - - break; } - - case PropertyType.ClassProperty: + else { - var classObject = _Buffer.ReadObject(); - Record(nameof(classObject), classObject); - propertyValue = classObject != null - ? $"class'{classObject.Name}'" - : "none"; - break; + // =none + propertyValue = "none"; } - case PropertyType.DelegateProperty: - { - _TempFlags |= DoNotAppendName; + break; + } - var outerObj = _Buffer.ReadObject(); // Where the assigned delegate property exists. - Record(nameof(outerObj), outerObj); + case PropertyType.ClassProperty: + { + var classObject = _Buffer.ReadObject(); + Record(nameof(classObject), classObject); + propertyValue = PropertyDisplay.FormatLiteral(classObject); + break; + } - string delegateName = _Buffer.ReadName(); - Record(nameof(delegateName), delegateName); + // Old StringProperty with a fixed size and null termination. + case PropertyType.StringProperty when _Buffer.Version < 100: + { + string str = _Buffer.ReadAnsiNullString(); + propertyValue = str; + break; + } + + case PropertyType.DelegateProperty when _Buffer.Version >= 100: + { + // Can by any object, usually a class. + var functionOwner = _Buffer.ReadObject(); + Record(nameof(functionOwner), functionOwner); - // Strip __%delegateName%__Delegate - string normalizedDelegateName = ((string)Name).Substring(2, Name.Length - 12); - propertyValue = $"{normalizedDelegateName}={delegateName}"; - break; - } + string functionName = _Buffer.ReadName(); + Record(nameof(functionName), functionName); - #region HardCoded Struct Types + // Can be null in UE3 packages + propertyValue = functionOwner != null + ? $"{functionOwner.Name}.{functionName}" + : $"{functionName}"; + break; + } - case PropertyType.Color: - { - _Buffer.ReadAtomicStruct(out UColor color); - propertyValue += PropertyDisplay.FormatLiteral(color); - break; - } + #region HardCoded Struct Types - case PropertyType.LinearColor: - { - string r = DeserializeDefaultPropertyValue(PropertyType.FloatProperty, ref deserializeFlags); - string g = DeserializeDefaultPropertyValue(PropertyType.FloatProperty, ref deserializeFlags); - string b = DeserializeDefaultPropertyValue(PropertyType.FloatProperty, ref deserializeFlags); - string a = DeserializeDefaultPropertyValue(PropertyType.FloatProperty, ref deserializeFlags); + case PropertyType.Color: + { + _Buffer.ReadStructMarshal(out UColor color); + propertyValue += $"R={PropertyDisplay.FormatLiteral(color.R)}," + + $"G={PropertyDisplay.FormatLiteral(color.G)}," + + $"B={PropertyDisplay.FormatLiteral(color.B)}," + + $"A={PropertyDisplay.FormatLiteral(color.A)}"; + break; + } - propertyValue += $"R={r},G={g},B={b},A={a}"; - break; - } + case PropertyType.LinearColor: + { + _Buffer.ReadStructMarshal(out ULinearColor color); + propertyValue += $"R={PropertyDisplay.FormatLiteral(color.R)}," + + $"G={PropertyDisplay.FormatLiteral(color.G)}," + + $"B={PropertyDisplay.FormatLiteral(color.B)}," + + $"A={PropertyDisplay.FormatLiteral(color.A)}"; + break; + } - case PropertyType.Vector: - { - string x = DeserializeDefaultPropertyValue(PropertyType.FloatProperty, ref deserializeFlags); - string y = DeserializeDefaultPropertyValue(PropertyType.FloatProperty, ref deserializeFlags); - string z = DeserializeDefaultPropertyValue(PropertyType.FloatProperty, ref deserializeFlags); + case PropertyType.Vector2D: + { + _Buffer.ReadStructMarshal(out UVector2D vector); + propertyValue += $"X={PropertyDisplay.FormatLiteral(vector.X)}," + + $"Y={PropertyDisplay.FormatLiteral(vector.Y)}"; + break; + } - propertyValue += $"X={x},Y={y},Z={z}"; - break; - } + case PropertyType.Vector: + { + _Buffer.ReadStructMarshal(out UVector vector); + propertyValue += $"X={PropertyDisplay.FormatLiteral(vector.X)}," + + $"Y={PropertyDisplay.FormatLiteral(vector.Y)}," + + $"Z={PropertyDisplay.FormatLiteral(vector.Z)}"; + break; + } - case PropertyType.TwoVectors: - { - string v1 = DeserializeDefaultPropertyValue(PropertyType.Vector, ref deserializeFlags); - string v2 = DeserializeDefaultPropertyValue(PropertyType.Vector, ref deserializeFlags); - propertyValue += $"v1=({v1}),v2=({v2})"; - break; - } + case PropertyType.Vector4: + { + _Buffer.ReadStructMarshal(out UVector4 vector); + propertyValue += $"X={PropertyDisplay.FormatLiteral(vector.X)}," + + $"Y={PropertyDisplay.FormatLiteral(vector.Y)}," + + $"Z={PropertyDisplay.FormatLiteral(vector.Z)}," + + $"W={PropertyDisplay.FormatLiteral(vector.W)}"; + break; + } - case PropertyType.Vector4: - { - string x = DeserializeDefaultPropertyValue(PropertyType.FloatProperty, ref deserializeFlags); - string y = DeserializeDefaultPropertyValue(PropertyType.FloatProperty, ref deserializeFlags); - string z = DeserializeDefaultPropertyValue(PropertyType.FloatProperty, ref deserializeFlags); - string w = DeserializeDefaultPropertyValue(PropertyType.FloatProperty, ref deserializeFlags); + case PropertyType.TwoVectors: + { + string v1 = DeserializeDefaultPropertyValue(PropertyType.Vector, ref deserializeFlags); + string v2 = DeserializeDefaultPropertyValue(PropertyType.Vector, ref deserializeFlags); + propertyValue += $"v1=({v1})," + + $"v2=({v2})"; + break; + } - propertyValue += $"X={x},Y={y},Z={z},W={w}"; - break; - } + case PropertyType.Rotator: + { + _Buffer.ReadStructMarshal(out URotator rotator); + propertyValue += $"Pitch={rotator.Pitch}," + + $"Yaw={rotator.Yaw}," + + $"Roll={rotator.Roll}"; + break; + } - case PropertyType.Vector2D: - { - string x = DeserializeDefaultPropertyValue(PropertyType.FloatProperty, ref deserializeFlags); - string y = DeserializeDefaultPropertyValue(PropertyType.FloatProperty, ref deserializeFlags); - propertyValue += $"X={x},Y={y}"; - break; - } + case PropertyType.Guid: + { + _Buffer.ReadStructMarshal(out UGuid guid); + propertyValue += $"A={guid.A}," + + $"B={guid.B}," + + $"C={guid.C}," + + $"D={guid.D}"; + break; + } - case PropertyType.Rotator: - { - string pitch = DeserializeDefaultPropertyValue(PropertyType.IntProperty, ref deserializeFlags); - string yaw = DeserializeDefaultPropertyValue(PropertyType.IntProperty, ref deserializeFlags); - string roll = DeserializeDefaultPropertyValue(PropertyType.IntProperty, ref deserializeFlags); - propertyValue += $"Pitch={pitch},Yaw={yaw},Roll={roll}"; - break; - } + case PropertyType.Sphere: + { + AssertFastSerialize(_Buffer); + _Buffer.ReadStructMarshal(out USphere sphere); + propertyValue += $"W={PropertyDisplay.FormatLiteral(sphere.W)}," + + $"X={PropertyDisplay.FormatLiteral(sphere.X)}," + + $"Y={PropertyDisplay.FormatLiteral(sphere.Y)}," + + $"Z={PropertyDisplay.FormatLiteral(sphere.Z)}"; - case PropertyType.Guid: - { - string a = DeserializeDefaultPropertyValue(PropertyType.IntProperty, ref deserializeFlags); - string b = DeserializeDefaultPropertyValue(PropertyType.IntProperty, ref deserializeFlags); - string c = DeserializeDefaultPropertyValue(PropertyType.IntProperty, ref deserializeFlags); - string d = DeserializeDefaultPropertyValue(PropertyType.IntProperty, ref deserializeFlags); - propertyValue += $"A={a},B={b},C={c},D={d}"; - break; - } + break; + } - case PropertyType.Sphere: - case PropertyType.Plane: - { - if (_Buffer.Version < VAtomicStructs) - { - throw new NotSupportedException("Not atomic"); - } - - string w = DeserializeDefaultPropertyValue(PropertyType.FloatProperty, ref deserializeFlags); - string v = DeserializeDefaultPropertyValue(PropertyType.Vector, ref deserializeFlags); - propertyValue += $"W={w},{v}"; - break; - } + case PropertyType.Plane: + { + AssertFastSerialize(_Buffer); + _Buffer.ReadStructMarshal(out UPlane plane); + propertyValue += $"W={PropertyDisplay.FormatLiteral(plane.W)}," + + $"X={PropertyDisplay.FormatLiteral(plane.X)}," + + $"Y={PropertyDisplay.FormatLiteral(plane.Y)}," + + $"Z={PropertyDisplay.FormatLiteral(plane.Z)}"; + break; + } - case PropertyType.Scale: - { - string scale = DeserializeDefaultPropertyValue(PropertyType.Vector, ref deserializeFlags); - string sheerRate = - DeserializeDefaultPropertyValue(PropertyType.FloatProperty, ref deserializeFlags); - string sheerAxis = - DeserializeDefaultPropertyValue(PropertyType.ByteProperty, ref deserializeFlags); - propertyValue += $"Scale=({scale}),SheerRate={sheerRate},SheerAxis={sheerAxis}"; - break; - } + case PropertyType.Scale: + { + _Buffer.ReadStructMarshal(out UScale scale); + propertyValue += "Scale=(" + + $"X={PropertyDisplay.FormatLiteral(scale.Scale.X)}," + + $"Y={PropertyDisplay.FormatLiteral(scale.Scale.Y)}," + + $"Z={PropertyDisplay.FormatLiteral(scale.Scale.Z)})," + + $"SheerRate={PropertyDisplay.FormatLiteral(scale.SheerRate)}," + + $"SheerAxis={scale.SheerAxis}"; + break; + } - case PropertyType.Box: - { - if (_Buffer.Version < VAtomicStructs) - { - throw new NotSupportedException("Not atomic"); - } - - string min = DeserializeDefaultPropertyValue(PropertyType.Vector, ref deserializeFlags); - string max = DeserializeDefaultPropertyValue(PropertyType.Vector, ref deserializeFlags); - string isValid = - DeserializeDefaultPropertyValue(PropertyType.ByteProperty, ref deserializeFlags); - propertyValue += $"Min=({min}),Max=({max}),IsValid={isValid}"; - break; - } + case PropertyType.Box: + { + AssertFastSerialize(_Buffer); + string min = DeserializeDefaultPropertyValue(PropertyType.Vector, ref deserializeFlags); + string max = DeserializeDefaultPropertyValue(PropertyType.Vector, ref deserializeFlags); + string isValid = + DeserializeDefaultPropertyValue(PropertyType.ByteProperty, ref deserializeFlags); + propertyValue += $"Min=({min})," + + $"Max=({max})," + + $"IsValid={isValid}"; + break; + } - case PropertyType.Quat: - { - propertyValue += DeserializeDefaultPropertyValue(PropertyType.Plane, ref deserializeFlags); - break; - } + case PropertyType.Quat: + { + AssertFastSerialize(_Buffer); + _Buffer.ReadStructMarshal(out UQuat quat); + propertyValue += $"X={PropertyDisplay.FormatLiteral(quat.X)}," + + $"Y={PropertyDisplay.FormatLiteral(quat.Y)}," + + $"Z={PropertyDisplay.FormatLiteral(quat.Z)}," + + $"W={PropertyDisplay.FormatLiteral(quat.W)}"; + break; + } - case PropertyType.Matrix: - { - if (_Buffer.Version < VAtomicStructs) - { - throw new NotSupportedException("Not atomic"); - } - - string xPlane = DeserializeDefaultPropertyValue(PropertyType.Plane, ref deserializeFlags); - string yPlane = DeserializeDefaultPropertyValue(PropertyType.Plane, ref deserializeFlags); - string zPlane = DeserializeDefaultPropertyValue(PropertyType.Plane, ref deserializeFlags); - string wPlane = DeserializeDefaultPropertyValue(PropertyType.Plane, ref deserializeFlags); - propertyValue += $"XPlane=({xPlane}),YPlane=({yPlane}),ZPlane=({zPlane}),WPlane=({wPlane})"; - break; - } + case PropertyType.Matrix: + { + AssertFastSerialize(_Buffer); + //_Buffer.ReadAtomicStruct(out UMatrix matrix); + string xPlane = DeserializeDefaultPropertyValue(PropertyType.Plane, ref deserializeFlags); + string yPlane = DeserializeDefaultPropertyValue(PropertyType.Plane, ref deserializeFlags); + string zPlane = DeserializeDefaultPropertyValue(PropertyType.Plane, ref deserializeFlags); + string wPlane = DeserializeDefaultPropertyValue(PropertyType.Plane, ref deserializeFlags); + propertyValue += $"XPlane=({xPlane}),YPlane=({yPlane}),ZPlane=({zPlane}),WPlane=({wPlane})"; + break; + } - case PropertyType.IntPoint: - { - string x = DeserializeDefaultPropertyValue(PropertyType.IntProperty, ref deserializeFlags); - string y = DeserializeDefaultPropertyValue(PropertyType.IntProperty, ref deserializeFlags); - propertyValue += $"X={x},Y={y}"; - break; - } + case PropertyType.IntPoint: + { + string x = DeserializeDefaultPropertyValue(PropertyType.IntProperty, ref deserializeFlags); + string y = DeserializeDefaultPropertyValue(PropertyType.IntProperty, ref deserializeFlags); + propertyValue += $"X={x},Y={y}"; + break; + } - #endregion + case PropertyType.PointRegion: + { + string zone = DeserializeDefaultPropertyValue(PropertyType.ObjectProperty, ref deserializeFlags); + string iLeaf = DeserializeDefaultPropertyValue(PropertyType.IntProperty, ref deserializeFlags); + string zoneNumber = + DeserializeDefaultPropertyValue(PropertyType.ByteProperty, ref deserializeFlags); + propertyValue += $"Zone={zone},iLeaf={iLeaf},ZoneNumber={zoneNumber}"; + break; + } - case PropertyType.PointerProperty: - case PropertyType.StructProperty: + #endregion + + case PropertyType.StructProperty: + { + deserializeFlags |= DeserializeFlags.WithinStruct; +#if DNF + if (_Buffer.Package.Build == UnrealPackage.GameBuild.BuildName.DNF) + { + goto nonAtomic; + } +#endif + // Ugly hack, but this will do for now until this entire function gets "rewritten" :D + if (Enum.TryParse(_TypeData.StructName, out PropertyType structPropertyType)) { - deserializeFlags |= DeserializeFlags.WithinStruct; - var isHardCoded = false; - var hardcodedStructs = (PropertyType[])Enum.GetValues(typeof(PropertyType)); - for (var i = (byte)PropertyType.StructOffset; i < hardcodedStructs.Length; ++i) + // Not atomic if <=UE2, + // TODO: Figure out all non-atomic structs + if (_Buffer.Version < (uint)PackageObjectLegacyVersion.FastSerializeStructs) { - string structType = Enum.GetName(typeof(PropertyType), (byte)hardcodedStructs[i]); - if (string.Compare(ItemName, structType, StringComparison.OrdinalIgnoreCase) != 0) - continue; - - // Not atomic if <=UE2, - // TODO: Figure out all non-atomic structs - if (_Buffer.Version < VAtomicStructs) switch (hardcodedStructs[i]) + switch (structPropertyType) { + case PropertyType.Quat: + case PropertyType.Scale: // not available in UE3 case PropertyType.Matrix: case PropertyType.Box: case PropertyType.Plane: goto nonAtomic; + + // None of these exist in older packages (UE2 or older). + case PropertyType.LinearColor: + case PropertyType.Vector2D: + case PropertyType.Vector4: + goto nonAtomic; } - - isHardCoded = true; - propertyValue += DeserializeDefaultPropertyValue(hardcodedStructs[i], ref deserializeFlags); - break; } - - nonAtomic: - if (!isHardCoded) + else { - // We have to modify the outer so that dynamic arrays within this struct - // will be able to find its variables to determine the array type. - FindProperty(out _Outer); - while (true) + switch (structPropertyType) { - var tag = new UDefaultProperty(_Container, _Outer); - if (tag.Deserialize()) - { - propertyValue += tag.Name + - (tag.ArrayIndex > 0 && tag.Type != PropertyType.BoolProperty - ? $"[{tag.ArrayIndex}]" - : string.Empty) + - "=" + tag.DeserializeValue(deserializeFlags) + ","; - } - else - { - if (propertyValue.EndsWith(",")) - propertyValue = propertyValue.Remove(propertyValue.Length - 1, 1); + //case PropertyType.Coords: + //case PropertyType.Range: + // Deprecated in UDK + case PropertyType.PointRegion: + goto nonAtomic; + } + } - break; - } + propertyValue += DeserializeDefaultPropertyValue(structPropertyType, ref deserializeFlags); + goto output; + } + + nonAtomic: + // We have to modify the outer so that dynamic arrays within this struct + // will be able to find its variables to determine the array type. + FindProperty(out _Outer); + var structTags = new LinkedList(); + bool hasMore = true; + while (true) + { + var tag = new UDefaultProperty(_Container, _Outer); + try + { + // Might throw an exception if the struct is atomic + if (tag.Deserialize()) + { + structTags.AddLast(tag); + continue; } } + catch (Exception ex) + { + // ignored + } - propertyValue = propertyValue.Length != 0 - ? $"({propertyValue})" - : "none"; break; } - case PropertyType.ArrayProperty: + foreach (var tag in structTags) { - int arraySize = _Buffer.ReadIndex(); - Record(nameof(arraySize), arraySize); - if (arraySize == 0) + string tagExpr = tag.Name; + if (tag.ArrayIndex > 0) { - propertyValue = "none"; - break; + tagExpr += $"[{tag.ArrayIndex}]"; } + propertyValue += $"{tagExpr}={tag.Value}"; - // Find the property within the outer/owner or its inheritances. - // If found it has to modify the outer so structs within this array can find their array variables. - // Additionally we need to know the property to determine the array's type. - var arrayType = PropertyType.None; - var property = FindProperty(out _Outer) as UArrayProperty; + if (tag != structTags.Last.Value) + { + propertyValue += ","; + } + } + + output: + propertyValue = propertyValue.Length != 0 + ? $"({propertyValue})" + : "none"; + break; + } + + case PropertyType.ArrayProperty: + { + int arraySize = _Buffer.ReadIndex(); + Record(nameof(arraySize), arraySize); + if (arraySize == 0) + { + propertyValue = "none"; + break; + } + + var arrayType = PropertyType.None; + if (_TypeData.InnerTypeName != null && !Enum.TryParse(_TypeData.InnerTypeName, out arrayType)) + { + throw new Exception( + $"Couldn't convert InnerTypeName \"{_TypeData.InnerTypeName}\" to PropertyType"); + } + + // Find the property within the outer/owner or its inheritances. + // If found it has to modify the outer so structs within this array can find their array variables. + // Additionally we need to know the property to determine the array's type. + if (arrayType == PropertyType.None) + { + var property = FindProperty(out _Outer); if (property?.InnerProperty != null) { arrayType = property.InnerProperty.Type; @@ -788,154 +1049,143 @@ private string DeserializeDefaultPropertyValue(PropertyType type, ref Deserializ else if (UnrealConfig.VariableTypes != null && UnrealConfig.VariableTypes.ContainsKey(Name)) { var varTuple = UnrealConfig.VariableTypes[Name]; - if (varTuple != null) arrayType = varTuple.Item2; + if (varTuple != null) + { + arrayType = varTuple.Item2; + } } + } - if (arrayType == PropertyType.None) + if (arrayType == PropertyType.None) + { + propertyValue = "/* Array type was not detected. */"; + break; + } + + deserializeFlags |= DeserializeFlags.WithinArray; + if ((deserializeFlags & DeserializeFlags.WithinStruct) != 0) + { + // Hardcoded fix for InterpCurve and InterpCurvePoint. + if (string.Compare(Name, "Points", StringComparison.OrdinalIgnoreCase) == 0) { - propertyValue = "/* Array type was not detected. */"; - break; + arrayType = PropertyType.StructProperty; } - deserializeFlags |= DeserializeFlags.WithinArray; - if ((deserializeFlags & DeserializeFlags.WithinStruct) != 0) + for (var i = 0; i < arraySize; ++i) { - // Hardcoded fix for InterpCurve and InterpCurvePoint. - if (string.Compare(Name, "Points", StringComparison.OrdinalIgnoreCase) == 0) - arrayType = PropertyType.StructProperty; - - for (var i = 0; i < arraySize; ++i) - propertyValue += DeserializeDefaultPropertyValue(arrayType, ref deserializeFlags) - + (i != arraySize - 1 ? "," : string.Empty); - - propertyValue = $"({propertyValue})"; + propertyValue += DeserializeDefaultPropertyValue(arrayType, ref deserializeFlags) + + (i != arraySize - 1 ? "," : string.Empty); } - else + + propertyValue = $"({propertyValue})"; + } + else + { + for (var i = 0; i < arraySize; ++i) { - for (var i = 0; i < arraySize; ++i) + string elementValue = DeserializeDefaultPropertyValue(arrayType, ref deserializeFlags); + if ((_TempFlags & ReplaceNameMarker) != 0) { - string elementValue = DeserializeDefaultPropertyValue(arrayType, ref deserializeFlags); - if ((_TempFlags & ReplaceNameMarker) != 0) - { - propertyValue += elementValue.Replace("%ARRAYNAME%", $"{Name}({i})"); - _TempFlags = 0x00; - } - else - { - propertyValue += $"{Name}({i})={elementValue}"; - } + propertyValue += elementValue.Replace("%ARRAYNAME%", $"{Name}({i})"); + _TempFlags = 0x00; + } + else + { + propertyValue += $"{Name}({i})={elementValue}"; + } - if (i != arraySize - 1) propertyValue += "\r\n" + UDecompilingState.Tabs; + if (i != arraySize - 1) + { + propertyValue += "\r\n" + UDecompilingState.Tabs; } } - - _TempFlags |= DoNotAppendName; - break; } - default: - propertyValue = "/* Unknown default property type! */"; - break; + _TempFlags |= DoNotAppendName; + break; } - } - catch (Exception e) - { - return $"{propertyValue}\r\n/* Exception thrown while deserializing {Name}\r\n{e} */"; - } - finally - { - _Outer = orgOuter; - } - - return propertyValue; - } - - #endregion - #region Decompilation - - public string Decompile() - { - _TempFlags = 0x00; - string value; - _Container.EnsureBuffer(); - try - { - value = DeserializeValue(); - } - catch (Exception e) - { - value = $"//{e}"; - } - finally - { - _Container.MaybeDisposeBuffer(); - } - - // Array or Inlined object - if ((_TempFlags & DoNotAppendName) != 0) - // The tag handles the name etc on its own. - return value; - - var arrayIndex = string.Empty; - if (ArrayIndex > 0 && Type != PropertyType.BoolProperty) arrayIndex += $"[{ArrayIndex}]"; - - return $"{Name}{arrayIndex}={value}"; - } - - #endregion - - #region Methods + case PropertyType.MapProperty: + { + if (Size == 0) break; + + int count = _Buffer.ReadIndex(); + Record(nameof(count), count); - private UProperty FindProperty(out UStruct outer) - { - UProperty property = null; - outer = _Outer ?? _Container.Class as UStruct; - for (var structField = outer; structField != null; structField = structField.Super as UStruct) - { - if (structField.Variables == null || !structField.Variables.Any()) - continue; + var property = FindProperty(out _Outer); + if (property == null) + { + propertyValue = "// Unable to decompile Map data."; + break; + } - property = structField.Variables.Find(i => i.Table.ObjectName == Name); - if (property == null) - continue; + propertyValue = "("; + for (int i = 0; i < count; ++i) + { + propertyValue += DeserializeDefaultPropertyValue(property.ValueProperty.Type, ref deserializeFlags); + if (i + 1 != count) + { + propertyValue += ","; + } + } + propertyValue += ")"; + break; + } - switch (property.Type) + case PropertyType.FixedArrayProperty: { - case PropertyType.StructProperty: - outer = ((UStructProperty)property).StructObject; + // We require the InnerProperty to properly deserialize this data type. + var property = FindProperty(out _Outer); + if (property == null) + { + propertyValue = "// Unable to decompile FixedArray data."; break; + } - case PropertyType.ArrayProperty: - var arrayField = property as UArrayProperty; - Debug.Assert(arrayField != null, "arrayField != null"); - var arrayInnerField = arrayField.InnerProperty; - if (arrayInnerField.Type == PropertyType.StructProperty) - _Outer = ((UStructProperty)arrayInnerField).StructObject; - - break; + var innerType = property.InnerProperty.Type; + propertyValue = "("; + for (int i = 0; i < property.Count; ++i) + { + propertyValue += DeserializeDefaultPropertyValue(innerType, ref deserializeFlags); + if (i + 1 != property.Count) + { + propertyValue += ","; + } + } + propertyValue += ")"; + break; + } - default: - outer = structField; - break; + // Note: We don't have to verify the package's version here. + case PropertyType.PointerProperty: + { + int offset = _Buffer.ReadInt32(); + propertyValue = PropertyDisplay.FormatLiteral(offset); + break; } - break; + default: + throw new Exception($"Unsupported property tag type {Type}"); } - return property; + _Outer = orgOuter; + return propertyValue; } #endregion - [Conditional("BINARYMETADATA")] - private void Record(string varName, object varObject = null) + public static PropertyType ResolvePropertyType(ushort propertyType) + { + return (PropertyType)propertyType; + } + + public static string ResolvePropertyTypeName(PropertyType propertyType) { - if (_RecordingEnabled) _Container.Record(varName, varObject); + return Enum.GetName(typeof(PropertyType), propertyType); } } - [System.Runtime.InteropServices.ComVisible(false)] + [ComVisible(false)] public sealed class DefaultPropertiesCollection : List { [CanBeNull] @@ -960,4 +1210,4 @@ public bool Contains(UName name) return Find(name) != null; } } -} \ No newline at end of file +} diff --git a/src/Core/Classes/UEnumDecompiler.cs b/src/Core/Classes/UEnumDecompiler.cs index 0bc476ca..d9ed305f 100644 --- a/src/Core/Classes/UEnumDecompiler.cs +++ b/src/Core/Classes/UEnumDecompiler.cs @@ -20,7 +20,7 @@ public override string Decompile() UnrealConfig.PrintEndBracket() + ";"; } - protected override string FormatHeader() + public override string FormatHeader() { return $"enum {Name}{DecompileMeta()}"; } diff --git a/src/Core/Classes/UField.cs b/src/Core/Classes/UField.cs index a4e1cf49..a4e892d6 100644 --- a/src/Core/Classes/UField.cs +++ b/src/Core/Classes/UField.cs @@ -1,5 +1,7 @@ using System; +using System.Collections.Generic; using UELib.Annotations; +using UELib.Branch; namespace UELib.Core { @@ -10,74 +12,57 @@ public partial class UField : UObject { #region Serialized Members - [CanBeNull] public UField Super { get; private set; } - [CanBeNull] public UField NextField { get; private set; } + [CanBeNull] public UStruct Super { get; set; } + [CanBeNull] public UField NextField { get; set; } #endregion - #region Script Members - /// /// Initialized by the UMetaData object, /// This Meta contains comments and other meta related info that belongs to this instance. /// [CanBeNull] public UMetaData.UFieldData MetaData; - #endregion - #region Constructors protected override void Deserialize() { base.Deserialize(); - // _SuperIndex got moved into UStruct since 700+ - if ((Package.Version < 756 -#if SPECIALFORCE2 - || Package.Build == UnrealPackage.GameBuild.BuildName.SpecialForce2 -#endif -#if TRANSFORMERS - || Package.Build == UnrealPackage.GameBuild.BuildName.Transformers -#endif - ) -#if BIOSHOCK - && Package.Build != UnrealPackage.GameBuild.BuildName.Bioshock_Infinite -#endif - ) + if (_Buffer.Version < (uint)PackageObjectLegacyVersion.SuperReferenceMovedToUStruct) { - Super = GetIndexObject(_Buffer.ReadObjectIndex()) as UField; - Record("Super", Super); - - NextField = GetIndexObject(_Buffer.ReadObjectIndex()) as UField; - Record("NextField", NextField); + Super = _Buffer.ReadObject(); + Record(nameof(Super), Super); } - else - { - NextField = GetIndexObject(_Buffer.ReadObjectIndex()) as UField; - Record("NextField", NextField); - // Should actually resist in UStruct - if (this is UStruct) - { - Super = GetIndexObject(_Buffer.ReadObjectIndex()) as UField; - Record("Super", Super); - } - } + NextField = _Buffer.ReadObject(); + Record(nameof(NextField), NextField); } #endregion + + public IEnumerable EnumerateSuper() + { + for (var super = Super; super != null; super = super.Super) + { + yield return super; + } + } - #region Methods - - public string GetSuperGroup() + public IEnumerable EnumerateSuper(UStruct super) { - var group = string.Empty; - for (var field = Super; field != null; field = field.Super) + for (; super != null; super = super.Super) { - group = $"{field.Name}.{@group}"; + yield return super; } + } - return group + Name; + public IEnumerable EnumerateNext() + { + for (var next = NextField; next != null; next = next.NextField) + { + yield return next; + } } public bool Extends(string classType) @@ -93,6 +78,16 @@ public bool Extends(string classType) return false; } - #endregion + [Obsolete] + public string GetSuperGroup() + { + var group = string.Empty; + for (var field = Super; field != null; field = field.Super) + { + group = $"{field.Name}.{@group}"; + } + + return group + Name; + } } -} \ No newline at end of file +} diff --git a/src/Core/Classes/UFieldDecompiler.cs b/src/Core/Classes/UFieldDecompiler.cs index a25852f8..7a9858d0 100644 --- a/src/Core/Classes/UFieldDecompiler.cs +++ b/src/Core/Classes/UFieldDecompiler.cs @@ -3,11 +3,37 @@ namespace UELib.Core { public partial class UField { - protected string DecompileMeta() + public string DecompileMeta() { return MetaData == null ? string.Empty : MetaData.Decompile(); } + public string FormatTooltipMetaData() + { + string tooltipValue = null; + MetaData?.Tags.TryGetValue("ToolTip", out tooltipValue); + if (tooltipValue == null) + { + return string.Empty; + } + + var comment = $"{UDecompilingState.Tabs}/** "; + // Multiline comment? + if (tooltipValue.IndexOf('\n') != -1) + { + comment += + " \r\n" + + $"{UDecompilingState.Tabs} *{tooltipValue.Replace("\n", "\n" + UDecompilingState.Tabs + " *")}" + + $"\r\n{UDecompilingState.Tabs}"; + } + else + { + comment += tooltipValue; + } + + return $"{comment} */\r\n"; + } + // Introduction of the change from intrinsic to native. private const uint NativeVersion = 69; diff --git a/src/Core/Classes/UFunction.cs b/src/Core/Classes/UFunction.cs index 5e9109ee..75cbff07 100644 --- a/src/Core/Classes/UFunction.cs +++ b/src/Core/Classes/UFunction.cs @@ -44,16 +44,30 @@ public partial class UFunction : UStruct, IUnrealNetObject protected override void Deserialize() { #if BORDERLANDS2 - if (Package.Build == UnrealPackage.GameBuild.BuildName.Borderlands2) + if (Package.Build == UnrealPackage.GameBuild.BuildName.Borderlands2 || + Package.Build == UnrealPackage.GameBuild.BuildName.Battleborn) { ushort size = _Buffer.ReadUShort(); - Record("Unknown:Borderlands2", size); _Buffer.Skip(size * 2); + Record("Unknown:Borderlands2", size); + } #endif - base.Deserialize(); - +#if UE4 + if (_Buffer.UE4Version > 0) + { + FunctionFlags = _Buffer.ReadUInt32(); + Record(nameof(FunctionFlags), (FunctionFlags)FunctionFlags); + if (HasFunctionFlag(Flags.FunctionFlags.Net)) + { + RepOffset = _Buffer.ReadUShort(); + Record(nameof(RepOffset), RepOffset); + } + FriendlyName = ExportTable.ObjectName; + return; + } +#endif if (_Buffer.Version < 64) { ushort paramsSize = _Buffer.ReadUShort(); @@ -80,7 +94,7 @@ protected override void Deserialize() #if TRANSFORMERS // TODO: Version? - FunctionFlags = Package.Build == UnrealPackage.GameBuild.BuildName.Transformers + FunctionFlags = Package.Build == BuildGeneration.HMS ? _Buffer.ReadUInt64() : _Buffer.ReadUInt32(); #else @@ -108,8 +122,8 @@ protected override void Deserialize() // TODO: Data-strip version? if (_Buffer.Version >= VFriendlyName && !Package.IsConsoleCooked() #if TRANSFORMERS - // Cooked, but not stripped, However FriendlyName got stripped or deprecated. - && Package.Build != UnrealPackage.GameBuild.BuildName.Transformers + // Cooked, but not stripped, However FriendlyName got stripped or deprecated. + && Package.Build != BuildGeneration.HMS #endif #if MKKE // Cooked and stripped, but FriendlyName still remains @@ -124,7 +138,7 @@ protected override void Deserialize() { // HACK: Workaround for packages that have stripped FriendlyName data. // FIXME: Operator names need to be translated. - FriendlyName = Table.ObjectName; + if (FriendlyName == null) FriendlyName = Table.ObjectName; } } @@ -143,7 +157,11 @@ protected override void FindChildren() #endregion #region Methods - + public bool HasFunctionFlag(uint flag) + { + return ((uint)FunctionFlags & flag) != 0; + } + public bool HasFunctionFlag(FunctionFlags flag) { return ((uint)FunctionFlags & (uint)flag) != 0; @@ -164,6 +182,14 @@ public bool IsPre() return IsOperator() && HasFunctionFlag(Flags.FunctionFlags.PreOperator); } + public bool HasOptionalParamData() + { + // FIXME: Deprecate version check, and re-map the function flags using the EngineBranch class approach. + return Package.Version > 300 + && ByteCodeManager != null + && HasFunctionFlag(Flags.FunctionFlags.OptionalParameters); + } + #endregion } -} \ No newline at end of file +} diff --git a/src/Core/Classes/UFunctionDecompiler.cs b/src/Core/Classes/UFunctionDecompiler.cs index cb371d81..d4ff6bdd 100644 --- a/src/Core/Classes/UFunctionDecompiler.cs +++ b/src/Core/Classes/UFunctionDecompiler.cs @@ -1,6 +1,7 @@ #if DECOMPILE using System; using System.Linq; +using UELib.Flags; namespace UELib.Core { @@ -9,27 +10,22 @@ public partial class UFunction /// /// Decompiles this object into a text format of: /// - /// [FLAGS] function NAME([VARIABLES]) [const] + /// [FLAGS] function NAME([VARIABLES])[;] [const] /// { /// [LOCALS] /// /// [CODE] - /// } + /// } [META DATA] /// /// public override string Decompile() { - string code; - try - { - code = FormatCode(); - } - catch (Exception e) - { - code = e.Message; - } - - return FormatHeader() + (string.IsNullOrEmpty(code) ? ";" : code); + string code = FormatCode(); + var body = $"{FormatHeader()}{code}{DecompileMeta()}"; + // Write a declaration only if code is empty. + return string.IsNullOrEmpty(code) + ? $"{body};" + : body; } private string FormatFlags() @@ -104,21 +100,52 @@ private string FormatFlags() output += "noexport "; } - if (HasFunctionFlag(Flags.FunctionFlags.K2Call)) + // FIXME: Version, added with one of the later UDK builds. + if (Package.Version >= 500) { - output += "k2call "; - } + if (HasFunctionFlag(Flags.FunctionFlags.K2Call)) + { + output += "k2call "; + } - if (HasFunctionFlag(Flags.FunctionFlags.K2Override)) - { - output += "k2override "; - } + if (HasFunctionFlag(Flags.FunctionFlags.K2Override)) + { + output += "k2override "; + } - if (HasFunctionFlag(Flags.FunctionFlags.K2Pure)) - { - output += "k2pure "; + if (HasFunctionFlag(Flags.FunctionFlags.K2Pure)) + { + output += "k2pure "; + } } +#if DNF + if (Package.Build == UnrealPackage.GameBuild.BuildName.DNF) + { + // 0x20000200 unknown specifier + + if (HasFunctionFlag(0x4000000)) + { + output += "animevent "; + } + + if (HasFunctionFlag(0x1000000)) + { + output += "cached "; + } + + if (HasFunctionFlag(0x2000000)) + { + output += "encrypted "; + } + // Only if non-static? + if (HasFunctionFlag(0x800000)) + { + // Along with an implicit "native" + output += "indexed "; + } + } +#endif if (HasFunctionFlag(Flags.FunctionFlags.Invariant)) { output += "invariant "; @@ -188,7 +215,7 @@ private string FormatFlags() return output; } - protected override string FormatHeader() + public override string FormatHeader() { var output = string.Empty; // static function (string?:) Name(Parms)... @@ -198,17 +225,13 @@ protected override string FormatHeader() output = $"// Export U{Outer.Name}::exec{Name}(FFrame&, void* const)\r\n{UDecompilingState.Tabs}"; } - string metaData = DecompileMeta(); - if (metaData != string.Empty) - { - output = metaData + "\r\n" + output; - } + string comment = FormatTooltipMetaData(); + string returnCode = ReturnProperty != null + ? ReturnProperty.GetFriendlyType() + " " + : string.Empty; - output += FormatFlags() - + (ReturnProperty != null - ? ReturnProperty.GetFriendlyType() + " " - : string.Empty) - + FriendlyName + FormatParms(); + output += comment + + FormatFlags() + returnCode + FriendlyName + FormatParms(); if (HasFunctionFlag(Flags.FunctionFlags.Const)) { output += " const"; @@ -219,14 +242,41 @@ protected override string FormatHeader() private string FormatParms() { + if (Params == null || !Params.Any()) + return "()"; + + bool hasOptionalData = HasOptionalParamData(); + if (hasOptionalData) + { + // Ensure a sound ByteCodeManager + ByteCodeManager.Deserialize(); + ByteCodeManager.JumpTo(0); + ByteCodeManager.CurrentTokenIndex = -1; + } + var output = string.Empty; - if (Params != null && Params.Any()) + var parameters = Params.Where(parm => parm != ReturnProperty).ToList(); + foreach (var parm in parameters) { - var parameters = Params.Where((p) => p != ReturnProperty); - foreach (var parm in parameters) + string parmCode = parm.Decompile(); + if (hasOptionalData && parm.HasPropertyFlag(PropertyFlagsLO.OptionalParm)) { - output += parm.Decompile() + (parm != parameters.Last() ? ", " : string.Empty); + // Look for an assignment. + var defaultToken = ByteCodeManager.NextToken; + if (defaultToken is UByteCodeDecompiler.DefaultParameterToken) + { + string defaultExpr = defaultToken.Decompile(); + parmCode += $" = {defaultExpr}"; + } } + + if (parm != parameters.Last()) + { + output += $"{parmCode}, "; + continue; + } + + output += parmCode; } return $"({output})"; @@ -248,7 +298,8 @@ private string FormatCode() } catch (Exception e) { - code = e.Message; + Console.Error.WriteLine($"Exception thrown: {e} in {nameof(FormatCode)}"); + code = $"/*ERROR: {e}*/"; } finally { diff --git a/src/Core/Classes/UMetaData.cs b/src/Core/Classes/UMetaData.cs index 1e0f50dc..aebcc698 100644 --- a/src/Core/Classes/UMetaData.cs +++ b/src/Core/Classes/UMetaData.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Linq; namespace UELib.Core @@ -10,8 +9,6 @@ namespace UELib.Core [UnrealRegisterClass] public sealed class UMetaData : UObject { - #region Serialized Members - public sealed class UFieldData : IUnrealDecompilable, IUnrealSerializableClass { private string _FieldName; @@ -19,7 +16,7 @@ public sealed class UFieldData : IUnrealDecompilable, IUnrealSerializableClass private UField _Field; // Dated qualified identifier to this meta data's field. e.g. UT3, Mirrors Edge - public Dictionary Tags; + public UMap Tags; public void Serialize(IUnrealStream stream) { @@ -36,12 +33,12 @@ public void Deserialize(IUnrealStream stream) else { // TODO: Possibly linked to a non-ufield? - _Field = (UField)stream.ReadObject(); + _Field = stream.ReadObject(); _Field.MetaData = this; } int length = stream.ReadInt32(); - Tags = new Dictionary(length); + Tags = new UMap(length); for (var i = 0; i < length; ++i) { var key = stream.ReadNameReference(); @@ -72,8 +69,15 @@ public override string ToString() } } - // ReSharper disable once MemberCanBePrivate.Global - public UArray MetaObjects; + #region Serialized Members + + private UArray _Fields; + + public UArray Fields + { + get => _Fields; + set => _Fields = value; + } #endregion @@ -82,7 +86,8 @@ public override string ToString() protected override void Deserialize() { base.Deserialize(); - _Buffer.ReadArray(out MetaObjects); + _Buffer.ReadArray(out _Fields); + Record(nameof(_Fields), _Fields); } #endregion @@ -103,12 +108,12 @@ public override string Decompile() { // UE3 Debug BeginDeserializing(); - if (MetaObjects == null) + if (_Fields == null) { return ""; } - return string.Join("\r\n", MetaObjects.ConvertAll(data => data + data.Decompile())); + return string.Join("\r\n", _Fields.ConvertAll(data => data + data.Decompile())); } #endregion @@ -119,4 +124,4 @@ public string GetUniqueMetas() return ""; } } -} \ No newline at end of file +} diff --git a/src/Core/Classes/UObject.cs b/src/Core/Classes/UObject.cs index 9a9e161a..cbf2ac11 100644 --- a/src/Core/Classes/UObject.cs +++ b/src/Core/Classes/UObject.cs @@ -1,8 +1,14 @@ using System; +using System.Collections.Generic; +using System.Diagnostics; using System.Diagnostics.Contracts; using System.IO; +using System.Linq; +using System.Runtime.CompilerServices; using UELib.Annotations; +using UELib.Branch; using UELib.Flags; +using UELib.ObjectModel.Annotations; namespace UELib.Core { @@ -21,10 +27,8 @@ public ObjectEventArgs(UObject objectRef) /// Instances of this class are deserialized from the exports table entries. /// [UnrealRegisterClass] - public partial class UObject : object, IContainsTable, IBinaryData, IDisposable, IComparable + public partial class UObject : object, IAcceptable, IContainsTable, IBinaryData, IDisposable, IComparable { - #region PreInitialized Members - /// /// The package this object resists in. /// @@ -41,38 +45,44 @@ public partial class UObject : object, IContainsTable, IBinaryData, IDisposable, /// /// The internal represented class in UnrealScript. /// - public UObject Class => Package.GetIndexObject(Table.ClassIndex); + [CanBeNull] + [Output(OutputSlot.Parameter)] + public UObject Class => ExportTable != null + ? Package.GetIndexObject(ExportTable.ClassIndex) + : null; /// /// [Package.Group:Outer].Object /// + [CanBeNull] public UObject Outer => Package.GetIndexObject(Table.OuterIndex); /// /// The object's index represented as a table index. /// - private int _ObjectIndex => Table is UExportTableItem ? Table.Index + 1 : -(Table.Index + 1); + private int _ObjectIndex => Table is UExportTableItem + ? Table.Index + 1 + : -(Table.Index + 1); /// /// The object's flags. /// private ulong _ObjectFlags => ExportTable?.ObjectFlags ?? 0; - public string Name => Table.ObjectName; - - #endregion + [Output(OutputSlot.Parameter)] public string Name => Table.ObjectName; #region Serialized Members - protected UObjectStream _Buffer; + protected UObjectRecordStream _Buffer; /// /// Copy of the Object bytes /// public UObjectStream Buffer => _Buffer; - [CanBeNull] - public UObject Default { get; protected set; } + public int NetIndex = -1; + + [CanBeNull] public UObject Default { get; protected set; } /// /// Object Properties e.g. SubObjects or/and DefaultProperties @@ -99,6 +109,7 @@ public enum ObjectState : byte public ObjectState DeserializationState; public Exception ThrownException; public long ExceptionPosition; + public UGuid ObjectGuid; /// /// Object will not be deserialized by UnrealPackage, Can only be deserialized by calling the methods yourself. @@ -133,22 +144,34 @@ public void BeginDeserializing() try { #if BINARYMETADATA - BinaryMetaData = new BinaryMetaData(); + BinaryMetaData = _Buffer.BinaryMetaData; #endif DeserializationState |= ObjectState.Deserializing; - Deserialize(); + if (HasObjectFlag(ObjectFlagsHO.PropertiesObject) + // Just in-case we have passed an overlapped object flag in UE2 or older packages. + && _Buffer.Version >= (uint)PackageObjectLegacyVersion.ClassDefaultCheckAddedToTemplateName) + { + DeserializeClassDefault(); + } + else + { + Deserialize(); + } + DeserializationState |= ObjectState.Deserialied; +#if STRICT + Debug.Assert(Buffer.Position == Buffer.Length); +#endif } catch (Exception e) { - ThrownException = new UnrealException($"Couldn't deserialize object {GetClassName()}'{GetOuterGroup()}'", e); + ThrownException = + new UnrealException($"Couldn't deserialize object {GetReferencePath()}", e); ExceptionPosition = _Buffer?.Position ?? -1; DeserializationState |= ObjectState.Errorlized; - Console.WriteLine(e.Source + ":" + Name + ":" + e.GetType().Name + " occurred while deserializing;" - + "\r\n" + e.StackTrace - + "\r\n" + e.Message - ); + Console.Error.WriteLine($"\r\n> Object deserialization error for {GetReferencePath()} as {GetType()}" + + $"\r\n> Exception: {ThrownException}"); } finally { @@ -159,16 +182,16 @@ public void BeginDeserializing() private void InitBuffer() { - //Console.WriteLine( "Init buffer for {0}", (string)this ); - var buff = new byte[ExportTable.SerialSize]; Package.Stream.Seek(ExportTable.SerialOffset, SeekOrigin.Begin); - Package.Stream.Read(buff, 0, ExportTable.SerialSize); - if (Package.Stream.BigEndianCode) - { - Array.Reverse(buff); - } - _Buffer = new UObjectStream(Package.Stream, buff); + //Console.WriteLine( "Init buffer for {0}", (string)this ); + var buffer = new byte[ExportTable.SerialSize]; + _Buffer = new UObjectRecordStream(Package.Stream, buffer); + + // 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"); + //Debug.Assert(read == ExportTable.SerialSize, $"Incomplete read; expected a total bytes of {ExportTable.SerialSize} but got {read}"); } internal void EnsureBuffer() @@ -219,6 +242,54 @@ protected void VengeanceDeserializeHeader(IUnrealStream stream, ref (int a, int } } #endif + private void DeserializeNetIndex() + { +#if MKKE || BATMAN + if (Package.Build == UnrealPackage.GameBuild.BuildName.MKKE || + Package.Build == UnrealPackage.GameBuild.BuildName.Batman4) + { + return; + } +#endif + if (_Buffer.Version < (uint)PackageObjectLegacyVersion.NetObjectsAdded || + _Buffer.UE4Version >= 196) + { + return; + } + + _Buffer.Read(out NetIndex); + Record(nameof(NetIndex), NetIndex); + } + + /// + /// Special route for objects that are acting as the ClassDefault for a class + /// i.e. a class like PrimitiveComponent is accompanied by an instance DEFAULT_PrimitiveComponent of the same class. + /// + private void DeserializeClassDefault() + { + DeserializeNetIndex(); + DeserializeProperties(); + } + + private void DeserializeTemplate(UComponent component) + { + _Buffer.Read(out component.TemplateOwnerClass); + Record(nameof(component.TemplateOwnerClass), component.TemplateOwnerClass); + + if (_Buffer.Version < (uint)PackageObjectLegacyVersion.ClassDefaultCheckAddedToTemplateName || IsTemplate()) + { + _Buffer.Read(out component.TemplateName); + Record(nameof(component.TemplateName), component.TemplateName); + } + } + + public bool IsTemplate() + { + // The ObjectFlagsHO.ArchetypeObject flag check was added later (Not checked for in GoW), no known version. + const ObjectFlagsHO templateFlags = ObjectFlagsHO.PropertiesObject | ObjectFlagsHO.ArchetypeObject; + return HasObjectFlag(templateFlags) || EnumerateOuter().Any(obj => obj.HasObjectFlag(templateFlags)); + } + /// /// Deserialize this object's structure from the _Buffer stream. /// @@ -227,7 +298,7 @@ protected virtual void Deserialize() #if VENGEANCE if (Package.Build == BuildGeneration.Vengeance) { - if (Package.LicenseeVersion >= 25) + if (_Buffer.LicenseeVersion >= 25) { var header = (3, 0); VengeanceDeserializeHeader(_Buffer, ref header); @@ -241,8 +312,7 @@ protected virtual void Deserialize() // This appears to be serialized for templates of classes like AmbientSoundNonLoop if (HasObjectFlag(ObjectFlagsLO.HasStack)) { - StateFrame = new UStateFrame(); - StateFrame.Deserialize(_Buffer); + _Buffer.ReadClass(out StateFrame); } #if MKKE || BATMAN if (Package.Build == UnrealPackage.GameBuild.BuildName.MKKE || @@ -251,45 +321,67 @@ protected virtual void Deserialize() goto skipNetIndex; } #endif - if (_Buffer.Version >= UExportTableItem.VNetObjects) + // No version check found in the GoW PC client + if (_Buffer.Version >= (uint)PackageObjectLegacyVersion.TemplateDataAddedToUComponent) { - int netIndex = _Buffer.ReadInt32(); - Record(nameof(netIndex), netIndex); + switch (this) + { + case UComponent component: + DeserializeTemplate(component); + break; + + // HACK: Ugly work around for unregistered component classes... + // Simply for checking for the parent's class is not reliable without importing objects. + case UnknownObject _ when _Buffer.Length >= 12 && GetClassName().EndsWith("Component"): + { + var fakeComponent = new UComponent(); + DeserializeTemplate(fakeComponent); + break; + } + } } skipNetIndex: - // TODO: Serialize component data here - //if( _Buffer.Version > 400 - // && HasObjectFlag( Flags.ObjectFlagsHO.PropertiesObject ) - // && HasObjectFlag( Flags.ObjectFlagsHO.ArchetypeObject ) ) - //{ - // var componentClass = _Buffer.ReadObjectIndex(); - // var componentName = _Buffer.ReadNameIndex(); - //} + DeserializeNetIndex(); #if THIEF_DS || DEUSEX_IW // FIXME: Not present in all objects, even some classes? - if (Package.Build == BuildGeneration.Thief && GetType() != typeof(UnknownObject)) + if (Package.Build == BuildGeneration.Flesh && GetType() != typeof(UnknownObject)) { // var native private const int ObjectInternalPropertyHash[1]; int thiefLinkDataObjectCount = _Buffer.ReadInt32(); Record(nameof(thiefLinkDataObjectCount), thiefLinkDataObjectCount); - for (var i = 0; i < thiefLinkDataObjectCount; i++) + for (int i = 0; i < thiefLinkDataObjectCount; i++) { // These probably contain the missing UFields. var thiefLinkDataObject = _Buffer.ReadObject(); Record(nameof(thiefLinkDataObject), thiefLinkDataObject); } - if (!(this is UClass)) + if (ExportTable.ClassIndex != 0) { _Buffer.Skip(4); + _Buffer.ConformRecordPosition(); } } #endif - if (!IsClassType("Class")) + if (ExportTable.ClassIndex == 0) + { + return; + } + + DeserializeProperties(); +#if UE4 + if (_Buffer.UE4Version > 0) { - DeserializeProperties(); + bool shouldSerializeGuid = _Buffer.ReadInt32() > 0; + Record(nameof(shouldSerializeGuid), shouldSerializeGuid); + if (shouldSerializeGuid) + { + _Buffer.ReadStruct(out ObjectGuid); + Record(nameof(ObjectGuid), ObjectGuid); + } } +#endif } /// @@ -311,23 +403,7 @@ protected void DeserializeProperties() } } - /// - /// Initializes this object instance important members. - /// - [Obsolete("Pending deprecation")] - public virtual void PostInitialize() - { - } - - [Obsolete] - public virtual void InitializeImports() - { - throw new NotImplementedException(); - } - -#endregion - -#region Methods + #endregion /// /// Checks if the object contains the specified @flag or one of the specified flags. @@ -336,9 +412,10 @@ public virtual void InitializeImports() /// /// The flag(s) to compare to. /// Whether it contained one of the specified flags. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool HasObjectFlag(ObjectFlagsLO flag) { - return ((uint)_ObjectFlags & (uint)flag) != 0; + return (_ObjectFlags & (ulong)flag) != 0; } /// @@ -348,9 +425,10 @@ public bool HasObjectFlag(ObjectFlagsLO flag) /// /// The flag(s) to compare to. /// Whether it contained one of the specified flags. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool HasObjectFlag(ObjectFlagsHO flag) { - return ((_ObjectFlags >> 32) & (uint)flag) != 0; + return (_ObjectFlags & ((ulong)flag << 32)) != 0; } public bool IsPublic() @@ -369,74 +447,67 @@ public bool IsProtected() public bool IsPrivate() { - return (_ObjectFlags & ((ulong)ObjectFlagsLO.Public | (ulong)ObjectFlagsLO.Private)) != (ulong)ObjectFlagsLO.Public; + return (_ObjectFlags & ((ulong)ObjectFlagsLO.Public | (ulong)ObjectFlagsLO.Private)) != + (ulong)ObjectFlagsLO.Public; } /// /// Gets a human-readable name of this object instance. /// /// The human-readable name of this object instance. - [Pure] public virtual string GetFriendlyType() { return Name; } - [Obsolete] - public bool ResistsInGroup() + public IEnumerable EnumerateOuter() { - throw new NotImplementedException(); + for (var outer = Outer; outer != null; outer = outer.Outer) + { + yield return outer; + } } /// - /// Gets the highest outer relative from the specified @offset. + /// Builds a full path string of the object /// - /// Optional relative offset. - /// The highest outer. - [Obsolete] - public UObject GetHighestOuter(byte offset = 0) + /// Full path of object e.g. "Core.Object.Vector" + public string GetPath() { - throw new NotImplementedException(); + string group = EnumerateOuter().Aggregate(string.Empty, (current, outer) => $"{outer.Name}.{current}"); + return $"{group}{Name}"; } - /// - /// Gets a full name of this object instance i.e. including outers. - /// - /// e.g. var Core.Object.Vector Location; - /// - /// The full name. - [Pure] - public string GetOuterGroup() + public void GetPath(out IList chain) { - var group = string.Empty; - // TODO:Should support importtable loop - for (var outer = Outer; outer != null; outer = outer.Outer) + chain = new List(3) { this }; + foreach (var outer in EnumerateOuter()) { - group = outer.Name + "." + group; + chain.Add(outer); } - - return group + Name; } - /// - /// Gets the name of this object instance outer. - /// - /// The outer name of this object instance. - [Pure] - public string GetOuterName() + public string GetReferencePath() { - return Table.OuterName; + if (ImportTable != null) + { + return $"{ImportTable.ClassName}'{GetPath()}'"; + } + + return Class != null + ? $"{Class.Name}'{GetPath()}'" + : $"Class'{GetPath()}'"; } /// /// Gets the name of this object instance class. /// /// The class name of this object instance. - [Pure] + [Obsolete("To be deprecated")] public string GetClassName() { - return ImportTable != null - ? ImportTable.ClassName + return ImportTable != null + ? ImportTable.ClassName : Class?.Name ?? "Class"; } @@ -445,110 +516,20 @@ public string GetClassName() /// /// The class name to compare to. /// TRUE if this object instance class name is equal className, FALSE otherwise. - [Pure] public bool IsClassType(string className) { return string.Compare(GetClassName(), className, StringComparison.OrdinalIgnoreCase) == 0; } - /// - /// Checks if this object's class equals @className, parents included. - /// - /// The name of the class to compare to. - /// Whether it extends class @className. - [Pure] - public bool IsClass(string className) - { - for (var c = Table.ClassTable; c != null; c = c.ClassTable) - { - if (string.Compare(c.ObjectName, className, StringComparison.OrdinalIgnoreCase) == 0) - return true; - } - - return false; - } - - /// - /// Tests whether this Object(such as a property) is a member of a specific object, or that of its parent. - /// - /// Field to test against. - /// Whether it is a member or not. - [Pure] - public bool IsMember(UField membersClass) - { - for (var p = membersClass; p != null; p = p.Super) - { - if (Outer == p) - { - return true; - } - } - - return false; - } - /// /// Macro for getting a object instance by index. /// - [Pure] protected UObject GetIndexObject(int index) { return Package.GetIndexObject(index); } - /// - /// Try to get the object located @index. - /// - /// The object's index. - /// The reference of the specified object's index. NULL if none. - [Obsolete] - protected UObject TryGetIndexObject(int index) - { - try - { - return GetIndexObject(index); - } - catch - { - return null; - } - } - - /// - /// Loads the package that this object instance resides in. - /// - /// Note: The package closes when the Owner is done with importing objects data. - /// - [Obsolete] - protected UnrealPackage LoadImportPackage() - { - UnrealPackage pkg = null; - try - { - var outer = Outer; - while (outer != null) - { - if (outer.Outer == null) - { - pkg = UnrealLoader.LoadCachedPackage(Path.GetDirectoryName(Package.FullPackageName) + "\\" + - outer.Name + ".u"); - break; - } - - outer = outer.Outer; - } - } - catch (IOException) - { - pkg?.Dispose(); - - return null; - } - - return pkg; - } - -#region IBuffered + #region IBuffered public virtual byte[] CopyBuffer() { @@ -578,62 +559,35 @@ public virtual byte[] CopyBuffer() return bytes; } - [Pure] public IUnrealStream GetBuffer() { - return Package?.Stream == null ? null : Package.Stream; + return Package?.Stream; } - [Pure] public int GetBufferPosition() { return ExportTable?.SerialOffset ?? -1; } - [Pure] public int GetBufferSize() { return ExportTable?.SerialSize ?? 0; } - [Pure] public string GetBufferId(bool fullName = false) { return fullName - ? Package.PackageName + "." + GetOuterGroup() + "." + GetClassName() - : GetOuterGroup() + "." + GetClassName(); + ? $"{Package.PackageName}.{GetPath()}.{GetClassName()}" + : $"{GetPath()}.{GetClassName()}"; } -#endregion + #endregion - /// - /// TODO: Move this feature into a stream. - /// Outputs the present position and the value of the parsed object. - /// - /// Only called in the DEBUGBUILD! - /// - /// The struct that was read from the previous buffer position. - /// The struct's value that was read. - [System.Diagnostics.Conditional("BINARYMETADATA")] + [Conditional("BINARYMETADATA")] + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal void Record(string varName, object varObject = null) { - long size = _Buffer.Position - _Buffer.LastPosition; - BinaryMetaData.AddField(varName, varObject, _Buffer.LastPosition, size); -#if LOG_RECORDS - if( varObject == null ) - { - Console.WriteLine( varName ); - return; - } - - var propertyType = varObject.GetType(); - Console.WriteLine( - "0x" + _Buffer.LastPosition.ToString("x8").ToUpper() - + " : ".PadLeft( 2, ' ' ) - + varName.PadRight( 32, ' ' ) + ":" + propertyType.Name.PadRight( 32, ' ' ) - + " => " + varObject - ); -#endif + _Buffer.Record(varName, varObject); } protected void AssertEOS(int size, string testSubject = "") @@ -666,20 +620,31 @@ public void Dispose() Dispose(true); } - private void Dispose(bool disposing) + protected virtual void Dispose(bool disposing) { - if (disposing) + if (!disposing) { - MaybeDisposeBuffer(); + return; } + + _Buffer?.Close(); + _Buffer = null; } ~UObject() { - Dispose(false); + Dispose(true); } -#endregion + public virtual void Accept(IVisitor visitor) + { + visitor.Visit(this); + } + + public virtual TResult Accept(IVisitor visitor) + { + return visitor.Visit(this); + } public static explicit operator int(UObject obj) { @@ -690,6 +655,56 @@ private void Dispose(bool disposing) { return obj?.Name; } + + /// + /// + /// + [Obsolete("Use Outer?.Name")] + public string GetOuterName() + { + return Outer?.Name; + } + + [Obsolete("Pending deprecation")] + public virtual void PostInitialize() + { + } + + [Obsolete("Deprecated", true)] + public virtual void InitializeImports() + { + throw new NotImplementedException(); + } + + [Obsolete("Deprecated", true)] + public bool ResistsInGroup() + { + throw new NotImplementedException(); + } + + [Obsolete("Deprecated", true)] + public UObject GetHighestOuter(byte offset = 0) + { + throw new NotImplementedException(); + } + + /// + /// + /// + [Obsolete("Use GetPath instead")] + public string GetOuterGroup() => GetPath(); + + [Obsolete("Deprecated", true)] + public bool IsClass(string className) + { + throw new NotImplementedException(); + } + + [Obsolete("Deprecated", true)] + public bool IsMember(UField membersClass) + { + throw new NotImplementedException(); + } } /// @@ -709,28 +724,9 @@ public UnknownObject() ShouldDeserializeOnDemand = true; } - protected override void Deserialize() - { - if (Package.Version > 400 && _Buffer.Length >= 12) - { - // componentClassIndex - _Buffer.Position += sizeof(int); - int componentNameIndex = _Buffer.ReadNameIndex(); - if (componentNameIndex == (int)Table.ObjectName) - { - base.Deserialize(); - return; - } - - _Buffer.Position -= 12; - } - - base.Deserialize(); - } - protected override bool CanDisposeBuffer() { return false; } } -} \ No newline at end of file +} diff --git a/src/Core/Classes/UObjectDecompiler.cs b/src/Core/Classes/UObjectDecompiler.cs index bf5d57de..31f505fd 100644 --- a/src/Core/Classes/UObjectDecompiler.cs +++ b/src/Core/Classes/UObjectDecompiler.cs @@ -1,4 +1,6 @@ -namespace UELib.Core +using System.Diagnostics; + +namespace UELib.Core { public partial class UObject : IUnrealDecompilable { @@ -12,7 +14,14 @@ public virtual string Decompile() BeginDeserializing(); } - var output = $"begin object name={Name} class={Class.Name}\r\n"; + if (ImportTable != null) + { + return $"// Cannot decompile import {Name}"; + } + + Debug.Assert(Class != null); + string output = $"begin object name={Name} class={Class.Name}" + + "\r\n"; UDecompilingState.AddTabs(1); try { @@ -23,14 +32,14 @@ public virtual string Decompile() UDecompilingState.RemoveTabs(1); } - return $"{output}{UDecompilingState.Tabs}object end\r\n{UDecompilingState.Tabs}// Reference: {Class.Name}'{GetOuterGroup()}'"; + return $"{output}{UDecompilingState.Tabs}object end" + + $"\r\n{UDecompilingState.Tabs}" + + $"// Reference: {Class.Name}'{GetOuterGroup()}'"; } - // Ment to be overriden! - protected virtual string FormatHeader() + public virtual string FormatHeader() { - // Note:Dangerous recursive call! - return Decompile(); + return GetReferencePath(); } protected string DecompileProperties() @@ -39,14 +48,9 @@ protected string DecompileProperties() return UDecompilingState.Tabs + "// This object has no properties!\r\n"; var output = string.Empty; - -#if DEBUG - output += UDecompilingState.Tabs + "// Object Offset:" + - UnrealMethods.FlagToString((uint)ExportTable.SerialOffset) + "\r\n"; -#endif - for (var i = 0; i < Properties.Count; ++i) { + //output += $"{UDecompilingState.Tabs}// {Properties[i].Type}\r\n"; string propOutput = Properties[i].Decompile(); // This is the first element of a static array @@ -59,14 +63,10 @@ protected string DecompileProperties() } // FORMAT: 'DEBUG[TAB /* 0xPOSITION */] TABS propertyOutput + NEWLINE - output += UDecompilingState.Tabs + -#if DEBUG_POSITIONS - "/*" + UnrealMethods.FlagToString( (uint)Properties[i]._BeginOffset ) + "*/\t" + -#endif - propOutput + "\r\n"; + output += UDecompilingState.Tabs + propOutput + "\r\n"; } return output; } } -} \ No newline at end of file +} diff --git a/src/Core/Classes/UState.cs b/src/Core/Classes/UState.cs index 20b74c65..9abb49b8 100644 --- a/src/Core/Classes/UState.cs +++ b/src/Core/Classes/UState.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using UELib.Flags; +using UELib.Branch; namespace UELib.Core { @@ -17,6 +18,7 @@ public partial class UState : UStruct { // FIXME: Version 61 is the lowest package version I know that supports StateFlags. private const int VStateFlags = 61; + // FIXME: Version private const int VFuncMap = 220; public const int VProbeMaskReducedAndIgnoreMaskRemoved = 691; @@ -59,15 +61,20 @@ public partial class UState : UStruct protected override void Deserialize() { base.Deserialize(); - +#if UE4 + if (_Buffer.UE4Version > 0) + { + return; + } +#endif #if TRANSFORMERS - if (Package.Build == UnrealPackage.GameBuild.BuildName.Transformers) + if (Package.Build == BuildGeneration.HMS) { goto noMasks; } #endif - if (_Buffer.Version < VProbeMaskReducedAndIgnoreMaskRemoved) + if (_Buffer.Version < (uint)PackageObjectLegacyVersion.ProbeMaskReducedAndIgnoreMaskRemoved) { ProbeMask = _Buffer.ReadUInt64(); Record(nameof(ProbeMask), ProbeMask); @@ -81,7 +88,7 @@ protected override void Deserialize() Record(nameof(ProbeMask), ProbeMask); } - noMasks: + noMasks: LabelTableOffset = _Buffer.ReadUInt16(); Record(nameof(LabelTableOffset), LabelTableOffset); @@ -90,29 +97,28 @@ protected override void Deserialize() #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.Battleborn || + Package.Build == BuildGeneration.HMS || Package.Build == UnrealPackage.GameBuild.BuildName.Batman4) { _StateFlags = _Buffer.ReadUShort(); goto skipStateFlags; } #endif - _StateFlags = _Buffer.ReadUInt32(); - skipStateFlags: + skipStateFlags: Record(nameof(_StateFlags), (StateFlags)_StateFlags); } - #if TRANSFORMERS - if (Package.Build == UnrealPackage.GameBuild.BuildName.Transformers) + if (Package.Build == BuildGeneration.HMS) { _Buffer.Skip(4); + _Buffer.ConformRecordPosition(); return; } #endif - if (_Buffer.Version < VFuncMap) return; - _Buffer.ReadMap(out FuncMap); + _Buffer.ReadMap(out FuncMap); Record(nameof(FuncMap), FuncMap); } @@ -145,4 +151,4 @@ public bool HasStateFlag(uint flag) #endregion } -} \ No newline at end of file +} diff --git a/src/Core/Classes/UStateDecompiler.cs b/src/Core/Classes/UStateDecompiler.cs index df7172c5..3128e066 100644 --- a/src/Core/Classes/UStateDecompiler.cs +++ b/src/Core/Classes/UStateDecompiler.cs @@ -55,7 +55,7 @@ private string GetEdit() return HasStateFlag(Flags.StateFlags.Editable) ? "()" : string.Empty; } - protected override string FormatHeader() + public override string FormatHeader() { var output = $"{GetAuto()}{GetSimulated()}state{GetEdit()} {Name}"; if (Super != null && Super.Name != Name diff --git a/src/Core/Classes/UStruct.cs b/src/Core/Classes/UStruct.cs index ca1b784f..e4222cba 100644 --- a/src/Core/Classes/UStruct.cs +++ b/src/Core/Classes/UStruct.cs @@ -1,7 +1,10 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Runtime.CompilerServices; using UELib.Annotations; +using UELib.Branch; +using UELib.Core.Tokens; using UELib.Flags; namespace UELib.Core @@ -12,19 +15,19 @@ namespace UELib.Core [UnrealRegisterClass] public partial class UStruct : UField { - // Greater or equal than: - // Definitely not after 110 - // FIXME: Version - private const int PrimitveCastVersion = 100; - private const int VCppText = 120; - // FIXME: Version - private const int VProcessedText = 129; - // FIXME: Version private const int VFriendlyNameMoved = 160; + /// + /// Used to determine if UClass has an interfaces UArray, and the ObjectToInterface CastToken (among others). + /// FIXME: Version + /// + public const int VInterfaceClass = 220; + + private const int VStorageScriptSize = 639; + #region Serialized Members [CanBeNull] public UTextBuffer ScriptText { get; private set; } @@ -76,14 +79,19 @@ public partial class UStruct : UField protected override void Deserialize() { base.Deserialize(); + + if (_Buffer.Version >= (uint)PackageObjectLegacyVersion.SuperReferenceMovedToUStruct) + { + Super = _Buffer.ReadObject(); + Record(nameof(Super), Super); + } #if BATMAN if (Package.Build == UnrealPackage.GameBuild.BuildName.Batman4) { goto skipScriptText; } #endif - // --SuperField - if (!Package.IsConsoleCooked()) + if (!Package.IsConsoleCooked() && _Buffer.UE4Version < 117) { ScriptText = _Buffer.ReadObject(); Record(nameof(ScriptText), ScriptText); @@ -98,16 +106,45 @@ protected override void Deserialize() } #endif // Moved to UFunction in UE3 - if (Package.Version < VFriendlyNameMoved) + if (_Buffer.Version < VFriendlyNameMoved) { FriendlyName = _Buffer.ReadNameReference(); Record(nameof(FriendlyName), FriendlyName); } - +#if DNF + if (Package.Build == UnrealPackage.GameBuild.BuildName.DNF) + { + if (_Buffer.LicenseeVersion >= 17) + { + // Back-ported CppText + CppText = _Buffer.ReadObject(); + Record(nameof(CppText), CppText); + + var dnfTextObj2 = _Buffer.ReadObject(); + Record(nameof(dnfTextObj2), dnfTextObj2); + + _Buffer.ReadArray(out UArray dnfIncludeTexts); + Record(nameof(dnfIncludeTexts), dnfIncludeTexts); + } + + if (_Buffer.LicenseeVersion >= 2) + { + // Bool? + byte dnfByte = _Buffer.ReadByte(); + Record(nameof(dnfByte), dnfByte); + + var dnfName = _Buffer.ReadNameReference(); + Record(nameof(dnfName), dnfName); + } + + goto lineData; + } +#endif // Standard, but UT2004' derived games do not include this despite reporting version 128+ - if (Package.Version >= VCppText - && !Package.IsConsoleCooked() - && Package.Build != BuildGeneration.UE2_5) + if (_Buffer.Version >= VCppText && _Buffer.UE4Version < 117 + && !Package.IsConsoleCooked() && + (Package.Build != BuildGeneration.UE2_5 && + Package.Build != BuildGeneration.AGP)) { CppText = _Buffer.ReadObject(); Record(nameof(CppText), CppText); @@ -115,7 +152,7 @@ protected override void Deserialize() #if VENGEANCE // Introduced with BioShock if (Package.Build == BuildGeneration.Vengeance && - Package.LicenseeVersion >= 29) + _Buffer.LicenseeVersion >= 29) { int vengeanceUnknownObject = _Buffer.ReadObjectIndex(); Record(nameof(vengeanceUnknownObject), vengeanceUnknownObject); @@ -123,21 +160,24 @@ 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 == BuildGeneration.UE2_5 && Package.LicenseeVersion >= 26) || - (Package.Build == BuildGeneration.Vengeance && Package.LicenseeVersion >= 2)) + if ((Package.Build == BuildGeneration.UE2_5 && _Buffer.LicenseeVersion >= 26) || + (Package.Build == BuildGeneration.AGP && _Buffer.LicenseeVersion >= 17) || + (Package.Build == BuildGeneration.Vengeance && _Buffer.LicenseeVersion >= 2)) { StructFlags = _Buffer.ReadUInt32(); Record(nameof(StructFlags), (StructFlags)StructFlags); } #if VENGEANCE if (Package.Build == BuildGeneration.Vengeance && - Package.LicenseeVersion >= 14) + _Buffer.LicenseeVersion >= 14) { ProcessedText = _Buffer.ReadObject(); Record(nameof(ProcessedText), ProcessedText); } #endif - if (!Package.IsConsoleCooked()) + lineData: + if (!Package.IsConsoleCooked() && + _Buffer.UE4Version < 117) { Line = _Buffer.ReadInt32(); Record(nameof(Line), Line); @@ -155,21 +195,17 @@ protected override void Deserialize() } #endif #if TRANSFORMERS - if (Package.Build == UnrealPackage.GameBuild.BuildName.Transformers) + if (Package.Build == BuildGeneration.HMS) { + int transformersEndLine = _Buffer.ReadInt32(); // The line where the struct's code body ends. - _Buffer.Skip(4); + Record(nameof(transformersEndLine), transformersEndLine); } #endif serializeByteCode: ByteScriptSize = _Buffer.ReadInt32(); Record(nameof(ByteScriptSize), ByteScriptSize); - const int vDataScriptSize = 639; - bool hasFixedScriptSize = Package.Version >= vDataScriptSize -#if TRANSFORMERS - && Package.Build != UnrealPackage.GameBuild.BuildName.Transformers -#endif - ; + bool hasFixedScriptSize = _Buffer.Version >= VStorageScriptSize; if (hasFixedScriptSize) { DataScriptSize = _Buffer.ReadInt32(); @@ -193,24 +229,16 @@ protected override void Deserialize() } else { - const int moonbaseVersion = 587; - const int shadowcomplexVersion = 590; - - bool isTrueScriptSize = Package.Build == UnrealPackage.GameBuild.BuildName.MOHA || - ( - Package.Version >= UnrealPackage.VINDEXDEPRECATED - && (Package.Version < moonbaseVersion && - Package.Version > shadowcomplexVersion) - ); - if (isTrueScriptSize) - { - _Buffer.Skip(DataScriptSize); - } - else - { - ByteCodeManager.Deserialize(); - } + ByteCodeManager.Deserialize(); } + + _Buffer.ConformRecordPosition(); +#if DNF + if (Package.Build == UnrealPackage.GameBuild.BuildName.DNF) + { + //_Buffer.ReadByte(); + } +#endif } protected override bool CanDisposeBuffer() @@ -233,7 +261,7 @@ public override void PostInitialize() Console.WriteLine(ice.Message); } } - + [Obsolete("Pending deprecation")] protected virtual void FindChildren() { @@ -256,7 +284,7 @@ protected virtual void FindChildren() { Enums.Insert(0, (UEnum)child); } - else if (child is UStruct && ((UStruct)(child)).IsPureStruct()) + else if (child is UStruct && ((UStruct)child).IsPureStruct()) { Structs.Insert(0, (UStruct)child); } @@ -276,20 +304,33 @@ protected virtual void FindChildren() } } -#endregion - -#region Methods + #endregion + + public IEnumerable EnumerateFields() + { + for (var field = Children; field != null; field = field.NextField) + { + yield return field; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public TokenFactory GetTokenFactory() + { + return Package.Branch.GetTokenFactory(Package); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool HasStructFlag(StructFlags flag) { return (StructFlags & (uint)flag) != 0; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool IsPureStruct() { return IsClassType("Struct") || IsClassType("ScriptStruct"); } - -#endregion } -} \ No newline at end of file +} diff --git a/src/Core/Classes/UStructDecompiler.cs b/src/Core/Classes/UStructDecompiler.cs index e12b6acd..82fbb1ba 100644 --- a/src/Core/Classes/UStructDecompiler.cs +++ b/src/Core/Classes/UStructDecompiler.cs @@ -49,7 +49,7 @@ public override string Decompile() return content + UnrealConfig.PrintEndBracket() + ";"; } - protected override string FormatHeader() + public override string FormatHeader() { var output = $"struct {FormatFlags()}{Name}{(Super != null ? $" {FormatExtends()} {Super.Name}" : string.Empty)}"; string metaData = DecompileMeta(); @@ -214,6 +214,11 @@ protected string FormatProperties() : $"({property.CategoryName})"; } output += $" {property.Decompile()};"; + string s = property.PostDecompile(); + if (!string.IsNullOrEmpty(s)) + { + output += s; + } } return output + "\r\n"; @@ -233,7 +238,7 @@ public string FormatDefaultProperties() var output = string.Empty; string innerOutput; - if (IsClassType("Class")) + if (ExportTable.ClassIndex == 0) { output += "\r\n" + "defaultproperties" + @@ -257,7 +262,7 @@ public string FormatDefaultProperties() } catch (Exception e) { - innerOutput = $"{UDecompilingState.Tabs}// {e.GetType().Name} occurred while decompiling properties!" + + innerOutput = $"{UDecompilingState.Tabs}/* {e} */ // occurred while decompiling properties!" + "\r\n"; } finally @@ -321,4 +326,4 @@ protected string DecompileScript() } } } -#endif \ No newline at end of file +#endif diff --git a/src/Core/Classes/UTextBuffer.cs b/src/Core/Classes/UTextBuffer.cs index ec0c469c..332885a8 100644 --- a/src/Core/Classes/UTextBuffer.cs +++ b/src/Core/Classes/UTextBuffer.cs @@ -7,7 +7,7 @@ public partial class UTextBuffer : UObject public uint Top; public uint Pos; - + public string ScriptText; #endregion @@ -22,11 +22,31 @@ public UTextBuffer() protected override void Deserialize() { base.Deserialize(); - + Top = _Buffer.ReadUInt32(); Record(nameof(Top), Top); Pos = _Buffer.ReadUInt32(); Record(nameof(Pos), Pos); +#if UNDYING + if (Package.Build == UnrealPackage.GameBuild.BuildName.Undying && + _Buffer.Version >= 85) + { + int uncompressedDataSize = _Buffer.ReadIndex(); + Record(nameof(uncompressedDataSize), uncompressedDataSize); + + int dataLength = _Buffer.ReadIndex(); + Record(nameof(dataLength), dataLength); + if (dataLength > 0) + { + var data = new byte[uncompressedDataSize]; + _Buffer.Read(data, 0, dataLength); + Record(nameof(data), data); + } + + ScriptText = "Text data is compressed"; + return; + } +#endif ScriptText = _Buffer.ReadText(); Record(nameof(ScriptText), "..."); } diff --git a/src/Core/Classes/UTextBufferDecompiler.cs b/src/Core/Classes/UTextBufferDecompiler.cs index 8e7244f2..8f052421 100644 --- a/src/Core/Classes/UTextBufferDecompiler.cs +++ b/src/Core/Classes/UTextBufferDecompiler.cs @@ -38,10 +38,5 @@ public override string Decompile() return output; } - - protected override string FormatHeader() - { - return "// Postprocessed copy of the source code."; - } } } \ No newline at end of file diff --git a/src/Core/Tables/UExportTableItem.cs b/src/Core/Tables/UExportTableItem.cs index b65d31a8..7bc7a608 100644 --- a/src/Core/Tables/UExportTableItem.cs +++ b/src/Core/Tables/UExportTableItem.cs @@ -1,33 +1,68 @@ using System; -using System.Diagnostics; -using System.Diagnostics.Contracts; +using System.ComponentModel; using System.IO; +using UELib.Annotations; +using UELib.Branch; +using UELib.Core; namespace UELib { /// - /// An export table entry, representing a @UObject in a package. + /// An export table entry, represents a @UObject export within a package. /// public sealed class UExportTableItem : UObjectTableItem, IUnrealSerializableClass { private const int VArchetype = 220; public const int VObjectFlagsToULONG = 195; + private const int VSerialSizeConditionless = 249; - // FIXME: Version? - public const int VNetObjects = 322; #region Serialized Members + + private int _ClassIndex; + public int ClassIndex + { + get => _ClassIndex; + set => _ClassIndex = value; + } - /// - /// Object index to the Super(parent) object of structs. - /// -- Not Fixed - /// - public int SuperIndex { get; private set; } + [CanBeNull] + public UObjectTableItem Class => Owner.GetIndexTable(ClassIndex); + + public int _SuperIndex; + public int SuperIndex + { + get => _SuperIndex; + set => _SuperIndex = value; + } + [CanBeNull] + public UObjectTableItem Super => Owner.GetIndexTable(_SuperIndex); + + public int _TemplateIndex; + public int TemplateIndex + { + get => _TemplateIndex; + set => _TemplateIndex = value; + } + [CanBeNull] + public UObjectTableItem Template => Owner.GetIndexTable(_TemplateIndex); - [Pure] - public UObjectTableItem SuperTable => Owner.GetIndexTable(SuperIndex); + public int _ArchetypeIndex; + public int ArchetypeIndex + { + get => _ArchetypeIndex; + set => _ArchetypeIndex = value; + } + [CanBeNull] + public UObjectTableItem Archetype => Owner.GetIndexTable(_ArchetypeIndex); + + [Obsolete("Use Class"), Browsable(false)] public UObjectTableItem ClassTable => Owner.GetIndexTable(_ClassIndex); + + [Obsolete] + protected override int __ClassIndex => _ClassIndex; - [Pure] + [Obsolete("Use Super"), Browsable(false)] public UObjectTableItem SuperTable => Owner.GetIndexTable(_SuperIndex); + [Obsolete("Use Super?.ObjectName"), Browsable(false)] public string SuperName { get @@ -37,16 +72,8 @@ public string SuperName } } - /// - /// Object index. - /// -- Not Fixed - /// - public int ArchetypeIndex { get; private set; } - - [Pure] - public UObjectTableItem ArchetypeTable => Owner.GetIndexTable(ArchetypeIndex); - - [Pure] + [Obsolete("Use Archetype"), Browsable(false)] public UObjectTableItem ArchetypeTable => Owner.GetIndexTable(_ArchetypeIndex); + [Obsolete("Use Archetype?.ObjectName"), Browsable(false)] public string ArchetypeName { get @@ -76,22 +103,28 @@ public string ArchetypeName //public Dictionary Components; //public List NetObjects; + public UGuid PackageGuid; + public uint PackageFlags; + + public bool IsNotForServer; + public bool IsNotForClient; + public bool IsForcedExport; + public bool IsNotForEditorGame; + public bool IsAsset; + #endregion // @Warning - Only supports Official builds. public void Serialize(IUnrealStream stream) { - stream.Write(ClassTable.Object); - stream.Write(SuperTable.Object); - stream.Write((int)OuterTable.Object); - + stream.Write(_ClassIndex); + stream.Write(_SuperIndex); + stream.Write(OuterIndex); stream.Write(ObjectName); - if (stream.Version >= VArchetype) { - ArchetypeIndex = stream.ReadInt32(); + _ArchetypeIndex = stream.ReadInt32(); } - stream.Write(stream.Version >= VObjectFlagsToULONG ? ObjectFlags : (uint)ObjectFlags); @@ -111,23 +144,8 @@ public void Serialize(IUnrealStream stream) public void Deserialize(IUnrealStream stream) { -#if AA2 - // Not attested in packages of LicenseeVersion 32 - if (stream.Package.Build == UnrealPackage.GameBuild.BuildName.AA2 && - stream.Package.LicenseeVersion >= 33) - { - SuperIndex = stream.ReadObjectIndex(); - int unkInt = stream.ReadInt32(); - Debug.WriteLine(unkInt, "unkInt"); - ClassIndex = stream.ReadObjectIndex(); - OuterIndex = stream.ReadInt32(); - ObjectFlags = ~stream.ReadUInt32(); - ObjectName = stream.ReadNameReference(); - goto streamSerialSize; - } -#endif - ClassIndex = stream.ReadObjectIndex(); - SuperIndex = stream.ReadObjectIndex(); + _ClassIndex = stream.ReadObjectIndex(); + _SuperIndex = stream.ReadObjectIndex(); OuterIndex = stream.ReadInt32(); // ObjectIndex, though always written as 32bits regardless of build. #if BIOSHOCK if (stream.Package.Build == UnrealPackage.GameBuild.BuildName.BioShock && @@ -139,21 +157,19 @@ public void Deserialize(IUnrealStream stream) ObjectName = stream.ReadNameReference(); if (stream.Version >= VArchetype) { - ArchetypeIndex = stream.ReadInt32(); + _ArchetypeIndex = stream.ReadInt32(); } - #if BATMAN if (stream.Package.Build == BuildGeneration.RSS) { stream.Skip(sizeof(int)); } #endif - _ObjectFlagsOffset = stream.Position; #if BIOSHOCK // Like UE3 but without the shifting of flags if (stream.Package.Build == UnrealPackage.GameBuild.BuildName.BioShock && - stream.Package.LicenseeVersion >= 40) + stream.LicenseeVersion >= 40) { ObjectFlags = stream.ReadUInt64(); goto streamSerialSize; @@ -165,7 +181,7 @@ public void Deserialize(IUnrealStream stream) ObjectFlags = (ObjectFlags << 32) | stream.ReadUInt32(); } - streamSerialSize: + streamSerialSize: SerialSize = stream.ReadIndex(); if (SerialSize > 0 || stream.Version >= VSerialSizeConditionless) { @@ -173,7 +189,7 @@ public void Deserialize(IUnrealStream stream) // FIXME: Can't change SerialOffset to 64bit due UE Explorer. if (stream.Package.Build == UnrealPackage.GameBuild.BuildName.RocketLeague && - stream.Package.LicenseeVersion >= 22) + stream.LicenseeVersion >= 22) { SerialOffset = stream.ReadIndex(); goto streamExportFlags; @@ -194,11 +210,11 @@ public void Deserialize(IUnrealStream stream) if (stream.Version < 543 #if ALPHAPROTOCOL - && stream.Package.Build != UnrealPackage.GameBuild.BuildName.AlphaProtcol + && stream.Package.Build != UnrealPackage.GameBuild.BuildName.AlphaProtocol #endif #if TRANSFORMERS - && (stream.Package.Build != UnrealPackage.GameBuild.BuildName.Transformers || - stream.Package.LicenseeVersion < 37) + && (stream.Package.Build != BuildGeneration.HMS || + stream.LicenseeVersion < 37) #endif ) { @@ -210,13 +226,13 @@ public void Deserialize(IUnrealStream stream) if (stream.Version < 247) return; - streamExportFlags: + streamExportFlags: ExportFlags = stream.ReadUInt32(); - if (stream.Version < VNetObjects) + if (stream.Version < (uint)PackageObjectLegacyVersion.NetObjectsAdded) return; #if TRANSFORMERS - if (stream.Package.Build == UnrealPackage.GameBuild.BuildName.Transformers && - stream.Package.LicenseeVersion >= 116) + if (stream.Package.Build == BuildGeneration.HMS && + stream.LicenseeVersion >= 116) { byte flag = stream.ReadByte(); if (flag == 0) @@ -260,9 +276,8 @@ public void Deserialize(IUnrealStream stream) stream.Skip(4); // Package flags } } - - #region Writing Methods - + + [Obsolete] private long _ObjectFlagsOffset; /// @@ -272,18 +287,30 @@ public void Deserialize(IUnrealStream stream) public void WriteObjectFlags() { Owner.Stream.Seek(_ObjectFlagsOffset, SeekOrigin.Begin); - Owner.Stream.UW.Write((uint)ObjectFlags); + Owner.Stream.Writer.Write((uint)ObjectFlags); } - #endregion - - #region Methods + public override string GetReferencePath() + { + return Class != null + ? $"{Class.ObjectName}'{GetPath()}'" + : $"Class'{GetPath()}'"; + } public override string ToString() { - return ObjectName + "(" + Index + 1 + ")"; + return $"{ObjectName}({Index}{1})"; } - #endregion + [Obsolete("Use ToString()")] + public string ToString(bool v) + { + return ToString(); + } + + public static explicit operator int(UExportTableItem item) + { + return item.Index; + } } -} \ No newline at end of file +} diff --git a/src/Core/Tables/UGenerationTableItem.cs b/src/Core/Tables/UGenerationTableItem.cs index 97fdd01a..8389467c 100644 --- a/src/Core/Tables/UGenerationTableItem.cs +++ b/src/Core/Tables/UGenerationTableItem.cs @@ -1,30 +1,59 @@ +using System; +using UELib.Annotations; + namespace UELib { + [PublicAPI] public struct UGenerationTableItem : IUnrealSerializableClass { - public int ExportsCount; - public int NamesCount; - public int NetObjectsCount; + private int _ExportCount; + private int _NameCount; + private int _NetObjectCount; + + public int ExportCount + { + get => _ExportCount; + set => _ExportCount = value; + } + + public int NameCount + { + get => _NameCount; + set => _NameCount = value; + } + + public int NetObjectCount + { + get => _NetObjectCount; + set => _NetObjectCount = value; + } + + [Obsolete] + public object ExportsCount => ExportCount; + [Obsolete] + public object NamesCount => NameCount; + [Obsolete] + public object NetObjectsCount => NetObjectCount; - private const int VNetObjectsCount = 322; + public const int VNetObjectsCount = 322; public void Serialize(IUnrealStream stream) { - stream.Write(ExportsCount); - stream.Write(NamesCount); - if (stream.Version >= VNetObjectsCount) + stream.Write(_ExportCount); + stream.Write(_NameCount); + if (stream.Version >= VNetObjectsCount && stream.UE4Version < 186) { - stream.Write(NetObjectsCount); + stream.Write(_NetObjectCount); } } public void Deserialize(IUnrealStream stream) { - ExportsCount = stream.ReadInt32(); - NamesCount = stream.ReadInt32(); - if (stream.Version >= VNetObjectsCount) + stream.Read(out _ExportCount); + stream.Read(out _NameCount); + if (stream.Version >= VNetObjectsCount && stream.UE4Version < 186) { - NetObjectsCount = stream.ReadInt32(); + stream.Read(out _NetObjectCount); } } } diff --git a/src/Core/Tables/UImportTableItem.cs b/src/Core/Tables/UImportTableItem.cs index 8e6bf41a..5d09a02a 100644 --- a/src/Core/Tables/UImportTableItem.cs +++ b/src/Core/Tables/UImportTableItem.cs @@ -1,64 +1,71 @@ -using System.Diagnostics; -using System.Diagnostics.Contracts; +using System; +using UELib.Core; namespace UELib { /// - /// An import table entry, representing a @UObject dependency in a package. - /// This includes the name of the package that this dependency belongs to. + /// An import table entry, represents a @UObject import within a package. /// public sealed class UImportTableItem : UObjectTableItem, IUnrealSerializableClass { #region Serialized Members - public UName PackageName; + private UName _PackageName; + + public UName PackageName + { + get => _PackageName; + set => _PackageName = value; + } + private UName _ClassName; - [Pure] - public override string ClassName => _ClassName; + public UName ClassName + { + get => _ClassName; + set => _ClassName = value; + } + + [Obsolete] protected override string __ClassName => _ClassName; + [Obsolete] protected override int __ClassIndex => (int)_ClassName; #endregion public void Serialize(IUnrealStream stream) { - stream.Write(PackageName); + stream.Write(_PackageName); stream.Write(_ClassName); - stream.Write(OuterTable != null ? (int)OuterTable.Object : 0); // Always an ordinary integer - stream.Write(ObjectName); + stream.Write(_OuterIndex); // Always an ordinary integer + stream.Write(_ObjectName); } public void Deserialize(IUnrealStream stream) { -#if AA2 - // Not attested in packages of LicenseeVersion 32 - if (stream.Package.Build == UnrealPackage.GameBuild.BuildName.AA2 - && stream.Package.LicenseeVersion >= 33) - { - PackageName = stream.ReadNameReference(); - _ClassName = stream.ReadNameReference(); - ClassIndex = (int)_ClassName; - byte unkByte = stream.ReadByte(); - Debug.WriteLine(unkByte, "unkByte"); - ObjectName = stream.ReadNameReference(); - OuterIndex = stream.ReadInt32(); // ObjectIndex, though always written as 32bits regardless of build. - return; - } -#endif - - PackageName = stream.ReadNameReference(); + _PackageName = stream.ReadNameReference(); _ClassName = stream.ReadNameReference(); - ClassIndex = (int)_ClassName; - OuterIndex = stream.ReadInt32(); // ObjectIndex, though always written as 32bits regardless of build. - ObjectName = stream.ReadNameReference(); + _OuterIndex = stream.ReadInt32(); // ObjectIndex, though always written as 32bits regardless of build. + _ObjectName = stream.ReadNameReference(); } - #region Methods + public override string GetReferencePath() + { + return $"{_ClassName}'{GetPath()}'"; + } public override string ToString() { return $"{ObjectName}({-(Index + 1)})"; } - #endregion + [Obsolete("Use ToString()")] + public string ToString(bool v) + { + return ToString(); + } + + public static explicit operator int(UImportTableItem item) + { + return -item.Index; + } } -} \ No newline at end of file +} diff --git a/src/Core/Tables/UNameTableItem.cs b/src/Core/Tables/UNameTableItem.cs index 3e15248e..c0daf71c 100644 --- a/src/Core/Tables/UNameTableItem.cs +++ b/src/Core/Tables/UNameTableItem.cs @@ -1,6 +1,5 @@ using System.Diagnostics; using System.Runtime.CompilerServices; -using UELib.Decoding; namespace UELib { @@ -11,34 +10,37 @@ public sealed class UNameTableItem : UTableItem, IUnrealSerializableClass { #region Serialized Members - /// - /// Object Name - /// - public string Name = string.Empty; + public string Name + { + get => _Name; + set => _Name = value; + } + private string _Name; - /// - /// Object Flags, such as LoadForEdit, LoadForServer, LoadForClient - /// - /// - /// 32bit in UE2 - /// 64bit in UE3 - /// - public ulong Flags; + public ulong Flags + { + get => _Flags; + set => _Flags = value; + } + private ulong _Flags; + public ushort NonCasePreservingHash; + public ushort CasePreservingHash; + #endregion public void Deserialize(IUnrealStream stream) { - Name = DeserializeName(stream); - Debug.Assert(Name.Length <= 1024, "Maximum name length exceeded! Possible corrupt or unsupported package."); + _Name = DeserializeName(stream); + Debug.Assert(_Name.Length <= 1024, "Maximum name length exceeded! Possible corrupt or unsupported package."); #if BIOSHOCK if (stream.Package.Build == UnrealPackage.GameBuild.BuildName.BioShock) { - Flags = stream.ReadUInt64(); + _Flags = stream.ReadUInt64(); return; } #endif - Flags = stream.Version >= UExportTableItem.VObjectFlagsToULONG + _Flags = stream.Version >= UExportTableItem.VObjectFlagsToULONG ? stream.ReadUInt64() : stream.ReadUInt32(); } @@ -48,60 +50,30 @@ private string DeserializeName(IUnrealStream stream) { #if UE1 // Very old packages use a simple Ansi encoding. - if (stream.Version < UnrealPackage.VSIZEPREFIXDEPRECATED) return stream.ReadASCIIString(); -#endif -#if AA2 - // Names are not encrypted in AAA/AAO 2.6 (LicenseeVersion 32) - if (stream.Package.Build == UnrealPackage.GameBuild.BuildName.AA2 - && stream.Package.LicenseeVersion >= 33 - && stream.Package.Decoder is CryptoDecoderAA2) - { - // Thanks to @gildor2, decryption code transpiled from https://github.com/gildor2/UEViewer, - int length = stream.ReadIndex(); - Debug.Assert(length < 0); - int size = -length; - - const byte n = 5; - byte shift = n; - var buffer = new char[size]; - for (var i = 0; i < size; i++) - { - ushort c = stream.ReadUInt16(); - ushort c2 = CryptoCore.RotateRight(c, shift); - Debug.Assert(c2 < byte.MaxValue); - buffer[i] = (char)(byte)c2; - shift = (byte)((c - n) & 0x0F); - } - - var str = new string(buffer, 0, buffer.Length - 1); - // Part of name ? - int number = stream.ReadIndex(); - //Debug.Assert(number == 0, "Unknown value"); - return str; - } + if (stream.Version < UnrealPackage.VSIZEPREFIXDEPRECATED) return stream.ReadAnsiNullString(); #endif return stream.ReadText(); } public void Serialize(IUnrealStream stream) { - stream.Write(Name); + stream.Write(_Name); if (stream.Version < UExportTableItem.VObjectFlagsToULONG) // Writing UINT - stream.Write((uint)Flags); + stream.Write((uint)_Flags); else // Writing ULONG - stream.Write(Flags); + stream.Write(_Flags); } public override string ToString() { - return Name; + return _Name; } public static implicit operator string(UNameTableItem a) { - return a.Name; + return a._Name; } public static implicit operator int(UNameTableItem a) diff --git a/src/Core/Tables/UObjectTableItem.cs b/src/Core/Tables/UObjectTableItem.cs index 39834d9f..c863d024 100644 --- a/src/Core/Tables/UObjectTableItem.cs +++ b/src/Core/Tables/UObjectTableItem.cs @@ -1,17 +1,18 @@ using System; -using System.Diagnostics.Contracts; +using System.Collections.Generic; +using System.ComponentModel; using System.IO; +using System.Linq; +using UELib.Annotations; using UELib.Core; namespace UELib { /// - /// An abstract implementation of the export and import table entries. + /// An internal implementation for the Export and Import table classes. /// - public abstract class UObjectTableItem : UTableItem, IBuffered + public abstract class UObjectTableItem : UTableItem, IBuffered, IComparable { - #region PreInitialized Members - /// /// Reference to the UnrealPackage this object resists in /// @@ -24,53 +25,81 @@ public abstract class UObjectTableItem : UTableItem, IBuffered /// public UObject Object; - #endregion - #region Serialized Members - /// - /// Name index to the name of this object - /// -- Fixed - /// - [Pure] - public UNameTableItem ObjectTable => Owner.Names[(int)ObjectName]; - - public UName ObjectName; - - /// - /// Import:Name index to the class of this object - /// Export:Object index to the class of this object - /// -- Not Fixed - /// - public int ClassIndex { get; protected set; } - - [Pure] - public UObjectTableItem ClassTable => Owner.GetIndexTable(ClassIndex); - - [Pure] - public virtual string ClassName => ClassIndex != 0 ? Owner.GetIndexTable(ClassIndex).ObjectName : "Class"; + protected UName _ObjectName; + public UName ObjectName + { + get => _ObjectName; + set => _ObjectName = value; + } - /// - /// Object index to the outer of this object - /// -- Not Fixed - /// - public int OuterIndex { get; protected set; } + protected int _OuterIndex; + public int OuterIndex + { + get => _OuterIndex; + set => _OuterIndex = value; + } - [Pure] - public UObjectTableItem OuterTable => Owner.GetIndexTable(OuterIndex); + [Obsolete, Browsable(false)] public UNameTableItem ObjectTable => Owner.Names[(int)_ObjectName]; + [Obsolete("Use UExportTableItem.Class"), Browsable(false)] public UObjectTableItem ClassTable => Owner.GetIndexTable(ClassIndex); + [Obsolete, Browsable(false)] public UObjectTableItem OuterTable => Owner.GetIndexTable(OuterIndex); + public UObjectTableItem Outer => Owner.GetIndexTable(_OuterIndex); - [Pure] + [Obsolete("Use Outer?.ObjectName"), Browsable(false)] public string OuterName { get { var table = OuterTable; - return table != null ? table.ObjectName : string.Empty; + return table != null ? table._ObjectName : string.Empty; } } + [Obsolete("Use UExportTableItem.ClassIndex"), Browsable(false)] + public int ClassIndex => __ClassIndex; + + [Obsolete] + protected virtual int __ClassIndex => 0; + + [Obsolete("Use Class?.ObjectName or UImportTableItem.ClassName"), Browsable(false)] + public string ClassName => __ClassName; + + [Obsolete] + protected virtual string __ClassName => ""; + #endregion + public IEnumerable EnumerateOuter() + { + for (var outer = Outer; outer != null; outer = outer.Outer) + { + yield return outer; + } + } + + /// + /// Builds a full path string of the object + /// + /// Full path of object e.g. "Core.Object.Vector" + public string GetPath() + { + string group = EnumerateOuter().Aggregate(string.Empty, (current, outer) => $"{outer.ObjectName}.{current}"); + return $"{group}{ObjectName}"; + } + + public virtual string GetReferencePath() + { + return $"?'{GetPath()}'"; + } + + public static string GetReferencePath([CanBeNull] UObjectTableItem item) + { + return item != null + ? item.GetReferencePath() + : "None"; + } + #region IBuffered public virtual byte[] CopyBuffer() @@ -86,30 +115,36 @@ public virtual byte[] CopyBuffer() return buff; } - [Pure] public IUnrealStream GetBuffer() { return Owner.Stream; } - [Pure] public int GetBufferPosition() { return Offset; } - [Pure] public int GetBufferSize() { return Size; } - [Pure] public string GetBufferId(bool fullName = false) { - return fullName ? Owner.PackageName + "." + ObjectName + ".table" : ObjectName + ".table"; + return fullName ? Owner.PackageName + "." + _ObjectName + ".table" : _ObjectName + ".table"; } #endregion + + public static explicit operator int(UObjectTableItem item) + { + return item.Index; + } + + public int CompareTo(string other) + { + return string.Compare(ObjectName.ToString(), other, StringComparison.Ordinal); + } } -} \ No newline at end of file +} diff --git a/src/Core/Tables/UTableItem.cs b/src/Core/Tables/UTableItem.cs index 0476af79..6eff9aed 100644 --- a/src/Core/Tables/UTableItem.cs +++ b/src/Core/Tables/UTableItem.cs @@ -5,37 +5,26 @@ namespace UELib /// /// An abstract class for any table entry to inherit from. /// - public abstract class UTableItem + public abstract class UTableItem : IComparable { - #region PreInitialized Members - /// /// Index into the table's enumerable. /// + [System.ComponentModel.Browsable(false)] public int Index { get; internal set; } /// /// Table offset in bytes. /// + [System.ComponentModel.Browsable(false)] public int Offset { get; internal set; } /// /// Table size in bytes. /// + [System.ComponentModel.Browsable(false)] public int Size { get; internal set; } - #endregion - - #region Methods - - public string ToString(bool shouldPrintMembers) - { - return shouldPrintMembers - ? String.Format("\r\nTable Index:{0}\r\nTable Offset:0x{1:X8}\r\nTable Size:0x{2:X8}\r\n", Index, - Offset, Size) - : base.ToString(); - } - - #endregion + public int CompareTo(UTableItem other) => other.Offset; } -} \ No newline at end of file +} diff --git a/src/Core/Tokens/ArrayTokens.cs b/src/Core/Tokens/ArrayTokens.cs index ea244682..3b1d0303 100644 --- a/src/Core/Tokens/ArrayTokens.cs +++ b/src/Core/Tokens/ArrayTokens.cs @@ -1,14 +1,14 @@ -namespace UELib.Core +using UELib.Branch; +using UELib.ObjectModel.Annotations; +using UELib.Tokens; + +namespace UELib.Core { public partial class UStruct { public partial class UByteCodeDecompiler { - private const uint ArrayMethodEndParmsVersion = 648; // TODO: Corrigate Version - - private const uint - ArrayMethodSizeParmsVersion = 480; // TODO: Corrigate Version (Definitely before 490(GoW)) - + [ExprToken(ExprToken.ArrayElement)] public class ArrayElementToken : Token { public override void Deserialize(IUnrealStream stream) @@ -29,10 +29,12 @@ public override string Decompile() } } + [ExprToken(ExprToken.DynArrayElement)] public class DynamicArrayElementToken : ArrayElementToken { } + [ExprToken(ExprToken.DynArrayLength)] public class DynamicArrayLengthToken : Token { public override void Deserialize(IUnrealStream stream) @@ -48,15 +50,14 @@ public override string Decompile() } } - // TODO:Byte code of this has apparently changed to ReturnNothing in UE3 - public class DynamicArrayMethodToken : Token + public abstract class DynamicArrayMethodToken : Token { - protected virtual void DeserializeMethodOne(IUnrealStream stream, bool skipEndParms = false) + protected void DeserializeOneParamMethodWithSkip(IUnrealStream stream, uint skipSizeVersion = (uint)PackageObjectLegacyVersion.SkipSizeAddedToArrayTokenIntrinsics) { // Array DeserializeNext(); - if (stream.Version > ArrayMethodSizeParmsVersion) + if (stream.Version >= skipSizeVersion) { // Size stream.Skip(2); @@ -66,19 +67,58 @@ protected virtual void DeserializeMethodOne(IUnrealStream stream, bool skipEndPa // Param 1 DeserializeNext(); - if (stream.Version > ArrayMethodEndParmsVersion && !skipEndParms) + if (stream.Version >= (uint)PackageObjectLegacyVersion.EndTokenAppendedToArrayTokenIntrinsics) { // EndParms DeserializeNext(); } + + Decompiler.DeserializeDebugToken(); } + + protected void DeserializeOneParamMethodNoSkip(IUnrealStream stream) + { + // Array + DeserializeNext(); + + // Param 1 + DeserializeNext(); - protected virtual void DeserializeMethodTwo(IUnrealStream stream, bool skipEndParms = false) + if (stream.Version >= (uint)PackageObjectLegacyVersion.EndTokenAppendedToArrayTokenIntrinsics) + { + // EndParms + DeserializeNext(); + } + + Decompiler.DeserializeDebugToken(); + } + + protected void DeserializeTwoParamMethodNoSkip(IUnrealStream stream) { // Array DeserializeNext(); - if (stream.Version > ArrayMethodSizeParmsVersion) + // Param 1 + DeserializeNext(); + + // Param 2 + DeserializeNext(); + + if (stream.Version >= (uint)PackageObjectLegacyVersion.EndTokenAppendedToArrayTokenIntrinsics) + { + // EndParms + DeserializeNext(); + } + + Decompiler.DeserializeDebugToken(); + } + + protected void DeserializeTwoParamMethodWithSkip(IUnrealStream stream, uint skipSizeVersion = (uint)PackageObjectLegacyVersion.SkipSizeAddedToArrayTokenIntrinsics) + { + // Array + DeserializeNext(); + + if (stream.Version >= skipSizeVersion) { // Size stream.Skip(2); @@ -91,70 +131,76 @@ protected virtual void DeserializeMethodTwo(IUnrealStream stream, bool skipEndPa // Param 2 DeserializeNext(); - if (stream.Version > ArrayMethodEndParmsVersion && !skipEndParms) + if (stream.Version >= (uint)PackageObjectLegacyVersion.EndTokenAppendedToArrayTokenIntrinsics) { // EndParms DeserializeNext(); } + + Decompiler.DeserializeDebugToken(); } - protected string DecompileMethodOne(string functionName, bool skipEndParms = false) + protected string DecompileOneParamMethod(string functionName) { Decompiler._CanAddSemicolon = true; - return DecompileNext() + "." + functionName + - "(" + DecompileNext() + (Package.Version > ArrayMethodEndParmsVersion && !skipEndParms - ? DecompileNext() - : ")"); + string context = DecompileNext(); + string param1 = DecompileNext(); + return $"{context}.{functionName}({param1})"; } - protected string DecompileMethodTwo(string functionName, bool skipEndParms = false) + protected string DecompileTwoParamMethod(string functionName) { Decompiler._CanAddSemicolon = true; - return DecompileNext() + "." + functionName + - "(" + DecompileNext() + ", " + DecompileNext() + - (Package.Version > ArrayMethodEndParmsVersion && !skipEndParms ? DecompileNext() : ")"); + string context = DecompileNext(); + string param1 = DecompileNext(); + string param2 = DecompileNext(); + return $"{context}.{functionName}({param1}, {param2})"; } } + [ExprToken(ExprToken.DynArrayFind)] public class DynamicArrayFindToken : DynamicArrayMethodToken { public override void Deserialize(IUnrealStream stream) { - DeserializeMethodOne(stream); + DeserializeOneParamMethodWithSkip(stream, (uint)PackageObjectLegacyVersion.SkipSizeAddedToArrayFindTokenIntrinsics); } - + public override string Decompile() { - return DecompileMethodOne("Find"); + return DecompileOneParamMethod("Find"); } } + [ExprToken(ExprToken.DynArrayFindStruct)] public class DynamicArrayFindStructToken : DynamicArrayMethodToken { public override void Deserialize(IUnrealStream stream) { - DeserializeMethodTwo(stream); + DeserializeOneParamMethodWithSkip(stream, (uint)PackageObjectLegacyVersion.SkipSizeAddedToArrayFindTokenIntrinsics); } public override string Decompile() { - return DecompileMethodTwo("Find"); + return DecompileTwoParamMethod("Find"); } } + [ExprToken(ExprToken.DynArraySort)] public class DynamicArraySortToken : DynamicArrayMethodToken { public override void Deserialize(IUnrealStream stream) { - DeserializeMethodOne(stream); + DeserializeOneParamMethodWithSkip(stream); } public override string Decompile() { - return DecompileMethodOne("Sort"); + return DecompileOneParamMethod("Sort"); } } + [ExprToken(ExprToken.DynArrayAdd)] public class DynamicArrayAddToken : DynamicArrayMethodToken { public override void Deserialize(IUnrealStream stream) @@ -167,116 +213,85 @@ public override void Deserialize(IUnrealStream stream) // EndParms DeserializeNext(); + + Decompiler.DeserializeDebugToken(); } public override string Decompile() { - return DecompileMethodOne("Add"); + return DecompileOneParamMethod("Add"); } } + [ExprToken(ExprToken.DynArrayAddItem)] public class DynamicArrayAddItemToken : DynamicArrayMethodToken { public override void Deserialize(IUnrealStream stream) { - DeserializeMethodOne(stream); + DeserializeOneParamMethodWithSkip(stream); } public override string Decompile() { - return DecompileMethodOne("AddItem"); + return DecompileOneParamMethod("AddItem"); } } + [ExprToken(ExprToken.DynArrayInsert)] public class DynamicArrayInsertToken : DynamicArrayMethodToken { - protected override void DeserializeMethodTwo(IUnrealStream stream, bool skipEndParms = false) - { - // Array - DeserializeNext(); - - // Param 1 - DeserializeNext(); - - // Param 2 - DeserializeNext(); - - if (stream.Version > ArrayMethodEndParmsVersion && !skipEndParms) - { - // EndParms - DeserializeNext(); - } - } - public override void Deserialize(IUnrealStream stream) { - DeserializeMethodTwo(stream); + DeserializeTwoParamMethodNoSkip(stream); } public override string Decompile() { - return DecompileMethodTwo("Insert"); + return DecompileTwoParamMethod("Insert"); } - - //(0x033) DynamicArrayInsertToken -> LocalVariableToken -> EndFunctionParmsToken } + [ExprToken(ExprToken.DynArrayInsertItem)] public class DynamicArrayInsertItemToken : DynamicArrayMethodToken { public override void Deserialize(IUnrealStream stream) { - DeserializeMethodTwo(stream); + DeserializeTwoParamMethodWithSkip(stream); } public override string Decompile() { - return DecompileMethodTwo("InsertItem"); + return DecompileTwoParamMethod("InsertItem"); } } + [ExprToken(ExprToken.DynArrayRemove)] public class DynamicArrayRemoveToken : DynamicArrayMethodToken { - protected override void DeserializeMethodTwo(IUnrealStream stream, bool skipEndParms = false) - { - // Array - DeserializeNext(); - - // Param 1 - DeserializeNext(); - - // Param 2 - DeserializeNext(); - - if (stream.Version > ArrayMethodEndParmsVersion && !skipEndParms) - { - // EndParms - DeserializeNext(); - } - } - public override void Deserialize(IUnrealStream stream) { - DeserializeMethodTwo(stream); + DeserializeTwoParamMethodNoSkip(stream); } public override string Decompile() { - return DecompileMethodTwo("Remove"); + return DecompileTwoParamMethod("Remove"); } } + [ExprToken(ExprToken.DynArrayRemoveItem)] public class DynamicArrayRemoveItemToken : DynamicArrayMethodToken { public override void Deserialize(IUnrealStream stream) { - DeserializeMethodOne(stream); + DeserializeOneParamMethodWithSkip(stream); } public override string Decompile() { - return DecompileMethodOne("RemoveItem"); + return DecompileOneParamMethod("RemoveItem"); } } } } -} \ No newline at end of file +} diff --git a/src/Core/Tokens/BlueprintTokens.cs b/src/Core/Tokens/BlueprintTokens.cs new file mode 100644 index 00000000..e217d8e3 --- /dev/null +++ b/src/Core/Tokens/BlueprintTokens.cs @@ -0,0 +1,63 @@ +namespace UELib.Core +{ + public partial class UStruct + { + public partial class UByteCodeDecompiler + { +#if UE4 + public class LetMulticastDelegateToken : LetDelegateToken + { + + } + + public class LetObjToken : LetToken + { + + } + + public class LetWeakObjPtrToken : LetToken + { + + } + + public class PushExecutionFlowToken : Token + { + private int _Size; + + public override void Deserialize( IUnrealStream stream ) + { + _Size = stream.ReadInt32(); + Decompiler.AlignSize( sizeof(int) ); + } + + public override string Decompile() + { + Decompiler._CanAddSemicolon = true; + Decompiler._MustCommentStatement = true; + return "PUSH " + _Size; + } + } + + public class PopExecutionFlowToken : Token + { + public override string Decompile() + { + Decompiler._CanAddSemicolon = true; + Decompiler._MustCommentStatement = true; + return "POP"; + } + } + + public class TracepointToken : Token + { + + } + + public class WireTracepointToken : Token + { + + } +#endif + } + } +} \ No newline at end of file diff --git a/src/Core/Tokens/CastTokens.cs b/src/Core/Tokens/CastTokens.cs index 1b555939..871d8fab 100644 --- a/src/Core/Tokens/CastTokens.cs +++ b/src/Core/Tokens/CastTokens.cs @@ -1,4 +1,9 @@ -using System.Diagnostics; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using UELib.ObjectModel.Annotations; +using UELib.Tokens; namespace UELib.Core { @@ -6,381 +11,145 @@ public partial class UStruct { public partial class UByteCodeDecompiler { - public class CastToken : Token + [ExprToken(ExprToken.PrimitiveCast)] + public class PrimitiveCastToken : Token { + public CastToken CastOpCode = CastToken.None; + + [SuppressMessage("ReSharper", "StringLiteralTypo")] + private static readonly Dictionary CastTypeNameMap = + new Dictionary + { + //{ CastToken.InterfaceToObject, "" }, + { CastToken.InterfaceToString, "string" }, + { CastToken.InterfaceToBool, "bool" }, + { CastToken.RotatorToVector, "Vector" }, + { CastToken.ByteToInt, "int" }, + { CastToken.ByteToBool, "bool" }, + { CastToken.ByteToFloat, "float" }, + { CastToken.IntToByte, "byte" }, + { CastToken.IntToBool, "bool" }, + { CastToken.IntToFloat, "float" }, + { CastToken.BoolToByte, "byte" }, + { CastToken.BoolToInt, "int" }, + { CastToken.BoolToFloat, "float" }, + { CastToken.FloatToByte, "byte" }, + { CastToken.FloatToInt, "int" }, + { CastToken.FloatToBool, "bool" }, + //{ CastToken.ObjectToInterface, "" }, + { CastToken.ObjectToBool, "bool" }, + { CastToken.NameToBool, "bool" }, + { CastToken.StringToByte, "byte" }, + { CastToken.StringToInt, "int" }, + { CastToken.StringToBool, "bool" }, + { CastToken.StringToFloat, "float" }, + { CastToken.StringToVector, "Vector" }, + { CastToken.StringToRotator, "Rotator" }, + { CastToken.VectorToBool, "bool" }, + { CastToken.VectorToRotator, "Rotator" }, + { CastToken.RotatorToBool, "bool" }, + { CastToken.ByteToString, "string" }, + { CastToken.IntToString, "string" }, + { CastToken.BoolToString, "string" }, + { CastToken.FloatToString, "string" }, + { CastToken.ObjectToString, "string" }, + { CastToken.NameToString, "string" }, + { CastToken.VectorToString, "string" }, + { CastToken.RotatorToString, "string" }, + { CastToken.DelegateToString, "string" }, + { CastToken.StringToName, "name" } + }; + + public void GetFriendlyCastName(out string castTypeName) + { + CastTypeNameMap.TryGetValue(CastOpCode, out castTypeName); + } + + protected virtual void DeserializeCastToken(IUnrealStream stream) + { + CastOpCode = (CastToken)stream.ReadByte(); + Decompiler.AlignSize(sizeof(byte)); + } + + private void RemapCastToken(IUnrealArchive stream) + { + if (stream.Version >= VInterfaceClass) return; + + // TODO: Could there be more? + switch (CastOpCode) + { + case CastToken.ObjectToInterface: + CastOpCode = CastToken.StringToName; + break; + } + } + public override void Deserialize(IUnrealStream stream) { + DeserializeCastToken(stream); + RemapCastToken(stream); DeserializeNext(); } public override string Decompile() { - return $"({DecompileNext()})"; + // Suppress implicit casts + switch (CastOpCode) + { + //case CastToken.IntToFloat: + //case CastToken.ByteToInt: + case CastToken.InterfaceToObject: + case CastToken.ObjectToInterface: + return DecompileNext(); + } + + GetFriendlyCastName(out string castTypeName); + Debug.Assert(castTypeName != default, "Detected an unresolved token."); + return $"{castTypeName}({DecompileNext()})"; } } - public class PrimitiveCastToken : Token + [ExprToken(ExprToken.PrimitiveCast)] + public sealed class PrimitiveInlineCastToken : PrimitiveCastToken { - public override void Deserialize(IUnrealStream stream) - { - DeserializeNext(); - } - - public override string Decompile() + protected override void DeserializeCastToken(IUnrealStream stream) { - return DecompileNext(); + CastOpCode = (CastToken)OpCode; } } - public abstract class ObjectCastToken : CastToken + [ExprToken(ExprToken.DynamicCast)] + public class DynamicCastToken : Token { - public UObject CastedObject; + public UClass CastClass; public override void Deserialize(IUnrealStream stream) { - CastedObject = stream.ReadObject(); + CastClass = stream.ReadObject(); Decompiler.AlignObjectSize(); - base.Deserialize(stream); + DeserializeNext(); } - } - public class DynamicCastToken : ObjectCastToken - { public override string Decompile() { - return CastedObject.Name + base.Decompile(); + return $"{CastClass.Name}({DecompileNext()})"; } } - public class MetaCastToken : ObjectCastToken + [ExprToken(ExprToken.MetaCast)] + public class MetaClassCastToken : DynamicCastToken { public override string Decompile() { - return $"class<{CastedObject.Name}>{base.Decompile()}"; + return $"class<{CastClass.Name}>({DecompileNext()})"; } } + [ExprToken(ExprToken.InterfaceCast)] public class InterfaceCastToken : DynamicCastToken { } - - public class RotatorToVectorToken : CastToken - { - public override string Decompile() - { - return $"vector{base.Decompile()}"; - } - } - - public class ByteToIntToken : CastToken - { - public override string Decompile() - { - return DecompileNext(); - //return "int" + base.Decompile(); - } - } - - public class ByteToFloatToken : CastToken - { - public override string Decompile() - { - return $"float{base.Decompile()}"; - } - } - - public class ByteToBoolToken : CastToken - { - public override string Decompile() - { - return $"bool{base.Decompile()}"; - } - } - - public class IntToByteToken : CastToken - { - public override string Decompile() - { - return $"byte{base.Decompile()}"; - } - } - - public class IntToBoolToken : CastToken - { -#if !SUPPRESS_BOOLINTEXPLOIT - public override string Decompile() - { - return $"bool{base.Decompile()}"; - } -#endif - } - - public class IntToFloatToken : CastToken - { - public override string Decompile() - { - return $"float{base.Decompile()}"; - } - } - - public class BoolToByteToken : CastToken - { - public override string Decompile() - { - return $"byte{base.Decompile()}"; - } - } - - public class BoolToIntToken : CastToken - { -#if !SUPPRESS_BOOLINTEXPLOIT - public override string Decompile() - { - return $"int{base.Decompile()}"; - } -#endif - } - - public class BoolToFloatToken : CastToken - { - public override string Decompile() - { - return $"float{base.Decompile()}"; - } - } - - public class FloatToByteToken : CastToken - { - public override string Decompile() - { - return $"byte{base.Decompile()}"; - } - } - - public class FloatToIntToken : CastToken - { - public override string Decompile() - { - return $"int{base.Decompile()}"; - } - } - - public class FloatToBoolToken : CastToken - { - public override string Decompile() - { - return $"bool{base.Decompile()}"; - } - } - - public class ObjectToBoolToken : CastToken - { - public override string Decompile() - { - return $"bool{base.Decompile()}"; - } - } - - public class NameToBoolToken : CastToken - { - public override string Decompile() - { - return $"bool{base.Decompile()}"; - } - } - - public class StringToByteToken : CastToken - { - public override string Decompile() - { - return $"byte{base.Decompile()}"; - } - } - - public class StringToIntToken : CastToken - { - public override string Decompile() - { - return $"int{base.Decompile()}"; - } - } - - public class StringToBoolToken : CastToken - { - public override string Decompile() - { - return $"bool{base.Decompile()}"; - } - } - - public class StringToFloatToken : CastToken - { - public override string Decompile() - { - return $"float{base.Decompile()}"; - } - } - - public class StringToVectorToken : CastToken - { - public override string Decompile() - { - return $"vector{base.Decompile()}"; - } - } - - public class StringToRotatorToken : CastToken - { - public override string Decompile() - { - return $"rotator{base.Decompile()}"; - } - } - - public class VectorToBoolToken : CastToken - { - public override string Decompile() - { - return $"bool{base.Decompile()}"; - } - } - - public class VectorToRotatorToken : CastToken - { - public override string Decompile() - { - return $"rotator{base.Decompile()}"; - } - } - - public class RotatorToBoolToken : CastToken - { - public override string Decompile() - { - return $"bool{base.Decompile()}"; - } - } - - public class ByteToStringToken : CastToken - { - public override string Decompile() - { - return $"string{base.Decompile()}"; - } - } - - public class IntToStringToken : CastToken - { - public override string Decompile() - { - return $"string{base.Decompile()}"; - } - } - - public class BoolToStringToken : CastToken - { - public override string Decompile() - { - return $"string{base.Decompile()}"; - } - } - - public class FloatToStringToken : CastToken - { - public override string Decompile() - { - return $"string{base.Decompile()}"; - } - } - - public class NameToStringToken : CastToken - { - public override string Decompile() - { - return $"string{base.Decompile()}"; - } - } - - public class VectorToStringToken : CastToken - { - public override string Decompile() - { - return $"string{base.Decompile()}"; - } - } - - public class RotatorToStringToken : CastToken - { - public override string Decompile() - { - return $"string{base.Decompile()}"; - } - } - - public class StringToNameToken : CastToken - { - public override string Decompile() - { - return $"name{base.Decompile()}"; - } - } - - public class ObjectToStringToken : CastToken - { - public override string Decompile() - { - return $"string{base.Decompile()}"; - } - } - - public class InterfaceToStringToken : CastToken - { - public override string Decompile() - { - return $"string{base.Decompile()}"; - } - } - - public class InterfaceToBoolToken : CastToken - { - public override string Decompile() - { - return $"bool{base.Decompile()}"; - } - } - - public class InterfaceToObjectToken : CastToken - { - } - - public class ObjectToInterfaceToken : CastToken - { - } - - public class DelegateToStringToken : CastToken - { - public override string Decompile() - { - return $"string{base.Decompile()}"; - } - } - - public sealed class UnresolvedCastToken : CastToken - { - public override void Deserialize(IUnrealStream stream) - { -#if DEBUG_HIDDENTOKENS - Debug.WriteLine("Detected an unresolved token."); -#endif - } - - public override string Decompile() - { - Decompiler.PreComment = $"// {FormatTokenInfo(this)}"; - return base.Decompile(); - } - } } } -} \ No newline at end of file +} diff --git a/src/Core/Tokens/ConstTokens.cs b/src/Core/Tokens/ConstTokens.cs index 731362e2..c86d0048 100644 --- a/src/Core/Tokens/ConstTokens.cs +++ b/src/Core/Tokens/ConstTokens.cs @@ -1,5 +1,7 @@ using System; using System.Globalization; +using UELib.ObjectModel.Annotations; +using UELib.Tokens; using UELib.UnrealScript; namespace UELib.Core @@ -8,6 +10,7 @@ public partial class UStruct { public partial class UByteCodeDecompiler { + [ExprToken(ExprToken.IntZero)] public class IntZeroToken : Token { public override string Decompile() @@ -16,6 +19,7 @@ public override string Decompile() } } + [ExprToken(ExprToken.IntOne)] public class IntOneToken : Token { public override string Decompile() @@ -24,6 +28,7 @@ public override string Decompile() } } + [ExprToken(ExprToken.True)] public class TrueToken : Token { public override string Decompile() @@ -32,6 +37,7 @@ public override string Decompile() } } + [ExprToken(ExprToken.False)] public class FalseToken : Token { public override string Decompile() @@ -40,6 +46,7 @@ public override string Decompile() } } + [ExprToken(ExprToken.NoObject)] public class NoneToken : Token { public override string Decompile() @@ -48,6 +55,7 @@ public override string Decompile() } } + [ExprToken(ExprToken.Self)] public class SelfToken : Token { public override string Decompile() @@ -56,6 +64,7 @@ public override string Decompile() } } + [ExprToken(ExprToken.IntConst)] public class IntConstToken : Token { public int Value; @@ -72,6 +81,7 @@ public override string Decompile() } } + [ExprToken(ExprToken.ByteConst)] public class ByteConstToken : Token { public byte Value; @@ -128,6 +138,7 @@ public override string Decompile() } } + [ExprToken(ExprToken.IntConstByte)] public class IntConstByteToken : Token { public byte Value; @@ -144,13 +155,14 @@ public override string Decompile() } } + [ExprToken(ExprToken.FloatConst)] public class FloatConstToken : Token { public float Value; public override void Deserialize(IUnrealStream stream) { - Value = stream.UR.ReadSingle(); + Value = stream.ReadFloat(); Decompiler.AlignSize(sizeof(float)); } @@ -160,6 +172,14 @@ public override string Decompile() } } + // Not supported, but has existed here and there + [ExprToken(ExprToken.StructConst)] + public class StructConstToken : Token + { + + } + + [ExprToken(ExprToken.ObjectConst)] public class ObjectConstToken : Token { public UObject ObjectRef; @@ -170,18 +190,13 @@ public override void Deserialize(IUnrealStream stream) Decompiler.AlignObjectSize(); } - // Produces: ClassName'ObjectName' public override string Decompile() { - if (ObjectRef == null) return "none"; - - string className = ObjectRef.GetClassName(); - if (string.IsNullOrEmpty(className)) className = "class"; - - return $"{className}'{ObjectRef.Name}'"; + return PropertyDisplay.FormatLiteral(ObjectRef); } } + [ExprToken(ExprToken.NameConst)] public class NameConstToken : Token { public UName Name; @@ -198,13 +213,14 @@ public override string Decompile() } } + [ExprToken(ExprToken.StringConst)] public class StringConstToken : Token { public string Value; public override void Deserialize(IUnrealStream stream) { - Value = stream.UR.ReadAnsi(); + Value = stream.ReadAnsiNullString(); Decompiler.AlignSize(Value.Length + 1); // inc null char } @@ -214,13 +230,14 @@ public override string Decompile() } } - public class UniStringConstToken : Token + [ExprToken(ExprToken.UnicodeStringConst)] + public class UnicodeStringConstToken : Token { public string Value; public override void Deserialize(IUnrealStream stream) { - Value = stream.UR.ReadUnicode(); + Value = stream.ReadUnicodeNullString(); Decompiler.AlignSize(Value.Length * 2 + 2); // inc null char } @@ -230,71 +247,62 @@ public override string Decompile() } } + [ExprToken(ExprToken.RotationConst)] public class RotationConstToken : Token { - public struct Rotator - { - public int Pitch, Yaw, Roll; - } - - public Rotator Value; + public URotator Rotation; public override void Deserialize(IUnrealStream stream) { - Value.Pitch = stream.ReadInt32(); - Decompiler.AlignSize(sizeof(int)); - Value.Yaw = stream.ReadInt32(); - Decompiler.AlignSize(sizeof(int)); - Value.Roll = stream.ReadInt32(); - Decompiler.AlignSize(sizeof(int)); + stream.ReadStruct(out Rotation); + Decompiler.AlignSize(12); } public override string Decompile() { - return - $"rot({PropertyDisplay.FormatLiteral(Value.Pitch)}, {PropertyDisplay.FormatLiteral(Value.Yaw)}, {PropertyDisplay.FormatLiteral(Value.Roll)})"; + return $"rot({PropertyDisplay.FormatLiteral(Rotation.Pitch)}, " + + $"{PropertyDisplay.FormatLiteral(Rotation.Yaw)}, " + + $"{PropertyDisplay.FormatLiteral(Rotation.Roll)})"; } } + [ExprToken(ExprToken.VectorConst)] public class VectorConstToken : Token { - public float X, Y, Z; + public UVector Vector; public override void Deserialize(IUnrealStream stream) { - X = stream.UR.ReadSingle(); - Decompiler.AlignSize(sizeof(float)); - Y = stream.UR.ReadSingle(); - Decompiler.AlignSize(sizeof(float)); - Z = stream.UR.ReadSingle(); - Decompiler.AlignSize(sizeof(float)); + stream.ReadStruct(out Vector); + Decompiler.AlignSize(12); } public override string Decompile() { - return - $"vect({PropertyDisplay.FormatLiteral(X)}, {PropertyDisplay.FormatLiteral(Y)}, {PropertyDisplay.FormatLiteral(Z)})"; + return $"vect({PropertyDisplay.FormatLiteral(Vector.X)}, " + + $"{PropertyDisplay.FormatLiteral(Vector.Y)}, " + + $"{PropertyDisplay.FormatLiteral(Vector.Z)})"; } } + [ExprToken(ExprToken.RangeConst)] public class RangeConstToken : Token { - public float A, B; + public URange Range; public override void Deserialize(IUnrealStream stream) { - A = stream.UR.ReadSingle(); - Decompiler.AlignSize(sizeof(float)); - B = stream.UR.ReadSingle(); - Decompiler.AlignSize(sizeof(float)); + stream.ReadStruct(out Range); + Decompiler.AlignSize(8); } public override string Decompile() { return - $"rng({PropertyDisplay.FormatLiteral(A)}, {PropertyDisplay.FormatLiteral(B)})"; + $"rng({PropertyDisplay.FormatLiteral(Range.A)}, " + + $"{PropertyDisplay.FormatLiteral(Range.B)})"; } } } } -} \ No newline at end of file +} diff --git a/src/Core/Tokens/ContextTokens.cs b/src/Core/Tokens/ContextTokens.cs index b0b03822..9f7b18d0 100644 --- a/src/Core/Tokens/ContextTokens.cs +++ b/src/Core/Tokens/ContextTokens.cs @@ -1,5 +1,7 @@ using System.Diagnostics; using UELib.Annotations; +using UELib.ObjectModel.Annotations; +using UELib.Tokens; namespace UELib.Core { @@ -7,46 +9,49 @@ public partial class UStruct { public partial class UByteCodeDecompiler { + [ExprToken(ExprToken.Context)] public class ContextToken : Token { - // Definitely not in UT3(512), APB, CrimeCraft, GoW2, MoonBase and Singularity. - // Greater or Equal than - private const ushort VSizeByteMoved = 588; - + public UProperty Property; + public ushort PropertyType; + public override void Deserialize(IUnrealStream stream) { - bool propertyAdded = stream.Version >= VSizeByteMoved -#if TERA - && stream.Package.Build != UnrealPackage.GameBuild.BuildName.Tera -#endif -#if TRANSFORMERS - && stream.Package.Build != UnrealPackage.GameBuild.BuildName.Transformers -#endif - ; - // A.? DeserializeNext(); // 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. + bool propertyAdded = stream.Version >= 588 +#if TERA + && stream.Package.Build != UnrealPackage.GameBuild.BuildName.Tera +#endif + ; if (propertyAdded) { // Property - stream.ReadObjectIndex(); + stream.Read(out Property); Decompiler.AlignObjectSize(); } - // PropertyType - stream.ReadByte(); - Decompiler.AlignSize(sizeof(byte)); - - // Additional byte in APB? - if (stream.Version > 512 && !propertyAdded) + // FIXME: Thinking of it... this appears to be identical to the changes found in SwitchToken, but the existing versions are different?. + if ((stream.Version >= 512 && !propertyAdded) +#if DNF + || stream.Package.Build == UnrealPackage.GameBuild.BuildName.DNF +#endif + ) { - stream.ReadByte(); + PropertyType = stream.ReadUInt16(); + Decompiler.AlignSize(sizeof(ushort)); + } + else + { + PropertyType = stream.ReadByte(); Decompiler.AlignSize(sizeof(byte)); } @@ -60,6 +65,7 @@ public override string Decompile() } } + [ExprToken(ExprToken.ClassContext)] public class ClassContextToken : ContextToken { public override string Decompile() @@ -71,6 +77,7 @@ public override string Decompile() } } + [ExprToken(ExprToken.InterfaceContext)] public class InterfaceContextToken : Token { public override void Deserialize(IUnrealStream stream) @@ -84,6 +91,7 @@ public override string Decompile() } } + [ExprToken(ExprToken.StructMember)] public class StructMemberToken : Token { public UField Property; @@ -145,4 +153,4 @@ public override string Decompile() } } } -} \ No newline at end of file +} diff --git a/src/Core/Tokens/EatStringToken.cs b/src/Core/Tokens/EatStringToken.cs new file mode 100644 index 00000000..da77964c --- /dev/null +++ b/src/Core/Tokens/EatStringToken.cs @@ -0,0 +1,22 @@ +using UELib.ObjectModel.Annotations; +using UELib.Tokens; + +namespace UELib.Core +{ + /// + /// Proceeded by a string expression. + /// + [ExprToken(ExprToken.EatString)] + public class EatStringToken : UStruct.UByteCodeDecompiler.Token + { + public override void Deserialize(IUnrealStream stream) + { + DeserializeNext(); + } + + public override string Decompile() + { + return DecompileNext(); + } + } +} diff --git a/src/Core/Tokens/FieldTokens.cs b/src/Core/Tokens/FieldTokens.cs index 8855a3a6..8995c716 100644 --- a/src/Core/Tokens/FieldTokens.cs +++ b/src/Core/Tokens/FieldTokens.cs @@ -1,4 +1,7 @@ -namespace UELib.Core +using UELib.ObjectModel.Annotations; +using UELib.Tokens; + +namespace UELib.Core { public partial class UStruct { @@ -22,6 +25,7 @@ public override string Decompile() } } + [ExprToken(ExprToken.NativeParm)] public class NativeParameterToken : FieldToken { public override string Decompile() @@ -36,22 +40,27 @@ public override string Decompile() } } + [ExprToken(ExprToken.InstanceVariable)] public class InstanceVariableToken : FieldToken { } + [ExprToken(ExprToken.LocalVariable)] public class LocalVariableToken : FieldToken { } + [ExprToken(ExprToken.StateVariable)] public class StateVariableToken : FieldToken { } + [ExprToken(ExprToken.OutVariable)] public class OutVariableToken : FieldToken { } + [ExprToken(ExprToken.DefaultVariable)] public class DefaultVariableToken : FieldToken { public override string Decompile() @@ -60,22 +69,6 @@ public override string Decompile() } } - public class DynamicVariableToken : Token - { - protected int LocalIndex; - - public override void Deserialize(IUnrealStream stream) - { - LocalIndex = stream.ReadInt32(); - Decompiler.AlignSize(sizeof(int)); - } - - public override string Decompile() - { - return $"UnknownLocal_{LocalIndex}"; - } - } - public class UndefinedVariableToken : Token { public override string Decompile() @@ -84,17 +77,13 @@ public override string Decompile() } } + [ExprToken(ExprToken.DelegateProperty)] public class DelegatePropertyToken : FieldToken { public UName PropertyName; public override void Deserialize(IUnrealStream stream) { - if (stream.Package.Build == UnrealPackage.GameBuild.BuildName.MOHA) - { - Decompiler.AlignSize(sizeof(int)); - } - PropertyName = ReadName(stream); // TODO: Corrigate version. Seen in version ~648(The Ball) may have been introduced earlier, but not prior 610. @@ -110,35 +99,16 @@ public override string Decompile() } } + [ExprToken(ExprToken.DefaultParmValue)] public class DefaultParameterToken : Token { - internal static int _NextParamIndex; - - private UField _NextParam - { - get - { - try - { - return ((UFunction)Decompiler._Container).Params[_NextParamIndex++]; - } - catch - { - return null; - } - } - } - + public ushort Size; + public override void Deserialize(IUnrealStream stream) { - stream.ReadUInt16(); // Size + Size = stream.ReadUInt16(); Decompiler.AlignSize(sizeof(ushort)); - if (stream.Package.Build == UnrealPackage.GameBuild.BuildName.MOHA) - { - Decompiler.AlignSize(sizeof(ushort)); - } - DeserializeNext(); // Expression DeserializeNext(); // EndParmValue } @@ -147,13 +117,11 @@ public override string Decompile() { string expression = DecompileNext(); DecompileNext(); // EndParmValue - Decompiler._CanAddSemicolon = true; - var param = _NextParam; - string paramName = param != null ? param.Name : $"@UnknownOptionalParam_{_NextParamIndex - 1}"; - return $"{paramName} = {expression}"; + return expression; } } + [ExprToken(ExprToken.BoolVariable)] public class BoolVariableToken : Token { public override void Deserialize(IUnrealStream stream) @@ -167,6 +135,7 @@ public override string Decompile() } } + [ExprToken(ExprToken.InstanceDelegate)] public class InstanceDelegateToken : Token { public UName DelegateName; @@ -178,4 +147,4 @@ public override void Deserialize(IUnrealStream stream) } } } -} \ No newline at end of file +} diff --git a/src/Core/Tokens/FunctionTokens.cs b/src/Core/Tokens/FunctionTokens.cs index a5e904c9..837a9491 100644 --- a/src/Core/Tokens/FunctionTokens.cs +++ b/src/Core/Tokens/FunctionTokens.cs @@ -2,6 +2,9 @@ using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Text; +using UELib.Branch; +using UELib.ObjectModel.Annotations; +using UELib.Tokens; namespace UELib.Core { @@ -9,6 +12,7 @@ public partial class UStruct { public partial class UByteCodeDecompiler { + [ExprToken(ExprToken.EndFunctionParms)] public class EndFunctionParmsToken : Token { } @@ -21,7 +25,7 @@ protected UName DeserializeFunctionName(IUnrealStream stream) return ReadName(stream); } - protected void DeserializeCall() + protected virtual void DeserializeCall(IUnrealStream stream) { DeserializeParms(); Decompiler.DeserializeDebugToken(); @@ -52,8 +56,8 @@ private static string PrecedenceToken(Token t) break; } - return addParenthesis - ? $"({t.Decompile()})" + return addParenthesis + ? $"({t.Decompile()})" : t.Decompile(); } @@ -131,7 +135,7 @@ private string DecompileParms() switch (t) { // Skipped optional parameters - case NoParmToken _: + case EmptyParmToken _: output.Append(v); break; @@ -158,73 +162,70 @@ private string DecompileParms() } } + [ExprToken(ExprToken.FinalFunction)] public class FinalFunctionToken : FunctionToken { public UFunction Function; public override void Deserialize(IUnrealStream stream) { - if (stream.Package.Build == UnrealPackage.GameBuild.BuildName.MOHA) - { - Decompiler.AlignSize(sizeof(int)); - } - Function = stream.ReadObject(); Decompiler.AlignObjectSize(); - DeserializeCall(); + DeserializeCall(stream); } public override string Decompile() { var output = string.Empty; - if (Function != null) + // Support for non native operators. + if (Function.IsPost()) { - // Support for non native operators. - if (Function.IsPost()) - { - output = DecompilePreOperator(Function.FriendlyName); - } - else if (Function.IsPre()) - { - output = DecompilePostOperator(Function.FriendlyName); - } - else if (Function.IsOperator()) - { - output = DecompileOperator(Function.FriendlyName); - } - else + output = DecompilePreOperator(Function.FriendlyName); + } + else if (Function.IsPre()) + { + output = DecompilePostOperator(Function.FriendlyName); + } + else if (Function.IsOperator()) + { + output = DecompileOperator(Function.FriendlyName); + } + else + { + // Calling Super??. + if (Function.Name == Decompiler._Container.Name && !Decompiler._IsWithinClassContext) { - // Calling Super??. - if (Function.Name == Decompiler._Container.Name && !Decompiler._IsWithinClassContext) + output = "super"; + + // Check if the super call is within the super class of this functions outer(class) + var container = Decompiler._Container; + var context = (UField)container.Outer; + // ReSharper disable once PossibleNullReferenceException + var contextFuncOuterName = context.Name; + // ReSharper disable once PossibleNullReferenceException + var callFuncOuterName = Function.Outer.Name; + if (context.Super == null || callFuncOuterName != context.Super.Name) { - output = "super"; - - // Check if the super call is within the super class of this functions outer(class) - var container = Decompiler._Container; - var context = (UField)container.Outer; - if (context?.Super == null || Function.GetOuterName() != context.Super.Name) + // If there's no super to call, then we have a recursive call. + if (container.Super == null) { - // There's no super to call then do a recursive super call. - if (container.Super == null) - { - output += $"({container.GetOuterName()})"; - } - else + output += $"({contextFuncOuterName})"; + } + else + { + // Different owners, then it is a deep super call. + if (callFuncOuterName != contextFuncOuterName) { - // Different owners, then it is a deep super call. - if (Function.GetOuterName() != container.GetOuterName()) - { - output += $"({Function.GetOuterName()})"; - } + output += $"({callFuncOuterName})"; } } - - output += "."; } - output += DecompileCall(Function.Name); + output += "."; } + + output += DecompileCall(Function.Name); } Decompiler._CanAddSemicolon = true; @@ -232,26 +233,23 @@ public override string Decompile() } } + [ExprToken(ExprToken.VirtualFunction)] public class VirtualFunctionToken : FunctionToken { public UName FunctionName; public override void Deserialize(IUnrealStream stream) { - // TODO: Corrigate Version (Definitely not in MOHA, but in roboblitz(369)) - if (stream.Version >= 178 && stream.Version < 421 /*MOHA*/) +#if UE3Proto + // FIXME: Version + if (stream.Version >= 178 && stream.Version < 200) { - byte isSuperCall = stream.ReadByte(); + byte isSuper = stream.ReadByte(); Decompiler.AlignSize(sizeof(byte)); } - - if (stream.Package.Build == UnrealPackage.GameBuild.BuildName.MOHA) - { - Decompiler.AlignSize(sizeof(int)); - } - +#endif FunctionName = DeserializeFunctionName(stream); - DeserializeCall(); + DeserializeCall(stream); } public override string Decompile() @@ -261,6 +259,7 @@ public override string Decompile() } } + [ExprToken(ExprToken.GlobalFunction)] public class GlobalFunctionToken : FunctionToken { public UName FunctionName; @@ -268,7 +267,7 @@ public class GlobalFunctionToken : FunctionToken public override void Deserialize(IUnrealStream stream) { FunctionName = DeserializeFunctionName(stream); - DeserializeCall(); + DeserializeCall(stream); } public override string Decompile() @@ -278,6 +277,7 @@ public override string Decompile() } } + [ExprToken(ExprToken.DelegateFunction)] public class DelegateFunctionToken : FunctionToken { public byte? IsLocal; @@ -286,8 +286,8 @@ public class DelegateFunctionToken : FunctionToken public override void Deserialize(IUnrealStream stream) { - // TODO: Corrigate Version - if (stream.Version >= 181) + // FIXME: Version + if (stream.Version >= (uint)PackageObjectLegacyVersion.IsLocalAddedToDelegateFunctionToken) { IsLocal = stream.ReadByte(); Decompiler.AlignSize(sizeof(byte)); @@ -297,7 +297,7 @@ public override void Deserialize(IUnrealStream stream) Decompiler.AlignObjectSize(); FunctionName = DeserializeFunctionName(stream); - DeserializeCall(); + DeserializeCall(stream); } public override string Decompile() @@ -307,13 +307,14 @@ public override string Decompile() } } + [ExprToken(ExprToken.NativeFunction)] public class NativeFunctionToken : FunctionToken { public NativeTableItem NativeItem; public override void Deserialize(IUnrealStream stream) { - DeserializeCall(); + DeserializeCall(stream); } public override string Decompile() @@ -348,4 +349,4 @@ public override string Decompile() } } } -} \ No newline at end of file +} diff --git a/src/Core/Tokens/JumpTokens.cs b/src/Core/Tokens/JumpTokens.cs index 27de6962..4691db31 100644 --- a/src/Core/Tokens/JumpTokens.cs +++ b/src/Core/Tokens/JumpTokens.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; -using UELib.Annotations; +using UELib.Branch; +using UELib.ObjectModel.Annotations; +using UELib.Tokens; namespace UELib.Core { @@ -8,51 +10,61 @@ public partial class UStruct { public partial class UByteCodeDecompiler { + [ExprToken(ExprToken.Return)] public class ReturnToken : Token { public override void Deserialize(IUnrealStream stream) { + if (Decompiler._Buffer.Version < (uint)PackageObjectLegacyVersion.ReturnExpressionAddedToReturnToken) + { + return; + } + // Expression DeserializeNext(); } public override string Decompile() { - #region CaseToken Support - // HACK: for case's that end with a return instead of a break. if (Decompiler.IsInNest(NestManager.Nest.NestType.Default) != null) { Decompiler._Nester.TryAddNestEnd(NestManager.Nest.NestType.Switch, Position + Size); } - #endregion + Decompiler.MarkSemicolon(); + if (Decompiler._Buffer.Version < (uint)PackageObjectLegacyVersion.ReturnExpressionAddedToReturnToken) + { + // FIXME: Transport the emitted "ReturnValue = Expression" over here. + return "return"; + } string returnValue = DecompileNext(); - Decompiler._CanAddSemicolon = true; - return "return" + (returnValue.Length != 0 ? " " + returnValue : string.Empty); + return "return" + (returnValue.Length != 0 + ? " " + returnValue + : string.Empty); } } + [ExprToken(ExprToken.ReturnNothing)] public class ReturnNothingToken : EatReturnValueToken { public override string Decompile() { - #region CaseToken Support - // HACK: for case's that end with a return instead of a break. if (Decompiler.IsInNest(NestManager.Nest.NestType.Default) != null) { Decompiler._Nester.TryAddNestEnd(NestManager.Nest.NestType.Switch, Position + Size); } - #endregion - - return ReturnValueProperty != null ? ReturnValueProperty.Name : string.Empty; + return ReturnValueProperty != null + ? ReturnValueProperty.Name + : string.Empty; } } - public class GoToLabelToken : Token + [ExprToken(ExprToken.GotoLabel)] + public class GotoLabelToken : Token { public override void Deserialize(IUnrealStream stream) { @@ -62,11 +74,12 @@ public override void Deserialize(IUnrealStream stream) public override string Decompile() { - Decompiler._CanAddSemicolon = true; + Decompiler.MarkSemicolon(); return $"goto {DecompileNext()}"; } } + [ExprToken(ExprToken.Jump)] public class JumpToken : Token { public bool MarkedAsSwitchBreak; @@ -75,6 +88,14 @@ public class JumpToken : Token public override void Deserialize(IUnrealStream stream) { +#if UE4 + if (stream.UE4Version > 0) + { + CodeOffset = (ushort)stream.ReadInt32(); + Decompiler.AlignSize(sizeof(int)); + return; + } +#endif CodeOffset = stream.ReadUInt16(); Decompiler.AlignSize(sizeof(ushort)); } @@ -92,17 +113,12 @@ public override void PostDeserialized() ); } - protected void Commentize() + protected void SetEndComment() { Decompiler.PreComment = $"// End:0x{CodeOffset:X2}"; } - protected void Commentize(string statement) - { - Decompiler.PreComment = $"// End:0x{CodeOffset:X2} [{statement}]"; - } - - protected void CommentStatement(string statement) + private void SetStatementComment(string statement) { Decompiler.PreComment = $"// [{statement}]"; } @@ -154,10 +170,10 @@ public override string Decompile() || Decompiler.IsInNest(NestManager.Nest.NestType.Default) != null) { NoJumpLabel(); - Commentize(); + SetEndComment(); // 'break' CodeOffset sits at the end of the switch, // check that it doesn't exist already and add it - uint switchEnd = Decompiler.IsInNest(NestManager.Nest.NestType.Default) != null + int switchEnd = Decompiler.IsInNest(NestManager.Nest.NestType.Default) != null ? Position + Size : CodeOffset; Decompiler._Nester.TryAddNestEnd(NestManager.Nest.NestType.Switch, switchEnd); @@ -178,7 +194,7 @@ public override string Decompile() } NoJumpLabel(); - Commentize(); + SetEndComment(); Decompiler._CanAddSemicolon = true; return "break"; } @@ -186,7 +202,7 @@ public override string Decompile() if (Decompiler.TokenAt(CodeOffset) is IteratorNextToken) { NoJumpLabel(); - Commentize(); + SetEndComment(); Decompiler._CanAddSemicolon = true; return "continue"; } @@ -196,13 +212,13 @@ public override string Decompile() { if (CodeOffset + 10 == destJump.CodeOffset) { - CommentStatement("Explicit Continue"); + SetStatementComment("Explicit Continue"); goto gotoJump; } if (CodeOffset == destJump.CodeOffset) { - CommentStatement("Explicit Break"); + SetStatementComment("Explicit Break"); goto gotoJump; } } @@ -218,7 +234,7 @@ public override string Decompile() && LinkedIfNest.Creator != outerNestEnd.Creator) { // this is more likely a continue within a for(;;) loop - CommentStatement("Explicit Continue"); + SetStatementComment("Explicit Continue"); goto gotoJump; } } @@ -236,7 +252,7 @@ public override string Decompile() if (JumpsOutOfSwitch()) { NoJumpLabel(); - Commentize(); + SetEndComment(); // 'break' CodeOffset sits at the end of the switch, // check that it doesn't exist already and add it Decompiler._Nester.TryAddNestEnd(NestManager.Nest.NestType.Switch, CodeOffset); @@ -248,10 +264,10 @@ public override string Decompile() if (CodeOffset < Position) { - CommentStatement("Loop Continue"); + SetStatementComment("Loop Continue"); } - gotoJump: + gotoJump: if (Position + Size == CodeOffset) { // Remove jump to next token @@ -315,6 +331,7 @@ private void NoJumpLabel() } } + [ExprToken(ExprToken.JumpIfNot)] public class JumpIfNotToken : JumpToken { public bool IsLoop; @@ -361,7 +378,7 @@ public override string Decompile() } } - Commentize(); + SetEndComment(); if (IsLoop) { Decompiler.PreComment += " [Loop If]"; @@ -407,8 +424,8 @@ public override string Decompile() ifEndJump.CodeOffset != elseStartToken.Position) { // Most likely an if-else, mark it as such and let the rest of the logic figure it out further - uint begin = Position; - var type = NestManager.Nest.NestType.If; + int begin = Position; + const NestManager.Nest.NestType type = NestManager.Nest.NestType.If; Decompiler._Nester.Nests.Add(new NestManager.NestBegin { Position = begin, Type = type, Creator = this }); var nestEnd = new NestManager.NestEnd @@ -441,40 +458,39 @@ public override string Decompile() } } + [ExprToken(ExprToken.FilterEditorOnly)] public class FilterEditorOnlyToken : JumpToken { public override string Decompile() { Decompiler._Nester.AddNest(NestManager.Nest.NestType.Scope, Position, CodeOffset); - Commentize(); + SetEndComment(); return "filtereditoronly"; } } + [ExprToken(ExprToken.Switch)] public class SwitchToken : Token { + public UField ExpressionField; public ushort PropertyType; public override void Deserialize(IUnrealStream stream) { -#if TRANSFORMERS - if (Package.Build == UnrealPackage.GameBuild.BuildName.Transformers) - { - PropertyType = stream.ReadUInt16(); - Decompiler.AlignSize(sizeof(ushort)); - goto deserialize; - } -#endif if (stream.Version >= 600) { // Points to the object that was passed to the switch, // beware that the followed token chain contains it as well! - stream.ReadObjectIndex(); + stream.Read(out ExpressionField); Decompiler.AlignObjectSize(); } - // TODO: Corrigate version - if (stream.Version >= 536 && stream.Version <= 587) + // FIXME: version + if ((stream.Version >= 536 && stream.Version <= 587) +#if DNF + || stream.Package.Build == UnrealPackage.GameBuild.BuildName.DNF +#endif + ) { PropertyType = stream.ReadUInt16(); Decompiler.AlignSize(sizeof(ushort)); @@ -485,7 +501,7 @@ public override void Deserialize(IUnrealStream stream) Decompiler.AlignSize(sizeof(byte)); } - deserialize: + deserialize: // Expression DeserializeNext(); } @@ -522,6 +538,7 @@ public override string Decompile() } } + [ExprToken(ExprToken.Case)] public class CaseToken : JumpToken { public bool IsDefault => CodeOffset == ushort.MaxValue; @@ -537,7 +554,7 @@ public override void Deserialize(IUnrealStream stream) public override string Decompile() { - Commentize(); + SetEndComment(); if (CodeOffset != ushort.MaxValue) { Decompiler._Nester.AddNest(NestManager.Nest.NestType.Case, Position, CodeOffset); @@ -552,6 +569,7 @@ public override string Decompile() } } + [ExprToken(ExprToken.Iterator)] public class IteratorToken : JumpToken { public override void Deserialize(IUnrealStream stream) @@ -563,7 +581,7 @@ public override void Deserialize(IUnrealStream stream) public override string Decompile() { Decompiler._Nester.AddNest(NestManager.Nest.NestType.ForEach, Position, CodeOffset, this); - Commentize(); + SetEndComment(); // foreach FunctionCall string expression = DecompileNext(); @@ -572,7 +590,8 @@ public override string Decompile() } } - public class ArrayIteratorToken : JumpToken + [ExprToken(ExprToken.DynArrayIterator)] + public class DynamicArrayIteratorToken : JumpToken { public byte WithIndexParam; @@ -597,7 +616,7 @@ public override string Decompile() { Decompiler._Nester.AddNest(NestManager.Nest.NestType.ForEach, Position, CodeOffset, this); - Commentize(); + SetEndComment(); // foreach ArrayVariable( Parameters ) string output; @@ -617,6 +636,7 @@ public override string Decompile() } } + [ExprToken(ExprToken.IteratorNext)] public class IteratorNextToken : Token { public override string Decompile() @@ -631,6 +651,7 @@ public override string Decompile() } } + [ExprToken(ExprToken.IteratorPop)] public class IteratorPopToken : Token { public override string Decompile() @@ -649,6 +670,7 @@ public override string Decompile() private List _Labels; private List<(ULabelEntry entry, int refs)> _TempLabels; + [ExprToken(ExprToken.LabelTable)] public class LabelTableToken : Token { public override void Deserialize(IUnrealStream stream) @@ -677,11 +699,14 @@ public override void Deserialize(IUnrealStream stream) } } + [ExprToken(ExprToken.Skip)] public class SkipToken : Token { + public ushort Size; + public override void Deserialize(IUnrealStream stream) { - stream.ReadUInt16(); // Size + Size = stream.ReadUInt16(); Decompiler.AlignSize(sizeof(ushort)); DeserializeNext(); @@ -693,6 +718,7 @@ public override string Decompile() } } + [ExprToken(ExprToken.Stop)] public class StopToken : Token { public override string Decompile() diff --git a/src/Core/Tokens/LetTokens.cs b/src/Core/Tokens/LetTokens.cs index b634ec56..7f70948b 100644 --- a/src/Core/Tokens/LetTokens.cs +++ b/src/Core/Tokens/LetTokens.cs @@ -1,9 +1,13 @@ -namespace UELib.Core +using UELib.ObjectModel.Annotations; +using UELib.Tokens; + +namespace UELib.Core { public partial class UStruct { public partial class UByteCodeDecompiler { + [ExprToken(ExprToken.Let)] public class LetToken : Token { public override void Deserialize(IUnrealStream stream) @@ -20,14 +24,17 @@ public override string Decompile() } } + [ExprToken(ExprToken.LetBool)] public class LetBoolToken : LetToken { } + [ExprToken(ExprToken.LetDelegate)] public class LetDelegateToken : LetToken { } + [ExprToken(ExprToken.EndParmValue)] public class EndParmValueToken : Token { public override string Decompile() @@ -36,6 +43,7 @@ public override string Decompile() } } + [ExprToken(ExprToken.Conditional)] public class ConditionalToken : Token { public override void Deserialize(IUnrealStream stream) @@ -65,4 +73,4 @@ public override string Decompile() } } } -} \ No newline at end of file +} diff --git a/src/Core/Tokens/OtherTokens.cs b/src/Core/Tokens/OtherTokens.cs index aff28101..38d6923c 100644 --- a/src/Core/Tokens/OtherTokens.cs +++ b/src/Core/Tokens/OtherTokens.cs @@ -1,6 +1,7 @@ using System; using System.Diagnostics; using UELib.Annotations; +using UELib.ObjectModel.Annotations; using UELib.Tokens; namespace UELib.Core @@ -9,32 +10,24 @@ public partial class UStruct { public partial class UByteCodeDecompiler { + [ExprToken(ExprToken.Nothing)] public class NothingToken : Token { - public override void Deserialize(IUnrealStream stream) - { - if (stream.Package.Build == UnrealPackage.GameBuild.BuildName.MOHA) - Decompiler.AlignSize(sizeof(int)); - } - - public override string Decompile() - { - // Empty default option parameter! - ++DefaultParameterToken._NextParamIndex; - return string.Empty; - } } - public class NoDelegateToken : NoneToken + [ExprToken(ExprToken.EmptyDelegate)] + public class EmptyDelegateToken : NoneToken { } + [ExprToken(ExprToken.NoObject)] public class NoObjectToken : NoneToken { } // A skipped parameter when calling a function - public class NoParmToken : Token + [ExprToken(ExprToken.EmptyParmValue)] + public class EmptyParmToken : Token { public override string Decompile() { @@ -42,24 +35,27 @@ public override string Decompile() } } + // Also known as an EndCode or EndFunction token. + [ExprToken(ExprToken.EndOfScript)] public class EndOfScriptToken : Token { } + [ExprToken(ExprToken.Assert)] public class AssertToken : Token { public ushort Line; - public bool DebugMode; + public byte? IsDebug; public override void Deserialize(IUnrealStream stream) { Line = stream.ReadUInt16(); Decompiler.AlignSize(sizeof(short)); - // TODO: Corrigate version, at least known since Mirrors Edge(536) - if (stream.Version >= 536) + // FIXME: Version, verified against (RoboBlitz v369) + if (stream.Version >= 200) { - DebugMode = stream.ReadByte() > 0; + IsDebug = stream.ReadByte(); Decompiler.AlignSize(sizeof(byte)); } @@ -68,9 +64,9 @@ public override void Deserialize(IUnrealStream stream) public override string Decompile() { - if (Package.Version >= 536) + if (IsDebug.HasValue) { - Decompiler.PreComment = "// DebugMode:" + DebugMode; + Decompiler.PreComment = $"// DebugMode: {IsDebug}"; } string condition = DecompileNext(); @@ -92,6 +88,7 @@ public override void Deserialize(IUnrealStream stream) } } + [ExprToken(ExprToken.StructCmpEq)] public class StructCmpEqToken : ComparisonToken { public override string Decompile() @@ -100,6 +97,7 @@ public override string Decompile() } } + [ExprToken(ExprToken.StructCmpNe)] public class StructCmpNeToken : ComparisonToken { public override string Decompile() @@ -121,6 +119,7 @@ public override void Deserialize(IUnrealStream stream) } } + [ExprToken(ExprToken.DelegateCmpEq)] public class DelegateCmpEqToken : DelegateComparisonToken { public override string Decompile() @@ -130,28 +129,31 @@ public override string Decompile() return output; } } - - public class DelegateFunctionCmpEqToken : DelegateComparisonToken + + [ExprToken(ExprToken.DelegateCmpNe)] + public class DelegateCmpNeToken : DelegateComparisonToken { public override string Decompile() { - var output = $"{DecompileNext()} == {DecompileNext()}"; + var output = $"{DecompileNext()} != {DecompileNext()}"; DecompileNext(); return output; } } - - public class DelegateCmpNEToken : DelegateComparisonToken + + [ExprToken(ExprToken.DelegateFunctionCmpEq)] + public class DelegateFunctionCmpEqToken : DelegateComparisonToken { public override string Decompile() { - var output = $"{DecompileNext()} != {DecompileNext()}"; + var output = $"{DecompileNext()} == {DecompileNext()}"; DecompileNext(); return output; } } - public class DelegateFunctionCmpNEToken : DelegateComparisonToken + [ExprToken(ExprToken.DelegateFunctionCmpNe)] + public class DelegateFunctionCmpNeToken : DelegateComparisonToken { public override string Decompile() { @@ -160,7 +162,8 @@ public override string Decompile() return output; } } - + + [ExprToken(ExprToken.EatReturnValue)] public class EatReturnValueToken : Token { // Null if version < 200 @@ -172,52 +175,52 @@ public override void Deserialize(IUnrealStream stream) // -- definitely not in the older UE3 builds v186 if (stream.Version < 200) return; - ReturnValueProperty = stream.ReadObject() as UProperty; + ReturnValueProperty = stream.ReadObject(); Decompiler.AlignObjectSize(); } } - public class CastStringSizeToken : Token + [ExprToken(ExprToken.ResizeString)] + public class ResizeStringToken : Token { - public byte Size; + public byte Length; public override void Deserialize(IUnrealStream stream) { - Size = stream.ReadByte(); + Length = stream.ReadByte(); Decompiler.AlignSize(sizeof(byte)); + + // Could there have been an explicit cast too maybe? + DeserializeNext(); + } + + public override string Decompile() + { + return DecompileNext(); } - - // TODO: Decompile format? } + [ExprToken(ExprToken.BeginFunction)] public class BeginFunctionToken : Token { public override void Deserialize(IUnrealStream stream) { - var structContainer = Decompiler._Container; - for (var field = structContainer.Children; field != null; field = field.NextField) + for (;;) { - var property = field as UProperty; - if (property == null) + byte elementSize = stream.ReadByte(); + Decompiler.AlignSize(sizeof(byte)); + if (elementSize == 0x00) { - continue; + break; } - if (!property.HasPropertyFlag(Flags.PropertyFlagsLO.Parm | Flags.PropertyFlagsLO.ReturnParm)) - continue; - - stream.ReadByte(); // Size - Decompiler.AlignSize(sizeof(byte)); - stream.ReadByte(); // bOutParam Decompiler.AlignSize(sizeof(byte)); } - - stream.ReadByte(); // End - Decompiler.AlignSize(sizeof(byte)); } } + [ExprToken(ExprToken.New)] public class NewToken : Token { // Greater Than! @@ -306,6 +309,7 @@ public override string Decompile() } } + [ExprToken(ExprToken.DebugInfo)] public class DebugInfoToken : Token { // Version, usually 100 @@ -326,10 +330,10 @@ public override void Deserialize(IUnrealStream stream) Decompiler.AlignSize(4); #if UNREAL2 // FIXME: Is this a legacy feature or U2 specific? - // Also in RSRS + // Also in RSRS, and GBX engine if (stream.Package.Build == UnrealPackage.GameBuild.BuildName.Unreal2XMP) { - OpCodeText = stream.ReadASCIIString(); + OpCodeText = stream.ReadAnsiNullString(); Decompiler.AlignSize(OpCodeText.Length + 1); Decompiler._Container.Record(nameof(OpCodeText), OpCodeText); if (!Enum.TryParse(OpCodeText, true, out OpCode)) @@ -354,6 +358,7 @@ public override string Decompile() #endif } + [ExprToken(ExprToken.LineNumber)] public class LineNumberToken : Token { public ushort Line; @@ -370,23 +375,6 @@ public override string Decompile() return DecompileNext(); } } -#if BIOSHOCK - public class LogFunctionToken : FunctionToken - { - public override void Deserialize(IUnrealStream stream) - { - // NothingToken(0x0B) twice - DeserializeCall(); - } - - public override string Decompile() - { - Decompiler._CanAddSemicolon = true; - // FIXME: Reverse-order of params? - return DecompileCall("log"); - } - } -#endif } } -} \ No newline at end of file +} diff --git a/src/Core/Tokens/Token.cs b/src/Core/Tokens/Token.cs index 8c45397f..2ca7d898 100644 --- a/src/Core/Tokens/Token.cs +++ b/src/Core/Tokens/Token.cs @@ -1,6 +1,10 @@ using System; using System.Diagnostics; +using System.Reflection; using System.Runtime.CompilerServices; +using UELib.ObjectModel.Annotations; +using UELib.Tokens; +using UELib.UnrealScript; namespace UELib.Core { @@ -8,29 +12,30 @@ public partial class UStruct { public partial class UByteCodeDecompiler { - public abstract class Token : IUnrealDecompilable, IUnrealDeserializableClass + public abstract class Token : IUnrealDecompilable, IUnrealDeserializableClass, IAcceptable { public UByteCodeDecompiler Decompiler { get; set; } - protected UnrealPackage Package => Decompiler.Package; - - public byte RepresentToken; + protected UnrealPackage Package => Decompiler._Package; /// - /// The relative position of this token. - /// Storage--The actual token position within the Buffer. + /// The raw serialized byte-code for this token. + /// + /// e.g. if this token were to represent an expression token such as + /// then the OpCode will read 0x36 for UE2, but 0x35 for UE3. + /// This can be useful for tracking or re-writing a token to a file. /// - public uint Position; - - public uint StoragePosition; + public byte OpCode; /// - /// The size of this token and its inlined tokens. - /// Storage--The actual token size within the Buffer. + /// The read position of this token in memory relative to . /// - public ushort Size; + public int Position, StoragePosition; - public ushort StorageSize; + /// + /// The read size of this token in memory, inclusive of child tokens. + /// + public short Size, StorageSize; public virtual void Deserialize(IUnrealStream stream) { @@ -45,11 +50,6 @@ public virtual string Decompile() return string.Empty; } - public virtual string Disassemble() - { - return $"0x{RepresentToken:X2}"; - } - protected string DecompileNext() { tryNext: @@ -58,7 +58,7 @@ protected string DecompileNext() return token.Decompile(); } - + [MethodImpl(MethodImplOptions.AggressiveInlining)] protected T NextToken() where T : Token @@ -75,13 +75,13 @@ protected Token NextToken() 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 . /// @@ -105,6 +105,13 @@ protected Token DeserializeNext() return Decompiler.DeserializeNext(); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected T DeserializeNext() + where T : Token + { + return (T)Decompiler.DeserializeNext(); + } + /// /// Wrapper for IUnrealStream.ReadNameReference to handle memory alignment as well as differences between builds. /// @@ -145,13 +152,48 @@ protected T ReadObject(IUnrealStream stream) return obj; } + public void Accept(IVisitor visitor) + { + visitor.Visit(this); + } + + public TResult Accept(IVisitor visitor) + { + return visitor.Visit(this); + } + + public ExprToken GetExprToken() + { + var type = GetType(); + var exprTokenAttr = type.GetCustomAttribute(); + return exprTokenAttr.ExprToken; + } + + public override int GetHashCode() => GetType().GetHashCode(); + public override string ToString() { return - $"\r\nType:{GetType().Name}\r\nToken:{RepresentToken:X2}\r\nPosition:{Position}\r\nSize:{Size}" - .Replace("\n", "\n" - + UDecompilingState.Tabs - ); + $"\r\nInstantiated Type: {GetType().Name}" + + $"\r\nSerialized OpCode: {OpCode:X2}h" + + $"\r\nOffset: {PropertyDisplay.FormatOffset(Position)}" + + $"\r\nSize: {PropertyDisplay.FormatOffset(Size)}"; + } + } + + public class BadToken : Token + { + public override void Deserialize(IUnrealStream stream) + { +#if STRICT + Debug.Fail($"Bad expression token 0x{OpCode:X2}"); +#endif + } + + public override string Decompile() + { + Decompiler.PreComment = $"// {FormatTokenInfo(this)}"; + return default; } } @@ -172,4 +214,4 @@ public override string Decompile() } } } -} \ No newline at end of file +} diff --git a/src/Core/Tokens/TokenFactory.cs b/src/Core/Tokens/TokenFactory.cs new file mode 100644 index 00000000..b75075fd --- /dev/null +++ b/src/Core/Tokens/TokenFactory.cs @@ -0,0 +1,98 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using UELib.Annotations; +using static UELib.Core.UStruct.UByteCodeDecompiler; + +namespace UELib.Core.Tokens +{ + [UsedImplicitly] + public class TokenFactory + { + [NotNull] protected readonly TokenMap TokenMap; + [NotNull] protected readonly Dictionary NativeTokenMap; + + public readonly byte ExtendedNative; + public readonly byte FirstNative; + + public TokenFactory( + TokenMap tokenMap, + Dictionary nativeTokenMap, + byte extendedNative, + byte firstNative) + { + TokenMap = tokenMap; + Debug.Assert(TokenMap != null, $"{nameof(TokenMap)} cannot be null"); + + NativeTokenMap = nativeTokenMap; + Debug.Assert(NativeTokenMap != null, $"{nameof(NativeTokenMap)} cannot be null"); + + ExtendedNative = extendedNative; + FirstNative = firstNative; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [NotNull] + public Type GetTokenTypeFromOpCode(byte opCode) + { + Debug.Assert(opCode < ExtendedNative, $"Unexpected native OpCode 0x{opCode:X2}"); + Debug.Assert(TokenMap.ContainsKey(opCode), $"OpCode 0x{opCode:X2} is missing from ${nameof(TokenMap)}"); + return TokenMap[opCode]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [NotNull] + public T CreateToken(byte opCode) + where T : Token + { + var tokenType = GetTokenTypeFromOpCode(opCode); + var token = (T)Activator.CreateInstance(tokenType); + token.OpCode = opCode; + return token; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [NotNull] + public static T CreateToken(Type tokenType) + where T : Token + { + return (T)Activator.CreateInstance(tokenType); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [NotNull] + public NativeFunctionToken CreateNativeToken(ushort nativeIndex) + { + if (NativeTokenMap.TryGetValue(nativeIndex, out var item)) + { + return new NativeFunctionToken + { + NativeItem = item, + }; + } + + return new NativeFunctionToken + { + NativeItem = new NativeTableItem + { + Type = FunctionType.Function, + Name = CreateGeneratedName($"NFUN_{nativeIndex}"), + ByteToken = nativeIndex + }, + }; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string CreateGeneratedName(string id) + { + return $"__{id}__"; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Dictionary FromPackage([CanBeNull] NativesTablePackage package) + { + return package?.NativeTokenMap ?? new Dictionary(); + } + } +} \ No newline at end of file diff --git a/src/Core/Tokens/TokenMap.cs b/src/Core/Tokens/TokenMap.cs new file mode 100644 index 00000000..7bb3696b --- /dev/null +++ b/src/Core/Tokens/TokenMap.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; + +namespace UELib.Core.Tokens +{ + public class TokenMap : Dictionary + { + public TokenMap() + { + } + + public TokenMap(byte extendedNative) : base(extendedNative - 1) + { + } + } +} diff --git a/src/Core/Tokens/ValidateObjectToken.cs b/src/Core/Tokens/ValidateObjectToken.cs new file mode 100644 index 00000000..a53d4fbd --- /dev/null +++ b/src/Core/Tokens/ValidateObjectToken.cs @@ -0,0 +1,11 @@ +using UELib.ObjectModel.Annotations; +using UELib.Tokens; + +namespace UELib.Core +{ + // Not sure what this this byte-code is for; can occur in old Unreal packages. + [ExprToken(ExprToken.ValidateObject)] + public class ValidateObjectToken : UStruct.UByteCodeDecompiler.Token + { + } +} diff --git a/src/Core/Types/UBox.cs b/src/Core/Types/UBox.cs new file mode 100644 index 00000000..5d4dfadd --- /dev/null +++ b/src/Core/Types/UBox.cs @@ -0,0 +1,35 @@ +using System.Runtime.InteropServices; + +namespace UELib.Core +{ + /// + /// Implements FBox/UObject.Box + /// + [StructLayout(LayoutKind.Sequential)] + public struct UBox : IUnrealSerializableClass, IUnrealAtomicStruct + { + public UVector Min, Max; + public byte IsValid; + + public UBox(UVector min, UVector max, byte isValid) + { + Min = min; + Max = max; + IsValid = isValid; + } + + public void Deserialize(IUnrealStream stream) + { + stream.ReadStruct(out Min); + stream.ReadStruct(out Max); + stream.Read(out IsValid); + } + + public void Serialize(IUnrealStream stream) + { + stream.WriteStruct(ref Min); + stream.WriteStruct(ref Max); + stream.Write(IsValid); + } + } +} \ No newline at end of file diff --git a/src/Core/Types/UBulkData.cs b/src/Core/Types/UBulkData.cs new file mode 100644 index 00000000..54e2df2f --- /dev/null +++ b/src/Core/Types/UBulkData.cs @@ -0,0 +1,280 @@ +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using UELib.Annotations; +using UELib.Branch; +using UELib.Flags; + +namespace UELib.Core +{ + /// + /// Implements FUntypedBulkData (UE3) and TLazyArray (UE3 or older) + /// + /// The primitive element type of the bulk data. + public struct UBulkData : IUnrealSerializableClass, IDisposable + where TElement : unmanaged + { + public BulkDataFlags Flags; + + public long StorageSize; + public long StorageOffset; + + public int ElementCount; + [CanBeNull] public byte[] ElementData; + + // TODO: multi-byte based data. + public UBulkData(BulkDataFlags flags, byte[] rawData) + { + Flags = flags; + StorageSize = -1; + StorageOffset = -1; + + ElementCount = rawData.Length; + ElementData = rawData; + } + + public void Deserialize(IUnrealStream stream) + { + if (stream.Version < (uint)PackageObjectLegacyVersion.LazyArrayReplacedWithBulkData) + { + DeserializeLegacyLazyArray(stream); + return; + } + + stream.Read(out uint flags); + Flags = (BulkDataFlags)flags; + + stream.Read(out ElementCount); + + StorageSize = stream.ReadInt32(); + StorageOffset = stream.ReadInt32(); + + if (Flags.HasFlag(BulkDataFlags.StoreInSeparateFile)) + { + return; + } + + Debug.Assert(stream.AbsolutePosition == StorageOffset); + // Skip the ElementData + stream.AbsolutePosition = StorageOffset + StorageSize; + } + + // Deserializes the TLazyArray format + private void DeserializeLegacyLazyArray(IUnrealStream stream) + { + int elementSize = Unsafe.SizeOf(); + + if (stream.Version < (uint)PackageObjectLegacyVersion.LazyArraySkipCountChangedToSkipOffset) + { + ElementCount = stream.ReadIndex(); + + StorageSize = ElementCount * elementSize; + StorageOffset = stream.AbsolutePosition; + + stream.AbsolutePosition += StorageSize; + return; + } + + // Absolute position in stream + long skipOffset = stream.ReadInt32(); + + // We still need these checks for Rainbow Six: Vegas 2 (V241) + if (stream.Version >= (uint)PackageObjectLegacyVersion.StorageSizeAddedToLazyArray) + { + StorageSize = stream.ReadInt32(); + } + + if (stream.Version >= (uint)PackageObjectLegacyVersion.LazyLoaderFlagsAddedToLazyArray) + { + stream.Read(out uint flags); + Flags = (BulkDataFlags)flags; + } + + if (stream.Version >= (uint)PackageObjectLegacyVersion.L8AddedToLazyArray) + { + stream.Read(out UName l8); + } + + ElementCount = stream.ReadLength(); + + StorageSize = skipOffset - stream.AbsolutePosition; + StorageOffset = stream.AbsolutePosition; + + stream.AbsolutePosition = skipOffset; + } + + public void LoadData(IUnrealStream stream) + { + if (Flags.HasFlag(BulkDataFlags.Unused) || ElementCount == 0) + { + return; + } + + int elementSize = Unsafe.SizeOf(); + ElementData = new byte[ElementCount * elementSize]; + + if (Flags.HasFlag(BulkDataFlags.StoreInSeparateFile)) + { + throw new NotSupportedException("Cannot read bulk data from a separate file yet!"); + } + + if (Flags.HasFlag(BulkDataFlags.StoreOnlyPayload)) + { + Debugger.Break(); + } + + Debug.Assert(StorageOffset != -1); + Debug.Assert(StorageSize != -1); + + long returnPosition = stream.Position; + stream.AbsolutePosition = StorageOffset; + + if (Flags.HasFlag(BulkDataFlags.Compressed)) + { + stream.ReadStruct(out CompressedChunkHeader header); + + Debugger.Break(); + + int offset = 0; + var decompressFlags = Flags.ToCompressionFlags(); + foreach (var chunk in header.Chunks) + { + stream.Read(ElementData, offset, chunk.CompressedSize); + offset += chunk.Decompress(ElementData, offset, decompressFlags); + } + + Debug.Assert(offset == ElementData.Length); + } + else + { + stream.Read(ElementData, 0, ElementData.Length); + } + + stream.Position = returnPosition; + } + + public void Serialize(IUnrealStream stream) + { + Debug.Assert(ElementData != null); + + if (stream.Version < (uint)PackageObjectLegacyVersion.LazyArrayReplacedWithBulkData) + { + SerializeLegacyLazyArray(stream); + return; + } + + stream.Write((uint)Flags); + + int elementCount = ElementData.Length / Unsafe.SizeOf(); + Debug.Assert(ElementCount == elementCount, "ElementCount mismatch"); + stream.Write(ElementCount); + + long storageSizePosition = stream.Position; + stream.Write((int)StorageSize); + stream.Write((int)StorageOffset); + + if (Flags.HasFlag(BulkDataFlags.StoreInSeparateFile)) + { + return; + } + + long storageOffsetPosition = stream.AbsolutePosition; + stream.Write(ElementData); + + StorageOffset = storageOffsetPosition; + StorageSize = stream.AbsolutePosition - StorageOffset; + + // Go back and rewrite the actual values. + stream.Position = storageSizePosition; + stream.Write((int)StorageSize); + stream.Write((int)StorageOffset); + // Restore + stream.Position = StorageOffset + StorageSize; + } + + private void SerializeLegacyLazyArray(IUnrealStream stream) + { + int elementSize = Unsafe.SizeOf(); + Debug.Assert(ElementCount == ElementData.Length / elementSize, "ElementCount mismatch"); + + if (stream.Version < (uint)PackageObjectLegacyVersion.LazyArraySkipCountChangedToSkipOffset) + { + stream.WriteIndex(ElementCount); + + StorageOffset = stream.AbsolutePosition; + stream.Write(ElementData, 0, ElementData.Length); + StorageSize = stream.AbsolutePosition - StorageOffset; + return; + } + + long storageDataPosition = stream.Position; + const int fakeSkipOffset = 0; + stream.Write(fakeSkipOffset); + + if (stream.Version >= (uint)PackageObjectLegacyVersion.StorageSizeAddedToLazyArray) + { + stream.Write((int)StorageSize); + } + + if (stream.Version >= (uint)PackageObjectLegacyVersion.LazyLoaderFlagsAddedToLazyArray) + { + stream.Write((uint)Flags); + } + + if (stream.Version >= (uint)PackageObjectLegacyVersion.L8AddedToLazyArray) + { + var dummy = new UName(""); + stream.Write(dummy); + } + + stream.WriteIndex(ElementCount); + StorageOffset = stream.AbsolutePosition; + if (Flags.HasFlag(BulkDataFlags.StoreInSeparateFile)) + { + throw new NotSupportedException("Cannot write bulk data to a separate file yet!"); + } + + if (Flags.HasFlag(BulkDataFlags.Compressed)) + { + throw new NotSupportedException("Compressing bulk data is not yet supported."); + } + + stream.Write(ElementData, 0, ElementData.Length); + + StorageSize = stream.AbsolutePosition - StorageOffset; + + long realSkipOffset = stream.AbsolutePosition; + stream.Position = storageDataPosition; + stream.Write((int)realSkipOffset); + stream.AbsolutePosition = realSkipOffset; + } + + public void Dispose() => ElementData = null; + } + + [Flags] + public enum BulkDataFlags : uint + { + // Formerly PayloadInSeparateFile? Attested in the assembly "!(LazyLoaderFlags & LLF_PayloadInSeparateFile)" + StoreInSeparateFile = 0b1, + CompressedZLIB = 0b10, + ForceSingleElementSerialization = 0b100, + SingleUse = 0b1000, + CompressedLZO = 0b10000, + Unused = 0b100000, + StoreOnlyPayload = 0b1000000, + CompressedLZX = 0b10000000, + Compressed = CompressedZLIB | CompressedLZO | CompressedLZX + } + + public static class BulkDataFlagsExtensions + { + public static CompressionFlags ToCompressionFlags(this BulkDataFlags flags) + { + uint cFlags = (((uint)flags & (uint)BulkDataFlags.CompressedZLIB) >> 1) + | (((uint)flags & (uint)BulkDataFlags.CompressedLZO) >> 3) + | (((uint)flags & (uint)BulkDataFlags.CompressedLZX) >> 5); + return (CompressionFlags)cFlags; + } + } +} diff --git a/src/Core/Types/UColor.cs b/src/Core/Types/UColor.cs index ea60767e..6dcbbcfb 100644 --- a/src/Core/Types/UColor.cs +++ b/src/Core/Types/UColor.cs @@ -1,25 +1,51 @@ -using System.Drawing; -using System.Runtime.InteropServices; -using UELib.Annotations; +using System.Runtime.InteropServices; -namespace UELib.Core.Types +namespace UELib.Core { /// - /// Implements FColor/UObject.Color + /// Implements FColor/UObject.Color /// - [StructLayout(LayoutKind.Sequential)] - public struct UColor : IUnrealAtomicStruct + [StructLayout(LayoutKind.Sequential, Pack = 4)] + public struct UColor : IUnrealSerializableClass, IUnrealAtomicStruct { // The order may change based on compile-time constants. - // Intel Win32 x86 - public byte B, G, R, A; - // Non-intel - //public byte A, R, G, B; - [PublicAPI] - public Color ToColor() + // + public byte B, G, R, A; // UE2, UE3 + //public byte R, G, B, A; // UE1 + + // + //public byte B, G, R, A; // UE3 + + // + //public byte A, R, G, B; // UE2, UE3 + //public byte A, B, G, R; // UE1 + + public UColor(byte b, byte g, byte r, byte a) + { + B = b; + G = g; + R = r; + A = a; + } + + // FIXME: RGBA UE1, UE2, UE3.. + // Always packed as one Int32 (order BGRA for Intel-byte-order) if serialized in bulk, and non bulk for later UE3 builds. + // Packed as RGBA for UE1 unless not build intel-byte-order. + public void Deserialize(IUnrealStream stream) + { + stream.Read(out R); + stream.Read(out G); + stream.Read(out B); + stream.Read(out A); + } + + public void Serialize(IUnrealStream stream) { - return Color.FromArgb(A, R, G, B); + stream.Write(R); + stream.Write(G); + stream.Write(B); + stream.Write(A); } } -} +} \ No newline at end of file diff --git a/src/Core/Types/UCoords.cs b/src/Core/Types/UCoords.cs new file mode 100644 index 00000000..4392b827 --- /dev/null +++ b/src/Core/Types/UCoords.cs @@ -0,0 +1,40 @@ +using System.Runtime.InteropServices; + +namespace UELib.Core +{ + /// + /// Implements FCoords/UObject.Coords + /// + [StructLayout(LayoutKind.Sequential, Pack = 4)] + public struct UCoords : IUnrealSerializableClass, IUnrealAtomicStruct + { + public UVector Origin; + public UVector XAxis; + public UVector YAxis; + public UVector ZAxis; + + public UCoords(ref UVector origin, ref UVector xAxis, ref UVector yAxis, ref UVector zAxis) + { + Origin = origin; + XAxis = xAxis; + YAxis = yAxis; + ZAxis = zAxis; + } + + public void Deserialize(IUnrealStream stream) + { + stream.ReadStruct(out Origin); + stream.ReadStruct(out XAxis); + stream.ReadStruct(out YAxis); + stream.ReadStruct(out ZAxis); + } + + public void Serialize(IUnrealStream stream) + { + stream.WriteStruct(ref Origin); + stream.WriteStruct(ref XAxis); + stream.WriteStruct(ref YAxis); + stream.WriteStruct(ref ZAxis); + } + } +} \ No newline at end of file diff --git a/src/Core/Types/UGuid.cs b/src/Core/Types/UGuid.cs new file mode 100644 index 00000000..feae5dd1 --- /dev/null +++ b/src/Core/Types/UGuid.cs @@ -0,0 +1,58 @@ +using System; +using System.Runtime.InteropServices; + +namespace UELib.Core +{ + /// + /// Implements FGuid/UObject.Guid + /// + [StructLayout(LayoutKind.Sequential, Pack = 8)] + public struct UGuid : IUnrealSerializableClass, IUnrealAtomicStruct + { + public uint A, B, C, D; + + public UGuid(uint a, uint b, uint c, uint d) + { + A = a; + B = b; + C = c; + D = d; + } + + public void Deserialize(IUnrealStream stream) + { + stream.Read(out A); + stream.Read(out B); + stream.Read(out C); + stream.Read(out D); + } + + public void Serialize(IUnrealStream stream) + { + stream.Write(A); + stream.Write(B); + stream.Write(C); + stream.Write(D); + } + + public static unsafe explicit operator Guid(UGuid u) + { + return *(Guid*)&u; + } + + public static unsafe explicit operator UGuid(Guid m) + { + return *(UGuid*)&m; + } + + public override int GetHashCode() + { + return ((Guid)this).GetHashCode(); + } + + public override string ToString() + { + return $"{A:X8}{B:X8}{C:X8}{D:X8}"; + } + } +} \ No newline at end of file diff --git a/src/Core/Types/ULinearColor.cs b/src/Core/Types/ULinearColor.cs new file mode 100644 index 00000000..e9fd7826 --- /dev/null +++ b/src/Core/Types/ULinearColor.cs @@ -0,0 +1,53 @@ +using System.Numerics; +using System.Runtime.InteropServices; + +namespace UELib.Core +{ + /// + /// Implements FLinearColor/UObject.LinearColor + /// + [StructLayout(LayoutKind.Sequential, Pack = 8)] + public struct ULinearColor : IUnrealSerializableClass, IUnrealAtomicStruct + { + public float R, G, B, A; + + public ULinearColor(float r, float g, float b, float a = 1.0f) + { + R = r; + G = g; + B = b; + A = a; + } + + public void Deserialize(IUnrealStream stream) + { + stream.Read(out R); + stream.Read(out G); + stream.Read(out B); + stream.Read(out A); + } + + public void Serialize(IUnrealStream stream) + { + stream.Write(R); + stream.Write(G); + stream.Write(B); + stream.Write(A); + } + + public static unsafe explicit operator Vector4(ULinearColor u) + { + return *(Vector4*)&u; + } + + public static unsafe explicit operator ULinearColor(Vector4 m) + { + return *(ULinearColor*)&m; + } + + public override int GetHashCode() + { + return ((Vector4)this).GetHashCode(); + } + } +} \ No newline at end of file diff --git a/src/Core/Types/UMatrix.cs b/src/Core/Types/UMatrix.cs new file mode 100644 index 00000000..aec52c44 --- /dev/null +++ b/src/Core/Types/UMatrix.cs @@ -0,0 +1,59 @@ +using System.Numerics; +using System.Runtime.InteropServices; + +namespace UELib.Core +{ + /// + /// Implements FMatrix/UObject.Matrix + /// + [StructLayout(LayoutKind.Sequential, Pack = 8)] + public struct UMatrix : IUnrealSerializableClass, IUnrealAtomicStruct + { + public float[,] M; + + public UMatrix(float[,] m) + { + M = m; + } + + public UMatrix(ref UPlane x, ref UPlane y, ref UPlane z, ref UPlane w) + { + M = new float[4, 4]; + M[0, 0] = x.X; M[0, 1] = x.Y; M[0, 2] = x.Z; M[0, 3] = x.W; + M[1, 0] = y.X; M[1, 1] = y.Y; M[1, 2] = y.Z; M[1, 3] = y.W; + M[2, 0] = z.X; M[2, 1] = z.Y; M[2, 2] = z.Z; M[2, 3] = z.W; + M[3, 0] = w.X; M[3, 1] = w.Y; M[3, 2] = w.Z; M[3, 3] = w.W; + } + + public void Deserialize(IUnrealStream stream) + { + stream.Read(out M[0, 0]); stream.Read(out M[0, 1]); stream.Read(out M[0, 2]); stream.Read(out M[0, 3]); + stream.Read(out M[1, 0]); stream.Read(out M[1, 1]); stream.Read(out M[1, 2]); stream.Read(out M[1, 3]); + stream.Read(out M[2, 0]); stream.Read(out M[2, 1]); stream.Read(out M[2, 2]); stream.Read(out M[2, 3]); + stream.Read(out M[3, 0]); stream.Read(out M[3, 1]); stream.Read(out M[3, 2]); stream.Read(out M[3, 3]); + } + + public void Serialize(IUnrealStream stream) + { + stream.Write(M[0, 0]); stream.Write(M[0, 1]); stream.Write(M[0, 2]); stream.Write(M[0, 3]); + stream.Write(M[1, 0]); stream.Write(M[1, 1]); stream.Write(M[1, 2]); stream.Write(M[1, 3]); + stream.Write(M[2, 0]); stream.Write(M[2, 1]); stream.Write(M[2, 2]); stream.Write(M[2, 3]); + stream.Write(M[3, 0]); stream.Write(M[3, 1]); stream.Write(M[3, 2]); stream.Write(M[3, 3]); + } + + //public static unsafe explicit operator Matrix4x4(UMatrix u) + //{ + // return *(Matrix4x4*)&u; + //} + + //public static unsafe explicit operator UMatrix(Matrix4x4 m) + //{ + // return *(UMatrix*)&m; + //} + + //public override int GetHashCode() + //{ + // return ((Matrix4x4)this).GetHashCode(); + //} + } +} \ No newline at end of file diff --git a/src/Core/Types/UName.cs b/src/Core/Types/UName.cs index 9400cf50..9a5dc5d4 100644 --- a/src/Core/Types/UName.cs +++ b/src/Core/Types/UName.cs @@ -1,16 +1,14 @@ using System; -// FIXME: Namespace correction is blocked by (UE Explorer) -namespace UELib +namespace UELib.Core { /// /// Implements FName. A data type that represents a string, usually acquired from a names table. /// - public class UName + public class UName : IComparable { private const string None = "None"; public const int Numeric = 0; - public const int VNameNumbered = 343; private UNameTableItem _NameItem; @@ -32,7 +30,7 @@ public UName(IUnrealStream stream) _NameItem = stream.Package.Names[index]; } - public UName(UNameTableItem nameItem, int number) + public UName(UNameTableItem nameItem, int number = Numeric) { _NameItem = nameItem; _Number = number; @@ -46,7 +44,7 @@ public UName(string text) Name = text }; _NameItem = nameEntry; - _Number = 0; + _Number = Numeric; } public bool IsNone() @@ -54,6 +52,8 @@ public bool IsNone() return _NameItem.Name.Equals(None, StringComparison.OrdinalIgnoreCase); } + public int CompareTo(UName other) => string.Compare(other.Text, Text, StringComparison.OrdinalIgnoreCase); + public override string ToString() { return Text; @@ -104,4 +104,4 @@ public override int GetHashCode() return a.Index; } } -} \ No newline at end of file +} diff --git a/src/Core/Types/UPlane.cs b/src/Core/Types/UPlane.cs new file mode 100644 index 00000000..7bcf7401 --- /dev/null +++ b/src/Core/Types/UPlane.cs @@ -0,0 +1,68 @@ +using System.Numerics; +using System.Runtime.InteropServices; + +namespace UELib.Core +{ + /// + /// Implements FPlane/UObject.Plane + /// Extends Vector, but we can't do this in C# + /// + [StructLayout(LayoutKind.Sequential, Pack = 8)] + public struct UPlane : IUnrealSerializableClass, IUnrealAtomicStruct + { + /// + /// It's important to retain W as the first field, because in Unreal FPlane extends FVector. + /// A necessary evil for proper Marshalling of the type. + /// + public float W; + + public float X, Y, Z; + + public UPlane(float x, float y, float z, float w) + { + X = x; + Y = y; + Z = z; + W = w; + } + + public UPlane(ref UVector v, float w) + { + X = v.X; + Y = v.Y; + Z = v.Z; + W = w; + } + + public void Deserialize(IUnrealStream stream) + { + stream.Read(out X); + stream.Read(out Y); + stream.Read(out Z); + stream.Read(out W); + } + + public void Serialize(IUnrealStream stream) + { + stream.Write(X); + stream.Write(Y); + stream.Write(Z); + stream.Write(W); + } + + public static unsafe explicit operator Plane(UPlane u) + { + return *(Plane*)&u; + } + + public static unsafe explicit operator UPlane(Plane m) + { + return *(UPlane*)&m; + } + + public override int GetHashCode() + { + return ((Plane)this).GetHashCode(); + } + } +} \ No newline at end of file diff --git a/src/Core/Types/UQuat.cs b/src/Core/Types/UQuat.cs new file mode 100644 index 00000000..9bab6ed9 --- /dev/null +++ b/src/Core/Types/UQuat.cs @@ -0,0 +1,61 @@ +using System.Numerics; +using System.Runtime.InteropServices; + +namespace UELib.Core +{ + /// + /// Implements FQuat/UObject.Quat + /// + [StructLayout(LayoutKind.Sequential, Pack = 8)] + public struct UQuat : IUnrealSerializableClass, IUnrealAtomicStruct + { + public float X, Y, Z, W; + + public UQuat(float x, float y, float z, float w) + { + X = x; + Y = y; + Z = z; + W = w; + } + + public UQuat(ref UVector v, float w) + { + X = v.X; + Y = v.Y; + Z = v.Z; + W = w; + } + + public void Deserialize(IUnrealStream stream) + { + stream.Read(out X); + stream.Read(out Y); + stream.Read(out Z); + stream.Read(out W); + } + + public void Serialize(IUnrealStream stream) + { + stream.Write(X); + stream.Write(Y); + stream.Write(Z); + stream.Write(W); + } + + public static unsafe explicit operator Quaternion(UQuat u) + { + return *(Quaternion*)&u; + } + + public static unsafe explicit operator UQuat(Quaternion m) + { + return *(UQuat*)&m; + } + + public override int GetHashCode() + { + return ((Quaternion)this).GetHashCode(); + } + } +} \ No newline at end of file diff --git a/src/Core/Types/URange.cs b/src/Core/Types/URange.cs new file mode 100644 index 00000000..771f0d9b --- /dev/null +++ b/src/Core/Types/URange.cs @@ -0,0 +1,31 @@ +using System.Runtime.InteropServices; + +namespace UELib.Core +{ + /// + /// Implements FRange/UObject.Range + /// + [StructLayout(LayoutKind.Sequential, Pack = 8)] + public struct URange : IUnrealSerializableClass, IUnrealAtomicStruct + { + public float A, B; + + public URange(float a, float b) + { + A = a; + B = b; + } + + public void Deserialize(IUnrealStream stream) + { + stream.Read(out A); + stream.Read(out B); + } + + public void Serialize(IUnrealStream stream) + { + stream.Write(A); + stream.Write(B); + } + } +} \ No newline at end of file diff --git a/src/Core/Types/URangeVector.cs b/src/Core/Types/URangeVector.cs new file mode 100644 index 00000000..d740477c --- /dev/null +++ b/src/Core/Types/URangeVector.cs @@ -0,0 +1,34 @@ +using System.Runtime.InteropServices; + +namespace UELib.Core +{ + /// + /// Implements FRangeVector/UObject.RangeVector + /// + [StructLayout(LayoutKind.Sequential, Pack = 4)] + public struct URangeVector : IUnrealSerializableClass, IUnrealAtomicStruct + { + public URange X, Y, Z; + + public URangeVector(URange x, URange y, URange z) + { + X = x; + Y = y; + Z = z; + } + + public void Deserialize(IUnrealStream stream) + { + stream.ReadStruct(out X); + stream.ReadStruct(out Y); + stream.ReadStruct(out Y); + } + + public void Serialize(IUnrealStream stream) + { + stream.WriteStruct(ref X); + stream.WriteStruct(ref Y); + stream.WriteStruct(ref Z); + } + } +} \ No newline at end of file diff --git a/src/Core/Types/URotator.cs b/src/Core/Types/URotator.cs new file mode 100644 index 00000000..ff4255c5 --- /dev/null +++ b/src/Core/Types/URotator.cs @@ -0,0 +1,29 @@ +using System.Runtime.InteropServices; + +namespace UELib.Core +{ + /// + /// Implements FRotator/UObject.Rotator + /// + [StructLayout(LayoutKind.Sequential, Pack = 4)] + public struct URotator : IUnrealSerializableClass, IUnrealAtomicStruct + { + public int Pitch; + public int Yaw; + public int Roll; + + public void Deserialize(IUnrealStream stream) + { + stream.Read(out Pitch); + stream.Read(out Yaw); + stream.Read(out Roll); + } + + public void Serialize(IUnrealStream stream) + { + stream.Write(Pitch); + stream.Write(Yaw); + stream.Write(Roll); + } + } +} \ No newline at end of file diff --git a/src/Core/Types/UScale.cs b/src/Core/Types/UScale.cs new file mode 100644 index 00000000..9d30904b --- /dev/null +++ b/src/Core/Types/UScale.cs @@ -0,0 +1,51 @@ +using System.Runtime.InteropServices; + +namespace UELib.Core +{ + /// + /// Implements ESheerAxis/UObject.ESheerAxis + /// + public enum SheerAxis : byte + { + None = 0, + XY = 1, + XZ = 2, + YX = 3, + YZ = 4, + ZX = 5, + ZY = 6 + } + + /// + /// Implements FScale/UObject.Scale + /// + [StructLayout(LayoutKind.Sequential, Pack = 8)] + public struct UScale : IUnrealSerializableClass, IUnrealAtomicStruct + { + public UVector Scale; + public float SheerRate; + public SheerAxis SheerAxis; + + public UScale(ref UVector scale, float sheerRate, SheerAxis sheerAxis) + { + Scale = scale; + SheerRate = sheerRate; + SheerAxis = sheerAxis; + } + + public void Deserialize(IUnrealStream stream) + { + stream.ReadStruct(out Scale); + stream.Read(out SheerRate); + var sheerAxis = (byte)SheerAxis; + stream.Read(out sheerAxis); + } + + public void Serialize(IUnrealStream stream) + { + stream.WriteStruct(ref Scale); + stream.Write(SheerRate); + stream.Write((byte)SheerAxis); + } + } +} \ No newline at end of file diff --git a/src/Core/Types/USphere.cs b/src/Core/Types/USphere.cs new file mode 100644 index 00000000..f3c9c41f --- /dev/null +++ b/src/Core/Types/USphere.cs @@ -0,0 +1,54 @@ +using System.Runtime.InteropServices; +using UELib.Branch; + +namespace UELib.Core +{ + /// + /// Implements FSphere/UObject.Sphere + /// Extends Plane, but we can't do this in C# + /// Cannot be serialized in bulk due the addition of the "W" field. + /// + [StructLayout(LayoutKind.Sequential, Pack = 8)] + public struct USphere : IUnrealSerializableClass, IUnrealAtomicStruct + { + public float W, X, Y, Z; + + public USphere(float x, float y, float z, float w) + { + X = x; + Y = y; + Z = z; + W = w; + } + + public USphere(ref UVector v, float w) + { + X = v.X; + Y = v.Y; + Z = v.Z; + W = w; + } + + public void Deserialize(IUnrealStream stream) + { + stream.Read(out X); + stream.Read(out Y); + stream.Read(out Z); + if (stream.Version >= (uint)PackageObjectLegacyVersion.SphereExtendsPlane) + { + stream.Read(out W); + } + } + + public void Serialize(IUnrealStream stream) + { + stream.Write(X); + stream.Write(Y); + stream.Write(Z); + if (stream.Version >= (uint)PackageObjectLegacyVersion.SphereExtendsPlane) + { + stream.Write(W); + } + } + } +} \ No newline at end of file diff --git a/src/Core/Types/UVector.cs b/src/Core/Types/UVector.cs new file mode 100644 index 00000000..cd5dc162 --- /dev/null +++ b/src/Core/Types/UVector.cs @@ -0,0 +1,55 @@ +using System.Numerics; +using System.Runtime.InteropServices; + +namespace UELib.Core +{ + /// + /// Implements FVector/UObject.Vector + /// + [StructLayout(LayoutKind.Sequential, Pack = 8)] + public struct UVector : IUnrealSerializableClass, IUnrealAtomicStruct + { + public float X, Y, Z; + + public UVector(float x, float y, float z) + { + X = x; + Y = y; + Z = z; + } + + public void Deserialize(IUnrealStream stream) + { + stream.Read(out X); + stream.Read(out Y); + stream.Read(out Z); + } + + public void Serialize(IUnrealStream stream) + { + stream.Write(X); + stream.Write(Y); + stream.Write(Z); + } + + public static unsafe explicit operator Vector3(UVector u) + { + return *(Vector3*)&u; + } + + public static unsafe explicit operator UVector(Vector3 m) + { + return *(UVector*)&m; + } + + public override int GetHashCode() + { + return ((Vector3)this).GetHashCode(); + } + + public override string ToString() + { + return $"vect({X:F},{Y:F},{Z:F})"; + } + } +} \ No newline at end of file diff --git a/src/Core/Types/UVector2D.cs b/src/Core/Types/UVector2D.cs new file mode 100644 index 00000000..45bc4501 --- /dev/null +++ b/src/Core/Types/UVector2D.cs @@ -0,0 +1,47 @@ +using System.Numerics; +using System.Runtime.InteropServices; + +namespace UELib.Core +{ + /// + /// Implements FVector2D/UObject.Vector2D or FPoint (<=UE2) + /// + [StructLayout(LayoutKind.Sequential, Pack = 8)] + public struct UVector2D : IUnrealSerializableClass, IUnrealAtomicStruct + { + public float X, Y; + + public UVector2D(float x, float y) + { + X = x; + Y = y; + } + + public void Deserialize(IUnrealStream stream) + { + stream.Read(out X); + stream.Read(out Y); + } + + public void Serialize(IUnrealStream stream) + { + stream.Write(X); + stream.Write(Y); + } + + public static unsafe explicit operator Vector2(UVector2D u) + { + return *(Vector2*)&u; + } + + public static unsafe explicit operator UVector2D(Vector2 m) + { + return *(UVector2D*)&m; + } + + public override int GetHashCode() + { + return ((Vector2)this).GetHashCode(); + } + } +} \ No newline at end of file diff --git a/src/Core/Types/UVector4.cs b/src/Core/Types/UVector4.cs new file mode 100644 index 00000000..4d9854c0 --- /dev/null +++ b/src/Core/Types/UVector4.cs @@ -0,0 +1,53 @@ +using System.Numerics; +using System.Runtime.InteropServices; + +namespace UELib.Core +{ + /// + /// Implements FVector4/UObject.Vector4 + /// + [StructLayout(LayoutKind.Sequential, Pack = 8)] + public struct UVector4 : IUnrealSerializableClass, IUnrealAtomicStruct + { + public float X, Y, Z, W; + + public UVector4(float x, float y, float z, float w) + { + X = x; + Y = y; + Z = z; + W = w; + } + + public void Deserialize(IUnrealStream stream) + { + stream.Read(out X); + stream.Read(out Y); + stream.Read(out Z); + stream.Read(out W); + } + + public void Serialize(IUnrealStream stream) + { + stream.Write(X); + stream.Write(Y); + stream.Write(Z); + stream.Write(W); + } + + public static unsafe explicit operator Vector4(UVector4 u) + { + return *(Vector4*)&u; + } + + public static unsafe explicit operator UVector4(Vector4 m) + { + return *(UVector4*)&m; + } + + public override int GetHashCode() + { + return ((Vector4)this).GetHashCode(); + } + } +} \ No newline at end of file diff --git a/src/Core/UStateFrame.cs b/src/Core/UStateFrame.cs index 50ef56e0..7c8678a6 100644 --- a/src/Core/UStateFrame.cs +++ b/src/Core/UStateFrame.cs @@ -30,6 +30,11 @@ public void Deserialize(IUnrealStream stream) LatentAction = stream.Version < VLatentActionReduced ? stream.ReadUInt32() : stream.ReadUInt16(); + if (stream.Package.Build == UnrealPackage.GameBuild.BuildName.DNF && + stream.LicenseeVersion >= 25) + { + uint dnfUInt32 = stream.ReadUInt32(); + } if (stream.Version >= VStateStack) stream.ReadArray(out StateStack); if (Node != null) Offset = stream.ReadIndex(); } diff --git a/src/Decompiler/app.config b/src/Decompiler/app.config new file mode 100644 index 00000000..1696df66 --- /dev/null +++ b/src/Decompiler/app.config @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/src/Eliot.UELib.csproj b/src/Eliot.UELib.csproj index 5fdb3a3b..4b414b9a 100644 --- a/src/Eliot.UELib.csproj +++ b/src/Eliot.UELib.csproj @@ -1,27 +1,9 @@ - - + - Debug - AnyCPU - 8.0.30703 - 2.0 - {7E45273F-434F-42FA-8269-0E760AEB41E1} + DECOMPILE;BINARYMETADATA;Forms;UE1;UE2;UE3;UE4;VENGEANCE;SWAT4;UNREAL2;INFINITYBLADE;BORDERLANDS2;GOW2;APB;SPECIALFORCE2;XIII;SINGULARITY;THIEF_DS;DEUSEX_IW;BORDERLANDS;MIRRORSEDGE;BIOSHOCK;HAWKEN;UT;DISHONORED;REMEMBERME;ALPHAPROTOCOL;VANGUARD;TERA;MKKE;TRANSFORMERS;XCOM2;DD2;DCUO;AA2;SPELLBORN;BATMAN;MOH;ROCKETLEAGUE;DNF;LSGAME;UNDYING;HP;DEVASTATION;SPLINTERCELL + net48 Library - Properties UELib - Eliot.UELib - v4.8 - 7.3 - 512 - - - - - - - - - publish\ true Disk false @@ -37,233 +19,55 @@ false true OnOutputUpdated - + false + true - true - full - false - bin\Debug\ - TRACE;DEBUG;DECOMPILE BINARYMETADATA Forms UE1 UE2 UE3 VENGEANCE SWAT4 UNREAL2 INFINITYBLADE BORDERLANDS2 GOW2 DEBUG_TEST APB SPECIALFORCE2 XIII SINGULARITY THIEF_DS DEUSEX_IW BORDERLANDS MIRRORSEDGE BIOSHOCK HAWKEN UT DISHONORED REMEMBERME ALPHAPROTOCOL VANGUARD TERA MKKE TRANSFORMERS XCOM2 DCUO AA2 SPELLBORN BATMAN MOH ROCKETLEAGUE - prompt + $(DefineConstants);TRACE;DEBUG; 0 - AnyCPU - - Auto - - - true + false GlobalizationRules.ruleset - false none - true - bin\Release\ - TRACE;DECOMPILE BINARYMETADATA Forms UE1 UE2 UE3 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 DCUO AA2 SPELLBORN BATMAN - prompt - 4 - - + $(DefineConstants);TRACE; true - true - false - - true - bin\Debug\ - TRACE;DEBUG;DECOMPILE BINARYMETADATA Forms DEBUG_TEST DEBUG_TOKENPOSITIONS DEBUG_HIDDENTOKENS DEBUG_NESTS DEBUG_FUNCTIONINFO UE1 UE2 UE3 VENGEANCE SWAT4 UNREAL2 INFINITYBLADE BORDERLANDS2 GOW2 DEBUG_TEST APB SPECIALFORCE2 XIII SINGULARITY THIEF_DS DEUSEX_IW BORDERLANDS MIRRORSEDGE BIOSHOCK HAWKEN UT DISHONORED REMEMBERME ALPHAPROTOCOL VANGUARD TERA MKKE TRANSFORMERS XCOM2 DCUO AA2 SPELLBORN BATMAN MOH - - - false - 0 - full - AnyCPU - true - false - - - bin\Publish\ - Forms DECOMPILE BINARYMETADATA UE1 UE2 UE3 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 DCUO AA2 SPELLBORN BATMAN - true - AnyCPU - - - false - true - false - - - true - bin\Test\ - TRACE;DEBUG;DECOMPILE UE1 UE2 UE3 VENGEANCE SWAT4 UNREAL2 INFINITYBLADE BORDERLANDS2 GOW2 DEBUG_TEST APB SPECIALFORCE2 XIII SINGULARITY THIEF_DS DEUSEX_IW BORDERLANDS MIRRORSEDGE BIOSHOCK HAWKEN UT DISHONORED REMEMBERME ALPHAPROTOCOL VANGUARD TERA MKKE TRANSFORMERS XCOM2 DCUO AA2 SPELLBORN BATMAN - true - 0 - full - AnyCPU - 7.3 - prompt - GlobalizationRules.ruleset - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + False Microsoft .NET Framework 4 %28x86 and x64%29 true - - False - .NET Framework 3.5 SP1 Client Profile - false - - - False - .NET Framework 3.5 SP1 - false - - - False - Windows Installer 3.1 - true - - - - - - - - - + + + + + + + + + True + \ + + + True + \ + + + + + + 6.0.0 + + + + + + Eliot.UELib.nuspec $(NuspecProperties);configuration=$(Configuration) @@ -277,5 +81,18 @@ $(NuspecProperties);PackageTags=$(PackageTags) $(NuspecProperties);RepositoryType=$(RepositoryType) $(NuspecProperties);RepositoryUrl=$(RepositoryUrl) + UnrealScript decompiler library for Unreal package files (.upk, .u, .uasset; etc), with support for Unreal Engine 1, 2, and 3. + UnrealEngine;UnrealScript;Decompiler;UPK;Exporter;Bytecode + 2009-2024 Eliot van Uytfanghe. All rights reserved. + EliotVU + https://github.com/EliotVU/Unreal-Library + README.MD + https://github.com/EliotVU/Unreal-Library + $(VersionPrefix)1.4.0 + en + LICENSE + True + True + latest-recommended \ No newline at end of file diff --git a/src/Eliot.UELib.nuspec b/src/Eliot.UELib.nuspec index 745b0118..c0646cc9 100644 --- a/src/Eliot.UELib.nuspec +++ b/src/Eliot.UELib.nuspec @@ -13,7 +13,7 @@ docs\README.md docs\CHANGELOG.md $copyright$ - UnrealEngine UnrealScript Decompiler UPK Explorer Viewer Exporter Bytecode + $tags$ diff --git a/src/Eliot.UELib.sln.DotSettings b/src/Eliot.UELib.sln.DotSettings index 01f43a3d..b22814ce 100644 --- a/src/Eliot.UELib.sln.DotSettings +++ b/src/Eliot.UELib.sln.DotSettings @@ -1,4 +1,6 @@  True True + True + True True \ No newline at end of file diff --git a/src/Engine/Classes/AActor.cs b/src/Engine/Classes/AActor.cs new file mode 100644 index 00000000..f39ccb05 --- /dev/null +++ b/src/Engine/Classes/AActor.cs @@ -0,0 +1,18 @@ +using UELib.Core; +using UELib.ObjectModel.Annotations; + +namespace UELib.Engine +{ + /// + /// Implements AActor/Engine.Actor + /// + [Output("Actor")] + [UnrealRegisterClass] + public class AActor : UObject + { + public AActor() + { + ShouldDeserializeOnDemand = true; + } + } +} diff --git a/src/Engine/Classes/ABrush.cs b/src/Engine/Classes/ABrush.cs new file mode 100644 index 00000000..5bc4c3a6 --- /dev/null +++ b/src/Engine/Classes/ABrush.cs @@ -0,0 +1,13 @@ +using UELib.ObjectModel.Annotations; + +namespace UELib.Engine +{ + /// + /// Implements ABrush/Engine.Brush + /// + [Output("Brush")] + [UnrealRegisterClass] + public class ABrush : AActor + { + } +} \ No newline at end of file diff --git a/src/Engine/Classes/UActorComponent.cs b/src/Engine/Classes/UActorComponent.cs new file mode 100644 index 00000000..66ad1570 --- /dev/null +++ b/src/Engine/Classes/UActorComponent.cs @@ -0,0 +1,23 @@ +using UELib.Branch; +using UELib.Core; + +namespace UELib.Engine +{ + /// + /// Implements UActorComponent/Engine.ActorComponent + /// + [UnrealRegisterClass] + [BuildGeneration(BuildGeneration.UE3)] + public class UActorComponent : UComponent + { + } + + /// + /// Implements UAudioComponent/Engine.AudioComponent + /// + [UnrealRegisterClass] + [BuildGeneration(BuildGeneration.UE3)] + public class UAudioComponent : UActorComponent + { + } +} diff --git a/src/Engine/Classes/UBitmapMaterial.cs b/src/Engine/Classes/UBitmapMaterial.cs index 4ac94cf5..38871e19 100644 --- a/src/Engine/Classes/UBitmapMaterial.cs +++ b/src/Engine/Classes/UBitmapMaterial.cs @@ -1,10 +1,15 @@ -using System; +using UELib.Annotations; +using UELib.Branch; using UELib.Core; namespace UELib.Engine { + /// + /// Implements UBitmapMaterial/Engine.BitmapMaterial + /// [UnrealRegisterClass] - public class UBitmapMaterial : UObject + [BuildGenerationRange(BuildGeneration.UE1, BuildGeneration.UE2_5)] + public class UBitmapMaterial : URenderedMaterial { // UE2 implementation public enum TextureFormat @@ -24,20 +29,35 @@ public enum TextureFormat }; public TextureFormat Format; + [CanBeNull] public UPalette Palette; public UBitmapMaterial() { ShouldDeserializeOnDemand = true; } - + protected override void Deserialize() { base.Deserialize(); + // HACK: This will do until we have a proper property linking setup. + var formatProperty = Properties.Find("Format"); if (formatProperty != null) { - Enum.TryParse(formatProperty.Value, out Format); + _Buffer.StartPeek(formatProperty._PropertyValuePosition); + _Buffer.Read(out byte index); + _Buffer.EndPeek(); + + Format = (TextureFormat)index; + } + + var paletteProperty = Properties.Find("Palette"); + if (paletteProperty != null) + { + _Buffer.StartPeek(paletteProperty._PropertyValuePosition); + _Buffer.Read(out Palette); + _Buffer.EndPeek(); } } } diff --git a/src/Engine/Classes/UBrushComponent.cs b/src/Engine/Classes/UBrushComponent.cs new file mode 100644 index 00000000..b5ceb4cb --- /dev/null +++ b/src/Engine/Classes/UBrushComponent.cs @@ -0,0 +1,15 @@ +using UELib.Branch; + +namespace UELib.Engine +{ + /// + /// Implements UBrushComponent/Engine.BrushComponent + /// + [UnrealRegisterClass] + [BuildGeneration(BuildGeneration.UE3)] + public class UBrushComponent : UPrimitiveComponent + { + // TODO: CachedPhysBrushData + protected override void Deserialize() => base.Deserialize(); + } +} diff --git a/src/Engine/Classes/UDecalComponent.cs b/src/Engine/Classes/UDecalComponent.cs new file mode 100644 index 00000000..eac251bb --- /dev/null +++ b/src/Engine/Classes/UDecalComponent.cs @@ -0,0 +1,15 @@ +using UELib.Branch; + +namespace UELib.Engine +{ + /// + /// Implements UDecalComponent/Engine.DecalComponent + /// + [UnrealRegisterClass] + [BuildGeneration(BuildGeneration.UE3)] + public class UDecalComponent : UPrimitiveComponent + { + // TODO: StaticReceivers + protected override void Deserialize() => base.Deserialize(); + } +} diff --git a/src/Engine/Classes/UFont.cs b/src/Engine/Classes/UFont.cs index 74a24690..8121e92e 100644 --- a/src/Engine/Classes/UFont.cs +++ b/src/Engine/Classes/UFont.cs @@ -1,60 +1,165 @@ -using System; +using UELib.Branch; using UELib.Core; namespace UELib.Engine { /// - /// Unreal Font. + /// Implements UFont/Engine.Font /// [UnrealRegisterClass] - public class UFont : UObject, IUnrealViewable + public class UFont : UObject { + public UArray Characters; + public UArray Textures; + public int Kerning; + public UMap CharRemap; + public bool IsRemapped; + public UFont() { ShouldDeserializeOnDemand = true; } - - private struct FontCharacter : IUnrealSerializableClass + + protected override void Deserialize() { - private int _StartU; - private int _StartV; - private int _USize; - private int _VSize; - byte _TextureIndex; + base.Deserialize(); - public void Serialize(IUnrealStream stream) + // UT2004(v128) < LicenseeVersion 29, but the correct version to test against is 122 + if (_Buffer.Version < (uint)PackageObjectLegacyVersion.FontPagesDisplaced) + { + _Buffer.Read(out UArray pages); + Record(nameof(pages), pages); + + _Buffer.Read(out int charactersPerPage); + Record(nameof(charactersPerPage), charactersPerPage); + + Characters = new UArray(); + Textures = new UArray(); + for (int i = 0; i < pages.Count; i++) + { + Textures.Add(pages[i].Texture); + foreach (var c in pages[i].Characters) + { + var character = c; + character.TextureIndex = (byte)i; + Characters.Add(character); + } + } + + if (pages.Count == 0 && charactersPerPage == 0) + { + _Buffer.Read(out string fontName); + Record(nameof(fontName), fontName); + + _Buffer.Read(out int fontHeight); + Record(nameof(fontHeight), fontHeight); + } + } + else if (_Buffer.Version < (uint)PackageObjectLegacyVersion.CleanupFonts) { - throw new NotImplementedException(); + _Buffer.Read(out Characters); + Record(nameof(Characters), Characters); + + _Buffer.Read(out Textures); + Record(nameof(Textures), Textures); } - public void Deserialize(IUnrealStream stream) + // No version check in UT2003 (v120) and UT2004 (v128) + if (_Buffer.Version >= (uint)PackageObjectLegacyVersion.KerningAddedToUFont && + _Buffer.Version < (uint)PackageObjectLegacyVersion.CleanupFonts) + { + _Buffer.Read(out Kerning); + Record(nameof(Kerning), Kerning); + } + + if (_Buffer.Version >= (uint)PackageObjectLegacyVersion.CharRemapAddedToUFont) { - _StartU = stream.ReadInt32(); - _StartV = stream.ReadInt32(); + _Buffer.Read(out CharRemap); + Record(nameof(CharRemap), CharRemap); + + if (_Buffer.Version >= (uint)PackageObjectLegacyVersion.CleanupFonts) + { + return; + } - _USize = stream.ReadInt32(); - _VSize = stream.ReadInt32(); + _Buffer.Read(out IsRemapped); + Record(nameof(IsRemapped), IsRemapped); - _TextureIndex = stream.ReadByte(); + //if (_Buffer.Package.Build == UnrealPackage.GameBuild.BuildName.Unreal2) + //{ + // _Buffer.Read(out int xPad); + // _Buffer.Read(out int yPad); + //} } - }; + } - private UArray _Characters; + public struct FontPage : IUnrealSerializableClass + { + public UTexture Texture; + public UArray Characters; - protected override void Deserialize() + public void Deserialize(IUnrealStream stream) + { + stream.Read(out Texture); + stream.Read(out Characters); + } + + public void Serialize(IUnrealStream stream) + { + stream.Write(Texture); + stream.Write(ref Characters); + } + } + + public struct FontCharacter : IUnrealSerializableClass { - base.Deserialize(); + public int StartU; + public int StartV; + public int USize; + public int VSize; + public byte TextureIndex; + public int VerticalOffset; - _Buffer.ReadArray(out _Characters); + public void Deserialize(IUnrealStream stream) + { + stream.Read(out StartU); + stream.Read(out StartV); + stream.Read(out USize); + stream.Read(out VSize); + + if (stream.Version >= (uint)PackageObjectLegacyVersion.FontPagesDisplaced) + { + stream.Read(out TextureIndex); + } + + if (stream.Version >= (uint)PackageObjectLegacyVersion.VerticalOffsetAddedToUFont) + { + stream.Read(out VerticalOffset); + } + } - // Textures + public void Serialize(IUnrealStream stream) + { + stream.Write(StartU); + stream.Write(StartV); + stream.Write(USize); + stream.Write(VSize); - // Kerning - _Buffer.ReadInt32(); + if (stream.Version >= (uint)PackageObjectLegacyVersion.FontPagesDisplaced) + { + stream.Write(TextureIndex); + } - // Remap + if (stream.Version >= (uint)PackageObjectLegacyVersion.VerticalOffsetAddedToUFont) + { + stream.Write(VerticalOffset); + } + } - _Buffer.UR.ReadBoolean(); + public override int GetHashCode() + { + return StartU ^ StartV ^ USize ^ VSize; + } } } -} \ No newline at end of file +} diff --git a/src/Engine/Classes/ULightComponent.cs b/src/Engine/Classes/ULightComponent.cs new file mode 100644 index 00000000..ae35004e --- /dev/null +++ b/src/Engine/Classes/ULightComponent.cs @@ -0,0 +1,15 @@ +using UELib.Branch; + +namespace UELib.Engine +{ + /// + /// Implements ULightComponent/Engine.LightComponent + /// + [UnrealRegisterClass] + [BuildGeneration(BuildGeneration.UE3)] + public class ULightComponent : UActorComponent + { + // TODO: InclusionConvexVolumes, ExclusionConvexVolumes + protected override void Deserialize() => base.Deserialize(); + } +} diff --git a/src/Engine/Classes/UMaterial.cs b/src/Engine/Classes/UMaterial.cs new file mode 100644 index 00000000..dde959e9 --- /dev/null +++ b/src/Engine/Classes/UMaterial.cs @@ -0,0 +1,21 @@ +namespace UELib.Engine +{ + /// + /// Implements UMaterial/Engine.Material + /// + [UnrealRegisterClass] + public class UMaterial : UMaterialInterface + { + protected override void Deserialize() + { + base.Deserialize(); +#if UNREAL2 + if (Package.Build == UnrealPackage.GameBuild.BuildName.Unreal2) + { + _Buffer.Read(out byte textureType); + Record(nameof(textureType), textureType); + } +#endif + } + } +} \ No newline at end of file diff --git a/src/Engine/Classes/UMaterialInterface.cs b/src/Engine/Classes/UMaterialInterface.cs new file mode 100644 index 00000000..76ca4492 --- /dev/null +++ b/src/Engine/Classes/UMaterialInterface.cs @@ -0,0 +1,13 @@ +using UELib.Branch; + +namespace UELib.Engine +{ + /// + /// Implements UMaterialInterface/Engine.MaterialInterface + /// + [UnrealRegisterClass] + [BuildGeneration(BuildGeneration.UE3)] + public class UMaterialInterface : USurface + { + } +} \ No newline at end of file diff --git a/src/Engine/Classes/UMeshComponent.cs b/src/Engine/Classes/UMeshComponent.cs new file mode 100644 index 00000000..3c5dd615 --- /dev/null +++ b/src/Engine/Classes/UMeshComponent.cs @@ -0,0 +1,31 @@ +using UELib.Branch; + +namespace UELib.Engine +{ + /// + /// Implements UMeshComponent/Engine.MeshComponent + /// + [UnrealRegisterClass] + [BuildGeneration(BuildGeneration.UE3)] + public class UMeshComponent : UPrimitiveComponent + { + } + + /// + /// Implements USkeletalMeshComponent/Engine.SkeletalMeshComponent + /// + [UnrealRegisterClass] + [BuildGeneration(BuildGeneration.UE3)] + public class USkeletalMeshComponent : UMeshComponent + { + } + + /// + /// Implements UStaticMeshComponent/Engine.StaticMeshComponent + /// + [UnrealRegisterClass] + [BuildGeneration(BuildGeneration.UE3)] + public class UStaticMeshComponent : UModelComponent + { + } +} diff --git a/src/Engine/Classes/UModel.cs b/src/Engine/Classes/UModel.cs index 9221b252..f83a14e4 100644 --- a/src/Engine/Classes/UModel.cs +++ b/src/Engine/Classes/UModel.cs @@ -1,13 +1,26 @@ -using UELib.Core; +using UELib.Engine; +using UELib.ObjectModel.Annotations; -namespace UELib.Engine +namespace UELib.Core { + /// + /// Implements UModel/Engine.Model + /// + [Output("Brush")] [UnrealRegisterClass] - public class UModel : UObject + public class UModel : UPrimitive { + [Output] + public UPolys Polys; + public UModel() { ShouldDeserializeOnDemand = true; } + + protected override void Deserialize() + { + base.Deserialize(); + } } -} \ No newline at end of file +} diff --git a/src/Engine/Classes/UModelComponent.cs b/src/Engine/Classes/UModelComponent.cs new file mode 100644 index 00000000..fcf2a70c --- /dev/null +++ b/src/Engine/Classes/UModelComponent.cs @@ -0,0 +1,29 @@ +using UELib.Branch; +using UELib.Core; + +namespace UELib.Engine +{ + /// + /// Implements UModelComponent/Engine.ModelComponent + /// + [UnrealRegisterClass] + [BuildGeneration(BuildGeneration.UE3)] + public class UModelComponent : UPrimitiveComponent + { + public UObject Model; + public int ZoneIndex; + + protected override void Deserialize() + { + base.Deserialize(); + + _Buffer.Read(out Model); + Record(nameof(Model), Model); + + _Buffer.Read(out ZoneIndex); + Record(nameof(ZoneIndex), ZoneIndex); + + // TODO: Elements (structure not implemented), ComponentIndex, Nodes + } + } +} diff --git a/src/Engine/Classes/UMultiFont.cs b/src/Engine/Classes/UMultiFont.cs new file mode 100644 index 00000000..8f6145a8 --- /dev/null +++ b/src/Engine/Classes/UMultiFont.cs @@ -0,0 +1,26 @@ +using UELib.Branch; +using UELib.Core; + +namespace UELib.Engine +{ + /// + /// Implements UMultiFont/Engine.MultiFont + /// + [UnrealRegisterClass] + [BuildGeneration(BuildGeneration.UE3)] + public class UMultiFont : UFont + { + public UArray ResolutionTestTable; + + protected override void Deserialize() + { + base.Deserialize(); + + if (_Buffer.Version < (uint)PackageObjectLegacyVersion.CleanupFonts) + { + _Buffer.ReadArray(out ResolutionTestTable); + Record(nameof(ResolutionTestTable), ResolutionTestTable); + } + } + } +} diff --git a/src/Engine/Classes/UPalette.cs b/src/Engine/Classes/UPalette.cs index 3e307158..b7babffa 100644 --- a/src/Engine/Classes/UPalette.cs +++ b/src/Engine/Classes/UPalette.cs @@ -1,15 +1,23 @@ using System.Diagnostics; +using UELib.Branch; using UELib.Core; -using UELib.Core.Types; namespace UELib.Engine { + /// + /// Implements UPalette/Engine.Palette + /// [UnrealRegisterClass] public class UPalette : UObject, IUnrealViewable { - // This could be a lot faster with a fixed array, but it's not a significant class of interest. + /// + /// No alpha was serialized for packages of version 65 or less. + /// public UArray Colors; + [Build(UnrealPackage.GameBuild.BuildName.Undying)] + public bool HasAlphaChannel; + public UPalette() { ShouldDeserializeOnDemand = true; @@ -19,9 +27,19 @@ protected override void Deserialize() { base.Deserialize(); + // This could be a lot faster with a fixed array, but it's not a significant class of interest. int count = _Buffer.ReadIndex(); Debug.Assert(count == 256); - _Buffer.ReadMarshalArray(out Colors, count); + _Buffer.ReadArray(out Colors, count); + Record(nameof(Colors), Colors); +#if UNDYING + if (Package.Build == UnrealPackage.GameBuild.BuildName.Undying && + _Buffer.Version >= 75) + { + _Buffer.Read(out HasAlphaChannel); // v28 + Record(nameof(HasAlphaChannel), HasAlphaChannel); + } +#endif } } -} \ No newline at end of file +} diff --git a/src/Engine/Classes/UPolys.cs b/src/Engine/Classes/UPolys.cs new file mode 100644 index 00000000..8f7c9df7 --- /dev/null +++ b/src/Engine/Classes/UPolys.cs @@ -0,0 +1,62 @@ +using UELib.Annotations; +using UELib.Branch; +using UELib.Core; +using UELib.Flags; +using UELib.ObjectModel.Annotations; + +namespace UELib.Engine +{ + /// + /// Implements UPolys/Engine.Polys + /// + [Output("PolyList")] + [UnrealRegisterClass] + public class UPolys : UObject + { + [CanBeNull] public UObject ElementOwner; + + [Output] + public UArray Element; + + public UPolys() + { + ShouldDeserializeOnDemand = true; + } + + protected override void Deserialize() + { + base.Deserialize(); + + // Faster serialization for cooked packages, no don't have to check for the package's version here. + if (Package.Summary.PackageFlags.HasFlag(PackageFlag.Cooked)) + { + ElementOwner = _Buffer.ReadObject(); + Record(nameof(ElementOwner), ElementOwner); + + _Buffer.ReadArray(out Element); + Record(nameof(Element), Element); + return; + } + + int num, max; + + num = _Buffer.ReadInt32(); + Record(nameof(num), num); + max = _Buffer.ReadInt32(); + Record(nameof(max), max); + + if (_Buffer.Version >= (uint)PackageObjectLegacyVersion.ElementOwnerAddedToUPolys) + { + ElementOwner = _Buffer.ReadObject(); + Record(nameof(ElementOwner), ElementOwner); + } + + Element = new UArray(num); + if (num > 0) + { + _Buffer.ReadArray(out Element, num); + Record(nameof(Element), Element); + } + } + } +} \ No newline at end of file diff --git a/src/Engine/Classes/UPrimitive.cs b/src/Engine/Classes/UPrimitive.cs new file mode 100644 index 00000000..e1352122 --- /dev/null +++ b/src/Engine/Classes/UPrimitive.cs @@ -0,0 +1,23 @@ +namespace UELib.Core +{ + /// + /// Implements UPrimitive/Engine.Primitive + /// + [UnrealRegisterClass] + public class UPrimitive : UObject + { + public UBox BoundingBox; + public USphere BoundingSphere; + + protected override void Deserialize() + { + base.Deserialize(); + + _Buffer.ReadStruct(out BoundingBox); + Record(nameof(BoundingBox), BoundingBox); + + _Buffer.ReadStruct(out BoundingSphere); + Record(nameof(BoundingSphere), BoundingSphere); + } + } +} diff --git a/src/Engine/Classes/UPrimitiveComponent.cs b/src/Engine/Classes/UPrimitiveComponent.cs new file mode 100644 index 00000000..98347cc1 --- /dev/null +++ b/src/Engine/Classes/UPrimitiveComponent.cs @@ -0,0 +1,166 @@ +using UELib.Branch; +using UELib.Core; + +namespace UELib.Engine +{ + /// + /// Implements UPrimitiveComponent/Engine.PrimitiveComponent + /// + [UnrealRegisterClass] + [BuildGeneration(BuildGeneration.UE3)] + public class UPrimitiveComponent : UActorComponent + { + protected override void Deserialize() + { + base.Deserialize(); + + if (_Buffer.Version < (uint)PackageObjectLegacyVersion.ComponentGuidDeprecated) + { + _Buffer.ReadStruct(out UGuid guid); + } + } + } + + /// + /// Implements USpriteComponent/Engine.SpriteComponent + /// + [UnrealRegisterClass] + [BuildGeneration(BuildGeneration.UE3)] + public class USpriteComponent : UPrimitiveComponent + { + } + + /// + /// Implements UCylinderComponent/Engine.CylinderComponent + /// + [UnrealRegisterClass] + [BuildGeneration(BuildGeneration.UE3)] + public class UCylinderComponent : UPrimitiveComponent + { + } + + /// + /// Implements UArrowComponent/Engine.ArrowComponent + /// + [UnrealRegisterClass] + [BuildGeneration(BuildGeneration.UE3)] + public class UArrowComponent : UPrimitiveComponent + { + } + + /// + /// Implements UDrawSphereComponent/Engine.DrawSphereComponent + /// + [UnrealRegisterClass] + [BuildGeneration(BuildGeneration.UE3)] + public class UDrawSphereComponent : UPrimitiveComponent + { + } + + /// + /// Implements UDrawCylinderComponent/Engine.DrawCylinderComponent + /// + [UnrealRegisterClass] + [BuildGeneration(BuildGeneration.UE3)] + public class UDrawCylinderComponent : UPrimitiveComponent + { + } + + /// + /// Implements UDrawBoxComponent/Engine.DrawBoxComponent + /// + [UnrealRegisterClass] + [BuildGeneration(BuildGeneration.UE3)] + public class UDrawBoxComponent : UPrimitiveComponent + { + } + + /// + /// Implements UDrawCapsuleComponent/Engine.DrawCapsuleComponent + /// + [UnrealRegisterClass] + [BuildGeneration(BuildGeneration.UE3)] + public class UDrawCapsuleComponent : UPrimitiveComponent + { + } + + /// + /// Implements UDrawFrustumComponent/Engine.DrawFrustumComponent + /// + [UnrealRegisterClass] + [BuildGeneration(BuildGeneration.UE3)] + public class UDrawFrustumComponent : UPrimitiveComponent + { + } + + /// + /// Implements UDrawQuadComponent/Engine.DrawQuadComponent + /// + [UnrealRegisterClass] + [BuildGeneration(BuildGeneration.UE3)] + public class UDrawQuadComponent : UPrimitiveComponent + { + } + + /// + /// Implements UDrawSoundRadiusComponent/Engine.DrawSoundRadiusComponent + /// + [UnrealRegisterClass] + [BuildGeneration(BuildGeneration.UE3)] + public class UDrawSoundRadiusComponent : UDrawSphereComponent + { + } + + /// + /// Implements ULightRadiusComponent/Engine.LightRadiusComponent + /// + [UnrealRegisterClass] + [BuildGeneration(BuildGeneration.UE3)] + public class ULightRadiusComponent : UDrawSphereComponent + { + } + + /// + /// Implements UCameraConeComponent/Engine.CameraConeComponent + /// + [UnrealRegisterClass] + [BuildGeneration(BuildGeneration.UE3)] + public class UCameraConeComponent : UPrimitiveComponent + { + } + + /// + /// Implements UCollisionComponent/Engine.CollisionComponent + /// + [UnrealRegisterClass] + [BuildGeneration(BuildGeneration.UE3)] + public class UCollisionComponent : UCylinderComponent + { + } + + /// + /// Implements UModelComponent/Engine.LineBatchComponent + /// + [UnrealRegisterClass] + [BuildGeneration(BuildGeneration.UE3)] + public class ULineBatchComponent : UPrimitiveComponent + { + } + + /// + /// Implements UParticleSystemComponent/Engine.ParticleSystemComponent + /// + [UnrealRegisterClass] + [BuildGeneration(BuildGeneration.UE3)] + public class UParticleSystemComponent : UPrimitiveComponent + { + } + /// + /// Implements UCascadeParticleSystemComponent/Engine.CascadeParticleSystemComponent + /// + [UnrealRegisterClass] + [BuildGeneration(BuildGeneration.UE3)] + public class UCascadeParticleSystemComponent : UParticleSystemComponent + { + } +} diff --git a/src/Engine/Classes/UProcBuildingRuleset.cs b/src/Engine/Classes/UProcBuildingRuleset.cs new file mode 100644 index 00000000..87771f86 --- /dev/null +++ b/src/Engine/Classes/UProcBuildingRuleset.cs @@ -0,0 +1,14 @@ +using UELib.Branch; +using UELib.Core; + +namespace UELib.Engine +{ + /// + /// Implements UProcBuildingRuleset/Engine.ProcBuildingRuleset + /// + [UnrealRegisterClass] + [BuildGeneration(BuildGeneration.UE3)] + public class UProcBuildingRuleset : UObject + { + } +} \ No newline at end of file diff --git a/src/Engine/Classes/URenderedMaterial.cs b/src/Engine/Classes/URenderedMaterial.cs new file mode 100644 index 00000000..bfee8cf7 --- /dev/null +++ b/src/Engine/Classes/URenderedMaterial.cs @@ -0,0 +1,13 @@ +using UELib.Branch; + +namespace UELib.Engine +{ + /// + /// Implements URenderedMaterial/Engine.RenderedMaterial + /// + [UnrealRegisterClass] + [BuildGenerationRange(BuildGeneration.UE1, BuildGeneration.UE2_5)] + public class URenderedMaterial : UMaterial + { + } +} \ No newline at end of file diff --git a/src/Engine/Classes/UShadowMapTexture2D.cs b/src/Engine/Classes/UShadowMapTexture2D.cs new file mode 100644 index 00000000..6a05ff5b --- /dev/null +++ b/src/Engine/Classes/UShadowMapTexture2D.cs @@ -0,0 +1,13 @@ +using UELib.Branch; + +namespace UELib.Engine +{ + /// + /// Implements UShadowMapTexture2D/Engine.ShadowMapTexture2D + /// + [UnrealRegisterClass] + [BuildGeneration(BuildGeneration.UE3)] + public class UShadowMapTexture2D : UTexture2D + { + } +} diff --git a/src/Engine/Classes/USound.cs b/src/Engine/Classes/USound.cs index e9a380f9..f750f3d8 100644 --- a/src/Engine/Classes/USound.cs +++ b/src/Engine/Classes/USound.cs @@ -1,47 +1,44 @@ using System.Collections.Generic; -using UELib.Core; +using UELib.Branch; -namespace UELib.Engine +namespace UELib.Core { + /// + /// Implements USound/Engine.Sound + /// [UnrealRegisterClass] - public class USound : UObject, IUnrealViewable, IUnrealExportable + [BuildGenerationRange(BuildGeneration.UE1, BuildGeneration.UE2_5)] // Re-branded in UE3 to USoundNodeWave + public class USound : UObject, IUnrealExportable { #region Serialized Members public UName FileType; + /// /// The likely hood that this sound will be selected from an array of sounds, see "USoundGroup". /// Null if not serialized. /// public float? Likelihood; - public byte[] Data; - #endregion + public UBulkData RawData; - private const string WAVExtension = "wav"; + #endregion - public IEnumerable ExportableExtensions => new[] { WAVExtension }; + public IEnumerable ExportableExtensions => new List { FileType }; public USound() { ShouldDeserializeOnDemand = true; } - public bool CompatableExport() + public bool CanExport() { - return Package.Version >= 61 && Package.Version <= 129 - && FileType != null && FileType.ToString().ToLower() == WAVExtension && - Data != null; + return RawData.StorageSize != -1; } public void SerializeExport(string desiredExportExtension, System.IO.Stream exportStream) { - switch (desiredExportExtension) - { - case WAVExtension: - exportStream.Write(Data, 0, Data.Length); - break; - } + exportStream.Write(RawData.ElementData, 0, RawData.ElementData.Length); } protected override void Deserialize() @@ -50,25 +47,115 @@ protected override void Deserialize() FileType = _Buffer.ReadNameReference(); Record(nameof(FileType), FileType); +#if HP + if (Package.Build == BuildGeneration.HP) + { + _Buffer.Read(out uint flags); + Record(nameof(flags), flags); + _Buffer.Read(out float duration); + Record(nameof(duration), duration); + + if (_Buffer.Version >= 77) + { + _Buffer.Read(out int numSamples); + Record(nameof(numSamples), numSamples); + } + + if (_Buffer.Version >= 78) + { + _Buffer.Read(out int bitsPerSample); + Record(nameof(bitsPerSample), bitsPerSample); + _Buffer.Read(out int numChannels); + Record(nameof(numChannels), numChannels); + } + + if (_Buffer.Version >= 79) + { + _Buffer.Read(out int sampleRate); + Record(nameof(sampleRate), sampleRate); + } + } +#endif +#if UNDYING + if (Package.Build == UnrealPackage.GameBuild.BuildName.Undying) + { + if (_Buffer.Version >= 77) + { + _Buffer.Read(out int v7c); + Record(nameof(v7c), v7c); + _Buffer.Read(out int v74); + Record(nameof(v74), v74); + _Buffer.Read(out float v6c); + Record(nameof(v6c), v6c); + } + + if (_Buffer.Version >= 79) + { + _Buffer.Read(out uint v78); + Record(nameof(v78), v78); + _Buffer.Read(out float v68); // Radius? + Record(nameof(v68), v68); + } + + if (_Buffer.Version >= 80) + { + _Buffer.Read(out float v80); + Record(nameof(v80), v80); + } + + if (_Buffer.Version >= 82) + { + _Buffer.Read(out int v84); + Record(nameof(v84), v84); + } + } +#endif +#if DEVASTATION + if (Package.Build == UnrealPackage.GameBuild.BuildName.Devastation + && _Buffer.LicenseeVersion >= 6 + && _Buffer.LicenseeVersion < 8) + { + _Buffer.Read(out int l14); + Record(nameof(l14), l14); + } +#endif #if UT - if ((Package.Build == UnrealPackage.GameBuild.BuildName.UT2004 - || Package.Build == UnrealPackage.GameBuild.BuildName.UT2003) /*&& Package.LicenseeVersion >= 2*/) + if ((Package.Build == UnrealPackage.GameBuild.BuildName.UT2004 || + Package.Build == UnrealPackage.GameBuild.BuildName.UT2003) + && _Buffer.LicenseeVersion >= 2) { Likelihood = _Buffer.ReadFloat(); Record(nameof(Likelihood), Likelihood); } #endif - if (Package.Version >= 63) + // Resource Interchange File Format + _Buffer.Read(out RawData); + Record(nameof(RawData), RawData); +#if UNDYING + if (Package.Build == UnrealPackage.GameBuild.BuildName.Undying) { - // LazyArray skip-offset - int nextSerialOffset = _Buffer.ReadInt32(); - Record(nameof(nextSerialOffset), nextSerialOffset); - } + if (_Buffer.Version >= 76) + { + _Buffer.Read(out uint v5c); + Record(nameof(v5c), v5c); - // Resource Interchange File Format - Data = new byte[_Buffer.ReadIndex()]; - _Buffer.Read(Data, 0, Data.Length); - Record(nameof(Data), Data); + int undyingExtraDataLength = _Buffer.ReadIndex(); + Record(nameof(undyingExtraDataLength), undyingExtraDataLength); + if (undyingExtraDataLength > 0) + { + var undyingExtraData = new byte[undyingExtraDataLength]; + _Buffer.Read(undyingExtraData, 0, undyingExtraDataLength); + Record(nameof(undyingExtraData), undyingExtraData); + } + } + + if (_Buffer.Version >= 85) + { + _Buffer.Read(out uint v8c); + Record(nameof(v8c), v8c); + } + } +#endif } } -} \ No newline at end of file +} diff --git a/src/Engine/Classes/USoundGroup.cs b/src/Engine/Classes/USoundGroup.cs new file mode 100644 index 00000000..a08f56f9 --- /dev/null +++ b/src/Engine/Classes/USoundGroup.cs @@ -0,0 +1,31 @@ +using UELib.Branch; + +namespace UELib.Core +{ + /// + /// Implements USoundGroup/Engine.SoundGroup + /// + [UnrealRegisterClass] + [BuildGeneration(BuildGeneration.UE2_5)] + public class USoundGroup : USound + { + // p.s. It's not safe to cast to USound + public UArray Sounds; + + protected override void Deserialize() + { + base.Deserialize(); +#if UT + if ((Package.Build == UnrealPackage.GameBuild.BuildName.UT2004 || + Package.Build == UnrealPackage.GameBuild.BuildName.UT2003) + && _Buffer.LicenseeVersion < 27) + { + _Buffer.Read(out string package); + Record(nameof(package), package); + } +#endif + _Buffer.ReadArray(out Sounds); + Record(nameof(Sounds), Sounds); + } + } +} diff --git a/src/Engine/Classes/USurface.cs b/src/Engine/Classes/USurface.cs new file mode 100644 index 00000000..9fd50f65 --- /dev/null +++ b/src/Engine/Classes/USurface.cs @@ -0,0 +1,14 @@ +using UELib.Branch; +using UELib.Core; + +namespace UELib.Engine +{ + /// + /// Implements USurface/Engine.Surface + /// + [UnrealRegisterClass] + [BuildGeneration(BuildGeneration.UE3)] + public class USurface : UObject, IUnrealViewable + { + } +} \ No newline at end of file diff --git a/src/Engine/Classes/UTerrainWeightMapTexture.cs b/src/Engine/Classes/UTerrainWeightMapTexture.cs new file mode 100644 index 00000000..2b3cac9c --- /dev/null +++ b/src/Engine/Classes/UTerrainWeightMapTexture.cs @@ -0,0 +1,13 @@ +using UELib.Branch; + +namespace UELib.Engine +{ + /// + /// Implements UTerrainWeightMapTexture/Engine.TerrainWeightMapTexture + /// + [UnrealRegisterClass] + [BuildGeneration(BuildGeneration.UE3)] + public class UTerrainWeightMapTexture : UTexture2D + { + } +} diff --git a/src/Engine/Classes/UTexture.cs b/src/Engine/Classes/UTexture.cs index fadf219a..5e3209f8 100644 --- a/src/Engine/Classes/UTexture.cs +++ b/src/Engine/Classes/UTexture.cs @@ -1,42 +1,56 @@ -using System; -using System.Diagnostics.CodeAnalysis; +using UELib.Annotations; +using UELib.Branch; using UELib.Core; namespace UELib.Engine { + /// + /// Implements UTexture/Engine.Texture + /// [UnrealRegisterClass] - public class UTexture : UBitmapMaterial, IUnrealViewable + public class UTexture : UBitmapMaterial { - public UArray Mips; public bool HasComp; + [BuildGenerationRange(BuildGeneration.UE1, BuildGeneration.UE2_5)] [CanBeNull] + public UArray Mips; + + [BuildGeneration(BuildGeneration.UE3)] + public UBulkData SourceArt; + protected override void Deserialize() { base.Deserialize(); - if (_Buffer.Version > 160) + // This kind of data was moved to UTexture2D + if (_Buffer.Version >= (uint)PackageObjectLegacyVersion.UE3) { - throw new NotSupportedException("UTexture is not supported for this build"); + _Buffer.Read(out SourceArt); + Record(nameof(SourceArt), SourceArt); + return; } - _Buffer.ReadArray(out Mips); - Record(nameof(Mips), Mips); - - var bHasCompProperty = Properties.Find("bHasComp"); - if (bHasCompProperty != null) + if (_Buffer.Version < (uint)PackageObjectLegacyVersion.CompMipsDeprecated) { - HasComp = bool.Parse(bHasCompProperty.Value); - if (HasComp) + var bHasCompProperty = Properties.Find("bHasComp"); + if (bHasCompProperty != null) { - throw new NotSupportedException("UTexture of this kind is not supported"); + HasComp = bool.Parse(bHasCompProperty.Value); + if (HasComp) + { + _Buffer.ReadArray(out UArray oldMips); + Record(nameof(oldMips), oldMips); + } } } + + _Buffer.ReadArray(out Mips); + Record(nameof(Mips), Mips); } - [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] - public struct MipMap : IUnrealSerializableClass + public struct LegacyMipMap : IUnrealSerializableClass { - public byte[] Data; + public UBulkData Data; public int USize; public int VSize; public byte UBits; @@ -44,23 +58,21 @@ public struct MipMap : IUnrealSerializableClass public void Deserialize(IUnrealStream stream) { - if (stream.Version >= 63) - { - int positionAfterData = stream.ReadInt32(); - } - - Data = new byte[stream.ReadIndex()]; - stream.Read(Data, 0, Data.Length); - USize = stream.ReadInt32(); - VSize = stream.ReadInt32(); - UBits = stream.ReadByte(); - VBits = stream.ReadByte(); + stream.Read(out Data); + stream.Read(out USize); + stream.Read(out VSize); + stream.Read(out UBits); + stream.Read(out VBits); } public void Serialize(IUnrealStream stream) { - throw new NotImplementedException(); + stream.Write(ref Data); + stream.Write(USize); + stream.Write(VSize); + stream.Write(UBits); + stream.Write(VBits); } } } -} \ No newline at end of file +} diff --git a/src/Engine/Classes/UTexture2D.cs b/src/Engine/Classes/UTexture2D.cs new file mode 100644 index 00000000..f605bb7b --- /dev/null +++ b/src/Engine/Classes/UTexture2D.cs @@ -0,0 +1,110 @@ +using UELib.Branch; +using UELib.Core; + +namespace UELib.Engine +{ + /// + /// Implements UTexture2D/Engine.Texture2D + /// + [UnrealRegisterClass] + [BuildGeneration(BuildGeneration.UE3)] + public class UTexture2D : UTexture + { + public uint SizeX, SizeY; + + public UArray Mips; + + /// + /// PVR Texture Compression + /// + public UArray CachedPVRTCMips; + + /// + /// ATI Texture Compression + /// + public UArray CachedATITCMips; + + /// + /// Ericsson Texture Compression + /// + public UArray CachedETCMips; + + public int CachedFlashMipMaxResolution; + public UBulkData CachedFlashMipData; + + public UGuid TextureFileCacheGuid; + + protected override void Deserialize() + { + base.Deserialize(); + + // These properties have been moved to ScriptProperties. + if (_Buffer.Version < (uint)PackageObjectLegacyVersion.DisplacedUTextureProperties) + { + _Buffer.Read(out SizeX); + Record(nameof(SizeX), SizeX); + + _Buffer.Read(out SizeY); + Record(nameof(SizeY), SizeY); + + _Buffer.Read(out int format); + Format = (TextureFormat)format; + Record(nameof(Format), Format); + } + + _Buffer.ReadArray(out Mips); + Record(nameof(Mips), Mips); + + if (_Buffer.Version >= (uint)PackageObjectLegacyVersion.AddedTextureFileCacheGuidToTexture2D) + { + _Buffer.ReadStruct(out TextureFileCacheGuid); + Record(nameof(TextureFileCacheGuid), TextureFileCacheGuid); + } + + if (_Buffer.Version >= (uint)PackageObjectLegacyVersion.AddedPVRTCToUTexture2D) + { + _Buffer.ReadArray(out CachedPVRTCMips); + Record(nameof(CachedPVRTCMips), CachedPVRTCMips); + } + + if (_Buffer.Version >= (uint)PackageObjectLegacyVersion.AddedATITCToUTexture2D) + { + _Buffer.Read(out CachedFlashMipMaxResolution); + Record(nameof(CachedFlashMipMaxResolution), CachedFlashMipMaxResolution); + + _Buffer.ReadArray(out CachedATITCMips); + Record(nameof(CachedATITCMips), CachedATITCMips); + + _Buffer.ReadStruct(out CachedFlashMipData); + Record(nameof(CachedFlashMipData), CachedFlashMipData); + } + + if (_Buffer.Version >= (uint)PackageObjectLegacyVersion.AddedETCToUTexture2D) + { + _Buffer.ReadArray(out CachedETCMips); + Record(nameof(CachedETCMips), CachedETCMips); + } + } + + public struct MipMap2D : IUnrealDeserializableClass + { + public UBulkData Data; + public uint SizeX; + public uint SizeY; + + public void Deserialize(IUnrealStream stream) + { + stream.Read(out Data); + stream.Read(out SizeX); + stream.Read(out SizeY); + } + + public void Serialize(IUnrealStream stream) + { + stream.Write(ref Data); + stream.Write(SizeX); + stream.Write(SizeY); + } + } + } +} diff --git a/src/Engine/Classes/UTexture2DComposite.cs b/src/Engine/Classes/UTexture2DComposite.cs new file mode 100644 index 00000000..9c01d294 --- /dev/null +++ b/src/Engine/Classes/UTexture2DComposite.cs @@ -0,0 +1,30 @@ +using System.Runtime.InteropServices; +using UELib.Branch; + +namespace UELib.Engine +{ + /// + /// Implements UTexture2DComposite/Engine.Texture2DComposite + /// + [UnrealRegisterClass] + [BuildGeneration(BuildGeneration.UE3)] + public class UTexture2DComposite : UTexture2D + { + [UnmanagedFunctionPointer(CallingConvention.ThisCall)] + private delegate void _Deserialize(); + + protected override void Deserialize() + { + // Deserialize from UTexture instead of UTexture2D + if (_Buffer.Version >= 600) + { + var ptr = typeof(UTexture).GetMethod(nameof(Deserialize)).MethodHandle.GetFunctionPointer(); + var deserializeFunc = Marshal.GetDelegateForFunctionPointer<_Deserialize>(ptr); + deserializeFunc(); + return; + } + + base.Deserialize(); + } + } +} diff --git a/src/Engine/Classes/UTexture2DDynamic.cs b/src/Engine/Classes/UTexture2DDynamic.cs new file mode 100644 index 00000000..2962390c --- /dev/null +++ b/src/Engine/Classes/UTexture2DDynamic.cs @@ -0,0 +1,13 @@ +using UELib.Branch; + +namespace UELib.Engine +{ + /// + /// Implements UTexture2DDynamic/Engine.Texture2DDynamic + /// + [UnrealRegisterClass] + [BuildGeneration(BuildGeneration.UE3)] + public class UTexture2DDynamic : UTexture + { + } +} diff --git a/src/Engine/Classes/UTexture3D.cs b/src/Engine/Classes/UTexture3D.cs new file mode 100644 index 00000000..5e85830b --- /dev/null +++ b/src/Engine/Classes/UTexture3D.cs @@ -0,0 +1,57 @@ +using UELib.Branch; +using UELib.Core; + +namespace UELib.Engine +{ + /// + /// Implements UTexture3D/Engine.Texture3D + /// + [UnrealRegisterClass] + [BuildGeneration(BuildGeneration.UE3)] + public class UTexture3D : UTexture + { + public UArray Mips; + public uint SizeX, SizeY; + + protected override void Deserialize() + { + base.Deserialize(); + + _Buffer.Read(out SizeX); + Record(nameof(SizeX), SizeX); + + _Buffer.Read(out SizeY); + Record(nameof(SizeY), SizeY); + + _Buffer.Read(out int format); + Format = (TextureFormat)format; + Record(nameof(Format), Format); + + _Buffer.ReadArray(out Mips); + } + + public struct MipMap3D : IUnrealDeserializableClass + { + public UBulkData Data; + public uint SizeX; + public uint SizeY; + public uint SizeZ; + + public void Deserialize(IUnrealStream stream) + { + stream.Read(out Data); + stream.Read(out SizeX); + stream.Read(out SizeY); + stream.Read(out SizeZ); + } + + public void Serialize(IUnrealStream stream) + { + stream.Write(ref Data); + stream.Write(SizeX); + stream.Write(SizeY); + stream.Write(SizeZ); + } + } + } +} diff --git a/src/Engine/Classes/UTextureCube.cs b/src/Engine/Classes/UTextureCube.cs new file mode 100644 index 00000000..488cd6c9 --- /dev/null +++ b/src/Engine/Classes/UTextureCube.cs @@ -0,0 +1,34 @@ +using UELib.Branch; + +namespace UELib.Engine +{ + /// + /// Implements UTextureCube/Engine.TextureCube + /// + [UnrealRegisterClass] + [BuildGeneration(BuildGeneration.UE3)] + public class UTextureCube : UTexture + { + public uint SizeX, SizeY; + + protected override void Deserialize() + { + base.Deserialize(); + + if (_Buffer.Version < (uint)PackageObjectLegacyVersion.DisplacedUTextureProperties) + { + _Buffer.Read(out SizeX); + Record(nameof(SizeX), SizeX); + _Buffer.Read(out SizeY); + Record(nameof(SizeY), SizeY); + + _Buffer.Read(out int format); + Format = (TextureFormat)format; + Record(nameof(Format), Format); + + _Buffer.Read(out int numMips); + Record(nameof(numMips), numMips); + } + } + } +} diff --git a/src/Engine/Classes/UTextureFlipBook.cs b/src/Engine/Classes/UTextureFlipBook.cs new file mode 100644 index 00000000..c396a5bd --- /dev/null +++ b/src/Engine/Classes/UTextureFlipBook.cs @@ -0,0 +1,13 @@ +using UELib.Branch; + +namespace UELib.Engine +{ + /// + /// Implements UTextureFlipBook/Engine.TextureFlipBook + /// + [UnrealRegisterClass] + [BuildGeneration(BuildGeneration.UE3)] + public class UTextureFlipBook : UTexture2D + { + } +} diff --git a/src/Engine/Classes/UTextureMovie.cs b/src/Engine/Classes/UTextureMovie.cs new file mode 100644 index 00000000..2001a6f1 --- /dev/null +++ b/src/Engine/Classes/UTextureMovie.cs @@ -0,0 +1,23 @@ +using UELib.Branch; +using UELib.Core; + +namespace UELib.Engine +{ + /// + /// Implements UTextureMovie/Engine.TextureMovie + /// + [UnrealRegisterClass] + [BuildGeneration(BuildGeneration.UE3)] + public class UTextureMovie : UTexture + { + public UBulkData RawData; + + protected override void Deserialize() + { + base.Deserialize(); + + _Buffer.Read(out RawData); + Record(nameof(RawData), RawData); + } + } +} diff --git a/src/Engine/Classes/UTextureRenderTarget2D.cs b/src/Engine/Classes/UTextureRenderTarget2D.cs new file mode 100644 index 00000000..3a384178 --- /dev/null +++ b/src/Engine/Classes/UTextureRenderTarget2D.cs @@ -0,0 +1,34 @@ +using UELib.Branch; + +namespace UELib.Engine +{ + /// + /// Implements UTextureRenderTarget2D/Engine.TextureRenderTarget2D + /// + [UnrealRegisterClass] + [BuildGeneration(BuildGeneration.UE3)] + public class UTextureRenderTarget2D : UTexture + { + public uint SizeX, SizeY; + + protected override void Deserialize() + { + base.Deserialize(); + + if (_Buffer.Version < (uint)PackageObjectLegacyVersion.DisplacedUTextureProperties) + { + _Buffer.Read(out SizeX); + Record(nameof(SizeX), SizeX); + _Buffer.Read(out SizeY); + Record(nameof(SizeY), SizeY); + + _Buffer.Read(out int format); + Format = (TextureFormat)format; + Record(nameof(Format), Format); + + _Buffer.Read(out int numMips); + Record(nameof(numMips), numMips); + } + } + } +} diff --git a/src/Engine/Classes/UTextureRenderTargetCube.cs b/src/Engine/Classes/UTextureRenderTargetCube.cs new file mode 100644 index 00000000..a66c60a0 --- /dev/null +++ b/src/Engine/Classes/UTextureRenderTargetCube.cs @@ -0,0 +1,34 @@ +using UELib.Branch; + +namespace UELib.Engine +{ + /// + /// Implements UTextureRenderTargetCube/Engine.TextureRenderTargetCube + /// + [UnrealRegisterClass] + [BuildGeneration(BuildGeneration.UE3)] + public class UTextureRenderTargetCube : UTexture + { + public uint SizeX, SizeY; + + protected override void Deserialize() + { + base.Deserialize(); + + if (_Buffer.Version < (uint)PackageObjectLegacyVersion.DisplacedUTextureProperties) + { + _Buffer.Read(out SizeX); + Record(nameof(SizeX), SizeX); + _Buffer.Read(out SizeY); + Record(nameof(SizeY), SizeY); + + _Buffer.Read(out int format); + Format = (TextureFormat)format; + Record(nameof(Format), Format); + + _Buffer.Read(out int numMips); + Record(nameof(numMips), numMips); + } + } + } +} diff --git a/src/Engine/Types/LightingChannelContainer.cs b/src/Engine/Types/LightingChannelContainer.cs new file mode 100644 index 00000000..d189a9c2 --- /dev/null +++ b/src/Engine/Types/LightingChannelContainer.cs @@ -0,0 +1,16 @@ +using System.Runtime.InteropServices; + +namespace UELib.Engine +{ + /// + /// See LightingChannelContainer in Engine/Classes/LightComponent.uc + /// + [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 4)] + public struct LightingChannelContainer : IUnrealAtomicStruct + { + [MarshalAs(UnmanagedType.I1)] public bool Initialized; + [MarshalAs(UnmanagedType.I1)] public bool BSP; + [MarshalAs(UnmanagedType.I1)] public bool Static; + [MarshalAs(UnmanagedType.I1)] public bool Dynamic; + } +} \ No newline at end of file diff --git a/src/Engine/Types/LightmassPrimitiveSettings.cs b/src/Engine/Types/LightmassPrimitiveSettings.cs new file mode 100644 index 00000000..e38043d2 --- /dev/null +++ b/src/Engine/Types/LightmassPrimitiveSettings.cs @@ -0,0 +1,75 @@ +using System.ComponentModel; +using System.Runtime.InteropServices; +using UELib.Branch; + +namespace UELib.Engine +{ + /// + /// Implements FLightmassPrimitiveSettings + /// + [StructLayout(LayoutKind.Sequential)] + public struct LightmassPrimitiveSettings : IUnrealSerializableClass + { + [MarshalAs(UnmanagedType.I1)] public bool UseTwoSidedLighting; + [MarshalAs(UnmanagedType.I1)] public bool ShadowIndirectOnly; + [MarshalAs(UnmanagedType.I1)] public bool UseEmissiveForStaticLighting; + + public float EmissiveLightFalloffExponent; + public float EmissiveLightExplicitInfluenceRadius; + public float EmissiveBoost; + public float DiffuseBoost; + public float SpecularBoost; + + [DefaultValue(1.0f)] public float FullyOccludedSamplesFraction; + + public void Deserialize(IUnrealStream stream) + { + if (stream.Version >= (uint)PackageObjectLegacyVersion.LightmassShadowIndirectOnlyOptionAdded) + { + stream.Read(out UseTwoSidedLighting); + stream.Read(out ShadowIndirectOnly); + stream.Read(out FullyOccludedSamplesFraction); + } + + if (stream.Version >= (uint)PackageObjectLegacyVersion.LightmassAdded) + { + stream.Read(out UseEmissiveForStaticLighting); + stream.Read(out EmissiveLightFalloffExponent); + } + + if (stream.Version >= (uint)PackageObjectLegacyVersion.LightmassExplicitEmissiveLightRadiusAdded) + { + stream.Read(out EmissiveLightExplicitInfluenceRadius); + } + + stream.Read(out EmissiveBoost); + stream.Read(out DiffuseBoost); + stream.Read(out SpecularBoost); + } + + public void Serialize(IUnrealStream stream) + { + if (stream.Version >= (uint)PackageObjectLegacyVersion.LightmassShadowIndirectOnlyOptionAdded) + { + stream.Write(UseTwoSidedLighting); + stream.Write(ShadowIndirectOnly); + stream.Write(FullyOccludedSamplesFraction); + } + + if (stream.Version >= (uint)PackageObjectLegacyVersion.LightmassAdded) + { + stream.Write(UseEmissiveForStaticLighting); + stream.Write(EmissiveLightFalloffExponent); + } + + if (stream.Version >= (uint)PackageObjectLegacyVersion.LightmassExplicitEmissiveLightRadiusAdded) + { + stream.Write(EmissiveLightExplicitInfluenceRadius); + } + + stream.Write(EmissiveBoost); + stream.Write(DiffuseBoost); + stream.Write(SpecularBoost); + } + } +} \ No newline at end of file diff --git a/src/Engine/Types/Poly.cs b/src/Engine/Types/Poly.cs new file mode 100644 index 00000000..9757c1f5 --- /dev/null +++ b/src/Engine/Types/Poly.cs @@ -0,0 +1,173 @@ +using System; +using System.ComponentModel; +using System.Numerics; +using UELib.Annotations; +using UELib.Branch; +using UELib.Core; +using UELib.ObjectModel.Annotations; + +namespace UELib.Engine +{ + /// + /// Implements FPoly + /// + [Output("Polygon")] + public class Poly : IUnrealSerializableClass, IAcceptable + { + [CanBeNull] public UObject Actor; + + public int BrushPoly = -1; + + [DefaultValue(null)] [Output("Item", OutputSlot.Parameter)] + public UName ItemName; + + [DefaultValue(null)] [Output("Texture")] + public UObject Material; + + [DefaultValue(3584u)] [Output("Flags", OutputSlot.Parameter)] + public uint PolyFlags = 3584u; + + [DefaultValue(-1)] [Output(OutputSlot.Parameter)] + public int Link = -1; + + [DefaultValue(32.0f)] [Output(OutputSlot.Parameter)] + public float ShadowMapScale = 32.0f; + + //[Output(OutputSlot.Parameter)] + public LightingChannelContainer LightingChannels; + + [Output("Origin", OutputFlags.ShorthandProperty)] + public UVector Base; + + [Output(OutputFlags.ShorthandProperty)] + public UVector Normal; + + [DefaultValue(0)] [Output("U")] public short PanU; + [DefaultValue(0)] [Output("V")] public short PanV; + + [Output(OutputFlags.ShorthandProperty)] + public UVector TextureU; + + [Output(OutputFlags.ShorthandProperty)] + public UVector TextureV; + + [Output(OutputFlags.ShorthandProperty)] + public UArray Vertex; + + [DefaultValue(32.0f)] public float LightMapScale = 32.0f; // Not exported for some reason + + public LightmassPrimitiveSettings LightmassSettings; + + [DefaultValue(null)] public UProcBuildingRuleset Ruleset; + [DefaultValue(null)] public UName RulesetVariation; + + public void Accept(IVisitor visitor) + { + visitor.Visit(this); + } + + public TResult Accept(IVisitor visitor) + { + return visitor.Visit(this); + } + + public void Deserialize(IUnrealStream stream) + { + // Always 16 + int numVertices = stream.Version < (uint)PackageObjectLegacyVersion.FixedVerticesToArrayFromPoly + ? stream.ReadIndex() + : -1; + + stream.ReadStruct(out Base); + stream.ReadStruct(out Normal); + stream.ReadStruct(out TextureU); + stream.ReadStruct(out TextureV); + + if (stream.Version >= (uint)PackageObjectLegacyVersion.FixedVerticesToArrayFromPoly) + { + stream.Read(out Vertex); + } + else + { + stream.ReadArray(out Vertex, numVertices); + } + + PolyFlags = stream.ReadUInt32(); + if (stream.Version < 250) + { + PolyFlags |= 0xe00; + } + + Actor = stream.ReadObject(); + if (stream.Version < (uint)PackageObjectLegacyVersion.TextureDeprecatedFromPoly) + { + Material = stream.ReadObject(); + } + + ItemName = stream.ReadNameReference(); + // The fact this is serialized after ItemName indicates we may have had both (A texture and material) at one point. + if (stream.Version >= (uint)PackageObjectLegacyVersion.MaterialAddedToPoly) + { + Material = stream.ReadObject(); + } + + Link = stream.ReadIndex(); + BrushPoly = stream.ReadIndex(); + + if (stream.Version < (uint)PackageObjectLegacyVersion.PanUVRemovedFromPoly) + { + PanU = stream.ReadInt16(); + PanV = stream.ReadInt16(); + + // Fix UV + var newBase = (Vector3)Base; + newBase -= (Vector3)TextureU / ((Vector3)TextureU).LengthSquared() * PanU; + newBase -= (Vector3)TextureV / ((Vector3)TextureV).LengthSquared() * PanV; + Base = (UVector)newBase; + } + + if (stream.Version >= (uint)PackageObjectLegacyVersion.LightMapScaleAddedToPoly && + stream.Version < (uint)PackageObjectLegacyVersion.LightMapScaleRemovedFromPoly) + { + LightMapScale = stream.ReadFloat(); + } + + if (stream.Version >= (uint)PackageObjectLegacyVersion.ShadowMapScaleAddedToPoly) + { + ShadowMapScale = stream.ReadFloat(); + } + + if (stream.Version >= (uint)PackageObjectLegacyVersion.LightingChannelsAddedToPoly) + { + stream.ReadStructMarshal(out LightingChannels); + } + else + { + LightingChannels.Initialized = true; + LightingChannels.BSP = true; + } + + if (stream.Version >= (uint)PackageObjectLegacyVersion.LightmassAdded) + { + stream.ReadStruct(out LightmassSettings); + } + + if (stream.Version >= (uint)PackageObjectLegacyVersion.UProcBuildingReferenceAddedToPoly) + { + if (stream.Version >= (uint)PackageObjectLegacyVersion.PolyRulesetVariationTypeChangedToName) + { + stream.Read(out RulesetVariation); + } + else + { + stream.Read(out Ruleset); + } + } + } + + public void Serialize(IUnrealStream stream) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/src/GlobalSuppressions.cs b/src/GlobalSuppressions.cs index 6e65402c..6f929546 100644 --- a/src/GlobalSuppressions.cs +++ b/src/GlobalSuppressions.cs @@ -6,28 +6,4 @@ // To add a suppression to this file, right-click the message in the // Error List, point to "Suppress Message(s)", and click // "In Project Suppression File". -// You do not need to add suppressions to this file manually. - -[assembly: - System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", - "CA2214:DoNotCallOverridableMethodsInConstructors", Scope = "member", - Target = "UELib.UObjectStream.#.ctor(UELib.UPackageStream,System.Byte[]&)")] -[assembly: - System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", - "CA2214:DoNotCallOverridableMethodsInConstructors", Scope = "member", - Target = "UELib.UPackageStream.#.ctor(System.String,System.IO.FileMode,System.IO.FileAccess)")] -[assembly: - System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2105:ArrayFieldsShouldNotBeReadOnly", - Scope = "member", Target = "UELib.UnrealExtensions.#TextureExt")] -[assembly: - System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1726:UsePreferredTerms", MessageId = "Flags", - Scope = "member", Target = "UELib.UnrealMethods.#FlagsToList(System.Type,System.UInt32)")] -[assembly: - System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", - MessageId = "ZLX", Scope = "member", Target = "UELib.Flags.CompressionFlags.#ZLX")] -[assembly: - System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", - MessageId = "ZLO", Scope = "member", Target = "UELib.Flags.CompressionFlags.#ZLO")] -[assembly: - System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", - MessageId = "ZLIB", Scope = "member", Target = "UELib.Flags.CompressionFlags.#ZLIB")] \ No newline at end of file +// You do not need to add suppressions to this file manually. \ No newline at end of file diff --git a/src/ObjectModel/Annotations/ExprTokenAttribute.cs b/src/ObjectModel/Annotations/ExprTokenAttribute.cs new file mode 100644 index 00000000..c73d7e49 --- /dev/null +++ b/src/ObjectModel/Annotations/ExprTokenAttribute.cs @@ -0,0 +1,15 @@ +using System; +using UELib.Tokens; + +namespace UELib.ObjectModel.Annotations +{ + public class ExprTokenAttribute : Attribute + { + public readonly ExprToken ExprToken; + + public ExprTokenAttribute(ExprToken exprToken) + { + ExprToken = exprToken; + } + } +} diff --git a/src/ObjectModel/Annotations/OutputAttribute.cs b/src/ObjectModel/Annotations/OutputAttribute.cs new file mode 100644 index 00000000..7f7e3044 --- /dev/null +++ b/src/ObjectModel/Annotations/OutputAttribute.cs @@ -0,0 +1,69 @@ +using System; +using System.Diagnostics; + +namespace UELib.ObjectModel.Annotations +{ + public enum OutputSlot + { + Parameter, + Property + } + + [Flags] + public enum OutputFlags + { + Default = 0x00, + ShorthandProperty = 1 << 0 + } + + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Class)] + public class OutputAttribute : Attribute + { + public readonly string Identifier; + public readonly OutputSlot Slot; + public readonly OutputFlags Flags; + + public OutputAttribute() + { + Slot = OutputSlot.Property; + Flags = OutputFlags.Default; + } + + public OutputAttribute(string identifier) + { + Identifier = identifier; + Slot = OutputSlot.Property; + Flags = OutputFlags.Default; + } + + public OutputAttribute(OutputFlags flags) + { + Slot = OutputSlot.Property; + Flags = flags; + } + + public OutputAttribute(string identifier, OutputFlags flags) + { + Identifier = identifier; + Slot = OutputSlot.Property; + Flags = flags; + } + + public OutputAttribute(string identifier, OutputSlot slot, OutputFlags flags = OutputFlags.Default) + { + Debug.Assert(slot != OutputSlot.Parameter || !flags.HasFlag(OutputFlags.ShorthandProperty)); + + Identifier = identifier; + Slot = slot; + Flags = flags; + } + + public OutputAttribute(OutputSlot slot, OutputFlags flags = OutputFlags.Default) + { + Debug.Assert(slot != OutputSlot.Parameter || !flags.HasFlag(OutputFlags.ShorthandProperty)); + + Slot = slot; + Flags = flags; + } + } +} \ No newline at end of file diff --git a/src/Properties/AssemblyInfo.cs b/src/Properties/AssemblyInfo.cs index 0761bf59..9f226477 100644 --- a/src/Properties/AssemblyInfo.cs +++ b/src/Properties/AssemblyInfo.cs @@ -1,17 +1,4 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("UELib")] -[assembly: AssemblyDescription("UnrealScript decompiler library for Unreal package files (.upk, .u, .uasset; etc), with support for Unreal Engine 1, 2, and 3.")] -[assembly: AssemblyConfiguration("Publish")] -[assembly: AssemblyCompany("EliotVU")] -[assembly: AssemblyProduct("UELib")] -[assembly: AssemblyCopyright("© 2009-2022 Eliot van Uytfanghe. All rights reserved.")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] +using System.Runtime.InteropServices; // Setting ComVisible to false makes the types in this assembly not visible // to COM components. If you need to access a type in this assembly from @@ -21,15 +8,3 @@ // The following GUID is for the ID of the typelib if this project is exposed to COM [assembly: Guid("0eb1c54e-8955-4c8d-98d3-16285f114f16")] -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// 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.1")] -[assembly: AssemblyFileVersion("1.3.1")] \ No newline at end of file diff --git a/src/UnrealBuild.cs b/src/UnrealBuild.cs index 095d8d4d..cd016367 100644 --- a/src/UnrealBuild.cs +++ b/src/UnrealBuild.cs @@ -2,6 +2,10 @@ namespace UELib { + /// + /// TODO: Re-purpose. + /// + /// public enum BuildGeneration { Undefined, @@ -13,6 +17,11 @@ public enum BuildGeneration /// UE1, + /// + /// Modified version for Harry Potter's Unreal Engine 1 + /// + HP, + /// /// Unreal Engine 2 /// @@ -23,13 +32,18 @@ public enum BuildGeneration /// /// Heavily modified Unreal Engine 2 by Ion Storm for Thief: Deadly Shadows /// - Thief, + Flesh, /// /// Unreal Engine 2 with some early UE3 upgrades. /// UE2_5, + /// + /// The Unreal Engine 2.5 engine used in the America's Army series. + /// + AGP, + /// /// Heavily modified Unreal Engine 2.5 for Vengeance: Tribes; also used by Swat4 and BioShock. /// @@ -77,6 +91,7 @@ public enum BuildGeneration } [Flags] + [Obsolete("To be deprecated, see BuildPlatform")] public enum BuildFlags : byte { /// @@ -88,11 +103,13 @@ public enum BuildFlags : byte /// Is cooked for Xenon(Xbox 360). Could be true on PC games. /// XenonCooked = 0x02, + } - /// - /// Some UDK games have disabled the DLLBind feature. - /// - NoDLLBind = 0x04 + public enum BuildPlatform + { + Undetermined, + PC, + Console } [AttributeUsage(AttributeTargets.Field)] diff --git a/src/UnrealConfig.cs b/src/UnrealConfig.cs index deef7d61..ef625d82 100644 --- a/src/UnrealConfig.cs +++ b/src/UnrealConfig.cs @@ -23,12 +23,14 @@ public static class UnrealConfig public static string PreEndBracket = "\r\n{0}"; public static string Indention = "\t"; + [Obsolete] public enum CookedPlatform { PC, Console } + [Obsolete] public static CookedPlatform Platform; public static Dictionary> VariableTypes; diff --git a/src/UnrealExtensions.cs b/src/UnrealExtensions.cs index 890c37cb..d25364e3 100644 --- a/src/UnrealExtensions.cs +++ b/src/UnrealExtensions.cs @@ -5,69 +5,63 @@ namespace UELib { public static class UnrealExtensions { - // .TFC - Texture File Cache - public const string UnrealCodeExt = ".uc"; public const string UnrealFlagsExt = ".UPKG"; - public static readonly string[] ScriptExt = new[]{ ".u", ".d2u", ".t3u" }; - public static readonly string[] TextureExt = new[]{ ".utx" }; - public static readonly string[] SoundExt = new[]{ ".uax", ".umx" }; - public static readonly string[] MeshExt = new[]{ ".usx", ".upx", ".ugx" }; - public static readonly string[] AnimExt = new[]{ ".ukx" }; - public static readonly string[] CacheExt = new[]{ ".uxx" }; - // UT2004, UDK, Unreal, Red Orchestra Map - public static readonly string[] MapExt = new[] + public static readonly string[] Code = { ".d2u", ".t3u" }; + public static readonly string[] Map = + { + ".ut2", ".udk", ".unr", + ".rom", ".un2", ".aao", + ".run", ".sac", ".xcm", + ".nrf", ".wot", ".scl", + ".dvs", ".rsm", ".ut3", + ".umap" + }; + + public static readonly string[] Other = { ".md5", ".usa", ".ums", ".rsa", ".sav" }; + public static readonly string[] Mod = { ".umod", ".ut2mod", ".ut4mod" }; + + public static readonly string[] Common = { ".u", ".upk", ".xxx", ".umap", ".uasset" }; + public static readonly string[] Legacy = { - ".ut2", ".udk", ".unr", ".rom", ".un2", ".aao", - ".run", ".sac", ".xcm", ".nrf", ".wot", ".scl", - ".dvs", ".rsm", ".ut3" + ".utx", ".uax", ".umx", + ".usx", ".upx", ".ugx", + ".ukx", ".uxx", ".uvx" }; - public static readonly string[] SaveExt = new[]{ ".uvx", ".md5", ".usa", ".ums", ".rsa", ".sav" }; - public static readonly string[] PackageExt = new[]{ ".upk" }; - public static readonly string[] ModExt = new[]{ ".umod", ".ut2mod", ".ut4mod" }; public static string FormatUnrealExtensionsAsFilter() { - var extensions = string.Empty; - var exts = FormatUnrealExtensionsAsList(); - foreach (string ext in exts) - { - extensions += "*" + ext; - if (ext != exts.Last()) - { - extensions += ";"; - } - } + string commonFilter = string.Empty; + commonFilter = Common.Aggregate(commonFilter, (current, ext) => current + "*" + ext + ";"); + + string mapFilter = string.Empty; + mapFilter = Map.Aggregate(mapFilter, (current, ext) => current + "*" + ext + ";"); + + string legacyFilter = string.Empty; + legacyFilter = Legacy.Aggregate(legacyFilter, (current, ext) => current + "*" + ext + ";" ); - return "All Unreal Files(" + extensions + ")|" + extensions; + return "All Files (*.*)|*.*"; + //$"Unreal Files ()|{commonFilter}|" + + //$"Unreal Legacy Files ()|{legacyFilter}|" + + //$"Unreal Map Files ()|{mapFilter}"; } public static List FormatUnrealExtensionsAsList() { - var exts = new List + var extensionsList = new List ( - ScriptExt.Length + - TextureExt.Length + - SoundExt.Length + - MeshExt.Length + - AnimExt.Length + - CacheExt.Length + - MapExt.Length + - SaveExt.Length + - PackageExt.Length + Common.Length + + Legacy.Length + + Map.Length + + Other.Length ); - exts.AddRange(ScriptExt); - exts.AddRange(TextureExt); - exts.AddRange(SoundExt); - exts.AddRange(MeshExt); - exts.AddRange(AnimExt); - exts.AddRange(CacheExt); - exts.AddRange(MapExt); - exts.AddRange(SaveExt); - exts.AddRange(PackageExt); - return exts; + extensionsList.AddRange(Common); + extensionsList.AddRange(Legacy); + extensionsList.AddRange(Map); + extensionsList.AddRange(Other); + return extensionsList; } } -} \ No newline at end of file +} diff --git a/src/UnrealFlags.cs b/src/UnrealFlags.cs index efca6f0b..7300a041 100644 --- a/src/UnrealFlags.cs +++ b/src/UnrealFlags.cs @@ -1,19 +1,110 @@ using System; +using System.Text; namespace UELib.Flags { + public class UnrealFlags + where TEnum : Enum + { + private ulong _Flags; + private readonly ulong[] _FlagsMap; + + public ulong Flags + { + get => _Flags; + set => _Flags = value; + } + + public UnrealFlags(ulong flags, ref ulong[] flagsMap) + { + _Flags = flags; + _FlagsMap = flagsMap; + } + + private bool HasFlag(int flagIndex) + { + ulong flag = _FlagsMap[flagIndex]; + return flag != 0 && (_Flags & flag) != 0; + } + + public bool HasFlag(TEnum flagIndex) + { + ulong flag = _FlagsMap[(int)(object)flagIndex]; + return flag != 0 && (_Flags & _FlagsMap[(int)(object)flagIndex]) != 0; + } + + public bool HasFlags(uint flags) + { + return (_Flags & flags) != 0; + } + + public bool HasFlags(ulong flags) + { + return (_Flags & flags) != 0; + } + + public static explicit operator uint(UnrealFlags flags) + { + return (uint)flags._Flags; + } + + public static explicit operator ulong(UnrealFlags flags) + { + return flags._Flags; + } + + public override string ToString() + { + var stringBuilder = new StringBuilder(); + var values = Enum.GetValues(typeof(TEnum)); + for (var i = 0; i < values.Length - 1 /* Max */; i++) + { + if (!HasFlag(i)) continue; + string n = Enum.GetName(typeof(TEnum), i); + stringBuilder.Append($"{n};"); + } + return stringBuilder.ToString(); + } + } + /// - /// Flags describing an package instance. - /// - /// Note: - /// This is valid for UE3 as well unless otherwise noted. - /// - /// @Redefined( Version, Clone ) - /// The flag is redefined in (Version) as (Clone) - /// - /// @Removed( Version ) - /// The flag is removed in (Version) + /// + /// + /// + /// + /// + /// /// + public enum PackageFlag + { + AllowDownload, + ClientOptional, + ServerSideOnly, + + /// + /// UE1??? + /// + Encrypted, + + Official, + + Cooked, +#if UE3 + ContainsMap, + ContainsDebugData, + ContainsScript, + StrippedSource, +#endif +#if UE4 + EditorOnly, + UnversionedProperties, + ReloadingForCooker, + FilterEditorOnly, +#endif + Max, + } + + [Obsolete("Use the normalized PackageFlag instead")] [Flags] public enum PackageFlags : uint { @@ -22,43 +113,50 @@ public enum PackageFlags : uint // 00020001 : A ordinary package /// - /// Whether clients are allowed to download the package from the server. + /// UEX: Whether clients are allowed to download the package from the server. + /// UE4: Displaced by "NewlyCreated" /// - AllowDownload = 0x00000001U, + AllowDownload = 0x00000001U, /// /// Whether clients can skip downloading the package but still able to join the server. /// - ClientOptional = 0x00000002U, + ClientOptional = 0x00000002U, /// /// Only necessary to load on the server. /// - ServerSideOnly = 0x00000004U, + ServerSideOnly = 0x00000004U, - BrokenLinks = 0x00000008U, // @Redefined(UE3, Cooked) + BrokenLinks = 0x00000008U, // @Redefined(UE3, Cooked) /// /// The package is cooked. /// - Cooked = 0x00000008U, // @Redefined + Cooked = 0x00000008U, // @Redefined /// /// ??? /// <= UT /// - Unsecure = 0x00000010U, + Unsecure = 0x00000010U, /// /// The package is encrypted. /// <= UT + /// Also attested in file UT2004/Packages.MD5 but it is not encrypted. /// - Encrypted = 0x00000020U, + Encrypted = 0x00000020U, + +#if UE4 + EditorOnly = 0x00000040U, + UnversionedProperties = 0x00002000U, +#endif /// /// Clients must download the package. /// - Need = 0x00008000U, + Need = 0x00008000U, /// /// Unknown flags @@ -67,33 +165,40 @@ public enum PackageFlags : uint /// /// Package holds map data. - Map = 0x00020000U, + ContainsMap = 0x00020000U, /// /// Package contains classes. /// - Script = 0x00200000U, + ContainsScript = 0x00200000U, /// /// The package was build with -Debug /// - Debug = 0x00400000U, - Imports = 0x00800000U, + ContainsDebugData = 0x00400000U, + + Imports = 0x00800000U, - Compressed = 0x02000000U, - FullyCompressed = 0x04000000U, + Compressed = 0x02000000U, + FullyCompressed = 0x04000000U, /// /// Whether package has metadata exported(anything related to the editor). /// - NoExportsData = 0x20000000U, + NoExportsData = 0x20000000U, /// /// Package's source is stripped. + /// UE4: Same as ReloadingForCooker? /// - Stripped = 0x40000000U, - - Protected = 0x80000000U, + Stripped = 0x40000000U, +#if UE4 + FilterEditorOnly = 0x80000000U, +#endif + Protected = 0x80000000U, +#if TRANSFORMERS + HMS_XmlFormat = 0x80000000U, +#endif } [Flags] @@ -179,10 +284,10 @@ public enum ObjectFlagsHO : ulong } /// - /// Flags describing an function instance. + /// Flags describing a function instance. /// [Flags] - public enum FunctionFlags : ulong // actually uint but were using ulong for UE2 and UE3 Compatably + public enum FunctionFlags : ulong { Final = 0x00000001U, Defined = 0x00000002U, @@ -198,24 +303,46 @@ public enum FunctionFlags : ulong // actually uint but were using ulong for UE2 Event = 0x00000800U, Operator = 0x00001000U, Static = 0x00002000U, - NoExport = 0x00004000U, // Can also be an identifier for functions with Optional parameters. + + /// + /// NoExport + /// UE3 (~V300): Indicates whether we have optional parameters, including optional expression data. + /// OptionalParameters = 0x00004000U, + NoExport = 0x00004000U, + Const = 0x00008000U, Invariant = 0x00010000U, + + // UE2 additions + // ============= + Public = 0x00020000U, Private = 0x00040000U, Protected = 0x00080000U, Delegate = 0x00100000U, - NetServer = 0x00200000U, #if VENGEANCE // Generated/Constructor? VG_Unk1 = 0x00200000U, VG_Overloaded = 0x00800000U, -#endif +#endif + /// + /// UE2: Multicast (Replicated to all relevant clients) + /// UE3: Function is replicated to relevant client. + /// + NetServer = 0x00200000U, + + Interface = 0x00400000U, NetClient = 0x01000000U, - DLLImport = 0x02000000U, // Also available in UE2(unknown meaning there) + + /// + /// UE2: Unknown + /// UE3 (V655) + /// + DLLImport = 0x02000000U, + // K2 Additions, late UDK, early implementation of Blueprints that were soon deprecated. K2Call = 0x04000000U, - K2Override = 0x08000000U, // K2Call? + K2Override = 0x08000000U, K2Pure = 0x10000000U, } diff --git a/src/UnrealInterfaces.cs b/src/UnrealInterfaces.cs index dd1d6c4f..a3d34af0 100644 --- a/src/UnrealInterfaces.cs +++ b/src/UnrealInterfaces.cs @@ -1,6 +1,8 @@ -using System.Collections.Generic; -using System.Diagnostics.Contracts; +using System; +using System.Collections.Generic; using System.IO; +using UELib.Annotations; +using UELib.Core; namespace UELib { @@ -19,6 +21,7 @@ public interface IUnrealDecompilable /// /// This class has a reference to an object and are both decompilable. /// + [Obsolete] public interface IDecompilableObject : IUnrealDecompilable { /// @@ -38,16 +41,12 @@ public interface IBuffered /// The copied buffer. byte[] CopyBuffer(); - [Pure] IUnrealStream GetBuffer(); - [Pure] int GetBufferPosition(); - [Pure] int GetBufferSize(); - [Pure] string GetBufferId(bool fullName = false); } @@ -56,7 +55,7 @@ public interface IBuffered /// public interface IBinaryData : IBuffered { - BinaryMetaData BinaryMetaData { get; } + [CanBeNull] BinaryMetaData BinaryMetaData { get; } } public interface IContainsTable @@ -70,6 +69,24 @@ public interface IContainsTable public interface IUnrealViewable { } + + public interface IVisitor + { + void Visit(IAcceptable visitable); + void Visit(UStruct.UByteCodeDecompiler.Token token); + } + + public interface IVisitor + { + TResult Visit(IAcceptable visitable); + TResult Visit(UStruct.UByteCodeDecompiler.Token token); + } + + public interface IAcceptable + { + void Accept(IVisitor visitor); + TResult Accept(IVisitor visitor); + } /// /// This class can be deserialized from a specified stream. @@ -86,23 +103,37 @@ public interface IUnrealSerializableClass : IUnrealDeserializableClass /// /// An atomic struct (e.g. UObject.Color, Vector, etc). - /// See + /// See /// public interface IUnrealAtomicStruct { } /// - /// This class is exportable into an non-unreal format + /// This class is capable of exporting data to a non-unreal format. + /// e.g. can be serialized to a stream and in turn be flushed to a .wav file. /// public interface IUnrealExportable { IEnumerable ExportableExtensions { get; } - bool CompatableExport(); + /// + /// Whether this object is exportable, usually called before any deserialization has occurred. + /// + bool CanExport(); + void SerializeExport(string desiredExportExtension, Stream exportStream); } + public static class IUnrealExportableImplementation + { + [Obsolete("Use CanExport()")] + public static bool CompatableExport(this IUnrealExportable exportable) + { + return exportable.CanExport(); + } + } + /// /// This class is replicable. /// @@ -113,4 +144,4 @@ public interface IUnrealNetObject bool RepReliable { get; } uint RepKey { get; } } -} \ No newline at end of file +} diff --git a/src/UnrealLoader.cs b/src/UnrealLoader.cs index a67427b6..cb9170ba 100644 --- a/src/UnrealLoader.cs +++ b/src/UnrealLoader.cs @@ -1,5 +1,6 @@ -using System.Collections.Generic; +using System; using System.IO; +using System.Linq; using UELib.Decoding; namespace UELib @@ -9,21 +10,21 @@ namespace UELib /// public static class UnrealLoader { - /// - /// Stored packages that were imported by certain objects. Kept here that in case re-use is necessary, that it will be loaded faster. - /// The packages and the list is closed and cleared by the main package that loaded them with ImportObjects(). - /// In any other case the list needs to be cleared manually. - /// - private static readonly List _CachedPackages = new List(); + public static UnrealPackage LoadPackage(string packagePath, FileAccess fileAccess = FileAccess.Read) + { + return LoadPackage(packagePath, UnrealPackage.GameBuild.BuildName.Unset, fileAccess); + } /// /// Loads the given file specified by PackagePath and /// returns the serialized UnrealPackage. /// - public static UnrealPackage LoadPackage(string packagePath, FileAccess fileAccess = FileAccess.Read) + public static UnrealPackage LoadPackage(string packagePath, UnrealPackage.GameBuild.BuildName buildTarget, + FileAccess fileAccess = FileAccess.Read) { var stream = new UPackageStream(packagePath, FileMode.Open, fileAccess); var package = new UnrealPackage(stream); + package.BuildTarget = buildTarget; package.Deserialize(stream); return package; } @@ -35,41 +36,62 @@ public static UnrealPackage LoadPackage(string packagePath, FileAccess fileAcces public static UnrealPackage LoadPackage(string packagePath, IBufferDecoder decoder, FileAccess fileAccess = FileAccess.Read) { - var stream = new UPackageStream(packagePath, FileMode.Open, fileAccess); - var package = new UnrealPackage(stream) { Decoder = decoder }; + var stream = new UPackageStream(packagePath, FileMode.Open, fileAccess) { Decoder = decoder }; + var package = new UnrealPackage(stream); package.Deserialize(stream); return package; } + /// + /// Loads the given file specified by PackagePath and + /// returns the serialized UnrealPackage with deserialized objects. + /// + public static UnrealPackage LoadFullPackage(string packagePath, FileAccess fileAccess = FileAccess.Read) + { + var package = LoadPackage(packagePath, fileAccess); + package?.InitializePackage(); + + return package; + } + /// /// Looks if the package is already loaded before by looking into the CachedPackages list first. /// If it is not found then it loads the given file specified by PackagePath and returns the serialized UnrealPackage. /// + [Obsolete] public static UnrealPackage LoadCachedPackage(string packagePath, FileAccess fileAccess = FileAccess.Read) { - var package = _CachedPackages.Find(pkg => pkg.PackageName == Path.GetFileNameWithoutExtension(packagePath)); - if (package != null) - return package; - - package = LoadPackage(packagePath, fileAccess); - if (package != null) - { - _CachedPackages.Add(package); - } + throw new NotImplementedException("Deprecated"); + } - return package; + /// + /// Tests if the given file path has a Unreal extension. + /// + /// The path to the Unreal file. + public static bool IsUnrealFileExtension(string filePath) + { + string fileExt = Path.GetExtension(filePath); + return UnrealExtensions.Common.Any(ext => fileExt == ext); } /// - /// Loads the given file specified by PackagePath and - /// returns the serialized UnrealPackage with deserialized objects. + /// Tests if the given file has a Unreal signature. /// - public static UnrealPackage LoadFullPackage(string packagePath, FileAccess fileAccess = FileAccess.Read) + /// The path to the Unreal file. + public static bool IsUnrealFileSignature(string filePath) { - var package = LoadPackage(packagePath, fileAccess); - package?.InitializePackage(); + var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read); + byte[] buffer = new byte[4]; + int read = stream.Read(buffer, 0, 4); + stream.Dispose(); - return package; + uint signature = BitConverter.ToUInt32(buffer, 0); + // Naive and a bit slow, but this works for most standard files. + return read == 4 && ( + signature == UnrealPackage.Signature || + signature == UnrealPackage.Signature_BigEndian + // TODO: Check for other games' signatures + ); } } -} \ No newline at end of file +} diff --git a/src/UnrealMethods.cs b/src/UnrealMethods.cs index ecd9cddc..97ad0e61 100644 --- a/src/UnrealMethods.cs +++ b/src/UnrealMethods.cs @@ -124,6 +124,7 @@ public LinkingObjectsException() : base("linking objects") /// /// Provides static methods for formating flags. /// + [Obsolete("Use .ToString() on the target enum instead.")] public static class UnrealMethods { public static string FlagsListToString(List flagsList) diff --git a/src/UnrealModel/README.md b/src/UnrealModel/README.md new file mode 100644 index 00000000..fa7535c1 --- /dev/null +++ b/src/UnrealModel/README.md @@ -0,0 +1,4 @@ +# Directory + +This directory contains all the data types that are equivalent to Unreal Engine data types. +See also /Src/Core/ and /Src/Engine/ which have not yet been moved to the "Unreal" namespace. \ No newline at end of file diff --git a/src/UnrealPackage.cs b/src/UnrealPackage.cs index b6eee5dd..5a1f9f23 100644 --- a/src/UnrealPackage.cs +++ b/src/UnrealPackage.cs @@ -1,17 +1,27 @@ using System; using System.Collections.Generic; -using System.ComponentModel; using System.Diagnostics; +using System.Diagnostics.Contracts; using System.IO; using System.Linq; using System.Reflection; +using System.Runtime.CompilerServices; using UELib.Annotations; +using UELib.Branch; +using UELib.Branch.UE2.AA2; +using UELib.Branch.UE2.DNF; +using UELib.Branch.UE3.APB; +using UELib.Branch.UE3.DD2; +using UELib.Branch.UE3.MOH; +using UELib.Branch.UE3.RSS; +using UELib.Branch.UE4; using UELib.Flags; namespace UELib { using Core; using Decoding; + using Branch.UE2.DVS; /// /// Represents the method that will handle the UELib.UnrealPackage.NotifyObjectAdded @@ -51,12 +61,14 @@ public sealed class UnrealRegisterClassAttribute : Attribute /// /// Represents data of a loaded unreal package. /// - public sealed class UnrealPackage : IDisposable, IBuffered + public sealed class UnrealPackage : IDisposable, IBinaryData { #region General Members + public BinaryMetaData BinaryMetaData { get; } = new BinaryMetaData(); + // Reference to the stream used when reading this package - public readonly UPackageStream Stream; + public UPackageStream Stream; /// /// The signature of a 'Unreal Package'. @@ -78,68 +90,9 @@ public sealed class UnrealPackage : IDisposable, IBuffered #endregion - #region Serialized Members - - public uint Version { get; set; } - - /// - /// For debugging purposes. Change this to override the present Version deserialized from the package. - /// - public static ushort OverrideVersion; - - #region Version history - - public const int VSIZEPREFIXDEPRECATED = 64; - public const int VINDEXDEPRECATED = 178; - public const int VCOOKEDPACKAGES = 277; - - /// - /// DLLBind(Name) - /// - public const int VDLLBIND = 655; - - /// - /// New class modifier "ClassGroup(Name[,Name])" - /// - public const int VCLASSGROUP = 789; - - private const int VCompression = 334; - private const int VEngineVersion = 245; - private const int VGroup = 269; - private const int VHeaderSize = 249; - private const int VPackageSource = 482; - private const int VAdditionalPackagesToCook = 516; - private const int VTextureAllocations = 767; - - #endregion - - public ushort LicenseeVersion { get; set; } - - /// - /// For debugging purposes. Change this to override the present Version deserialized from the package. - /// - public static ushort OverrideLicenseeVersion; - // TODO: Move to UnrealBuild.cs public sealed class GameBuild : object { - [UsedImplicitly] - [AttributeUsage(AttributeTargets.Field)] - private sealed class BuildDecoderAttribute : Attribute - { - private readonly Type _BuildDecoder; - - public BuildDecoderAttribute(Type buildDecoder) - { - _BuildDecoder = buildDecoder; - } - - public IBufferDecoder CreateDecoder() - { - return (IBufferDecoder)Activator.CreateInstance(_BuildDecoder); - } - } - [AttributeUsage(AttributeTargets.Field, AllowMultiple = true)] private sealed class BuildAttribute : Attribute { @@ -196,13 +149,13 @@ private sealed class BuildAttribute : Attribute Generation = gen; } - public bool Verify(GameBuild gb, UnrealPackage package) + public bool Verify(GameBuild gb, PackageFileSummary summary) { return _VerifyEqual - ? package.Version == _MinVersion && package.LicenseeVersion == _MinLicensee - : package.Version >= _MinVersion && package.Version <= _MaxVersion - && package.LicenseeVersion >= _MinLicensee - && package.LicenseeVersion <= _MaxLicensee; + ? summary.Version == _MinVersion && summary.LicenseeVersion == _MinLicensee + : summary.Version >= _MinVersion && summary.Version <= _MaxVersion + && summary.LicenseeVersion >= _MinLicensee + && summary.LicenseeVersion <= _MaxLicensee; } } @@ -215,28 +168,44 @@ public enum BuildName /// /// Standard + /// /// 61/000 /// - [Build(61, 0)] Unreal1, + [Build(61, 0, BuildGeneration.UE1)] Unreal1, /// /// Standard, Unreal Tournament & Deus Ex + /// /// 68:69/000 /// - [Build(68, 69, 0u, 0u)] UT, + [Build(68, 69, 0u, 0u, BuildGeneration.UE1)] + UT, + + //[Build(80, 0, BuildGeneration.UE1)] + BrotherBear, + + /// + /// Clive Barker's Undying + /// + /// 72:85/000 (Only 84 to 85 is auto detected, other versions are overlapping with older unrelated games) + /// + [Build(84, 85, 0u, 0u, BuildGeneration.UE1)] + Undying, /// /// Deus Ex: Invisible War + /// /// Missing support for custom classes such as BitfieldProperty and BitfieldEnum among others. /// 95/69 /// - [Build(95, 69, BuildGeneration.Thief)] DeusEx_IW, + [Build(95, 69, BuildGeneration.Flesh)] DeusEx_IW, /// /// Thief: Deadly Shadows + /// /// 95/133 /// - [Build(95, 133, BuildGeneration.Thief)] + [Build(95, 133, BuildGeneration.Flesh)] Thief_DS, /// @@ -246,6 +215,8 @@ public enum BuildName /// [Build(99, 117, 5u, 8u)] UT2003, + [Build(100, 17)] SC1, + /// /// 100/058 /// @@ -254,19 +225,27 @@ public enum BuildName /// /// 110/2609 /// - [Build(110, 2609)] Unreal2, + [Build(110, 110, 2481u, 2609u)] Unreal2, + + /// + /// 118:120/004:008 + /// + [BuildEngineBranch(typeof(EngineBranchDVS))] [Build(118, 120, 4u, 8u)] + Devastation, /// /// Tom Clancy's Rainbow Six 3: Raven Shield + /// /// 118/011:014 /// [Build(118, 118, 11u, 14u)] R6RS, /// /// Unreal II: eXpanded MultiPlayer + /// /// 126/000 /// - [Build(126, 0)] Unreal2XMP, + [Build(123, 126, 0u, 0u)] Unreal2XMP, /// /// 118:128/025:029 @@ -276,17 +255,38 @@ public enum BuildName UT2004, /// - /// Built on UT2004 + /// America's Army 2.X /// Represents both AAO and AAA + /// + /// Built on UT2004 /// 128/032:033 + /// + /// For now we have three AA2 versions defined here to help us distinguish the byte-code token map. /// - [Build(128, 128, 32u, 33u, BuildGeneration.UE2_5)] - AA2, + [Build(128, 32u, BuildGeneration.AGP)] [BuildEngineBranch(typeof(EngineBranchAA2))] + AA2_2_5, + + [Build(128, 32u, BuildGeneration.AGP)] [BuildEngineBranch(typeof(EngineBranchAA2))] + AA2_2_6, + + [Build(128, 33u, BuildGeneration.AGP)] [BuildEngineBranch(typeof(EngineBranchAA2))] + AA2_2_8, + + /// + /// Vanguard: Saga of Heroes + /// + /// 129/035 + /// Some packages have 128/025 but those are in conflict with UT2004. + /// + [Build(128, 129, 34u, 35u, BuildGeneration.UE2_5)] + Vanguard_SOH, // IrrationalGames/Vengeance - 129:143/027:059 /// /// Tribes: Vengeance + /// + /// 130/027 /// [Build(130, 27, BuildGeneration.Vengeance)] Tribes_VG, @@ -297,13 +297,29 @@ public enum BuildName [Build(129, 27, BuildGeneration.Vengeance)] Swat4, + /// + /// Lemony Snicket's A Series of Unfortunate Events + /// + /// 129/003 + /// + [Build(129, 3, BuildGeneration.UE2)] LSGame, + /// /// BioShock 1 & 2 + /// /// 130:143/056:059 /// [Build(130, 143, 56u, 59u, BuildGeneration.Vengeance)] BioShock, + /// + /// Duke Nukem Forever + /// + /// 156/036 + /// + [Build(156, 36u, BuildGeneration.UE2)] [BuildEngineBranch(typeof(EngineBranchDNF))] + DNF, + /// /// The Chronicles of Spellborn /// @@ -315,11 +331,15 @@ public enum BuildName Spellborn, /// + /// Standard + /// /// 369/006 /// [Build(369, 6)] RoboBlitz, /// + /// Medal of Honor: Airborne + /// /// 421/011 /// [Build(421, 11)] MOHA, @@ -331,10 +351,18 @@ public enum BuildName MKKE, /// + /// Gears of War + /// /// 490/009 /// [Build(490, 9)] GoW1, + [Build(511, 039, BuildGeneration.HMS)] // The Bourne Conspiracy + [Build(511, 145, BuildGeneration.HMS)] // Transformers: War for Cybertron (PC version) + [Build(511, 144, BuildGeneration.HMS)] + // Transformers: War for Cybertron (PS3 and XBox 360 version) + Transformers, + /// /// 512/000 /// @@ -345,17 +373,27 @@ public enum BuildName /// [Build(536, 43)] MirrorsEdge, + /// + /// Transformers: Dark of the Moon + /// + [Build(537, 174, BuildGeneration.HMS)] Transformers2, + /// /// 539/091 /// - [Build(539, 91)] AlphaProtcol, + [Build(539, 91)] AlphaProtocol, /// + /// APB: All Points Bulletin & APB: Reloaded + /// /// 547/028:032 /// - [Build(547, 547, 28u, 32u)] APB, + [Build(547, 547, 28u, 32u)] [BuildEngineBranch(typeof(EngineBranchAPB))] + APB, /// + /// Standard, Gears of War 2 + /// /// 575/000 /// Xenon is enabled here, because the package is missing editor data, the editor data of UStruct is however still serialized. /// @@ -368,8 +406,9 @@ public enum BuildName [Build(576, 5)] CrimeCraft, /// - /// 576/021 + /// Batman: Arkham Asylum /// + /// 576/021 /// No Special support, but there's no harm in recognizing this build. /// [Build(576, 21)] Batman1, @@ -386,7 +425,7 @@ public enum BuildName /// XenonCooked is required to read the Xbox 360 packages. /// 581/058 /// - [Build(581, 58, BuildFlags.ConsoleCooked)] + [Build(581, 58, BuildFlags.ConsoleCooked)] [BuildEngineBranch(typeof(EngineBranchMOH))] MOH, /// @@ -411,16 +450,26 @@ public enum BuildName [Build(610, 14)] Tera, /// + /// DC Universe Online + /// /// 648/6405 /// [Build(648, 6405)] DCUO, - [Build(687, 111)] DungeonDefenders2, + /// + /// Dungeon Defenders 2 + /// + /// 687-688/111-117 + /// + [Build(687, 688, 111, 117)] [BuildEngineBranch(typeof(EngineBranchDD2))] + DD2, /// - /// 727/075 + /// BioShock Infinite + /// 727/075 (partially upgraded to 756 or higher) /// - [Build(727, 75)] Bioshock_Infinite, + [Build(727, 75)] [OverridePackageVersion((uint)PackageObjectLegacyVersion.SuperReferenceMovedToUStruct)] + Bioshock_Infinite, /// /// 742/029 @@ -440,6 +489,8 @@ public enum BuildName InfinityBlade, /// + /// Standard, Gears of War 3 + /// /// 828/000 /// [Build(828, 0, BuildFlags.ConsoleCooked)] @@ -476,28 +527,23 @@ public enum BuildName [Build(845, 120)] XCOM2WotC, /// + /// Transformers: Fall of Cybertron /// 846/181 /// - [Build(511, 039)] // The Bourne Conspiracy - [Build(511, 145)] // Transformers: War for Cybertron (PC version) - [Build(511, 144)] // Transformers: War for Cybertron (PS3 and XBox 360 version) - [Build(537, 174)] // Transformers: Dark of the Moon - [Build(846, 181, 2, 1)] - // FIXME: The serialized version is false, needs to be adjusted. - // Transformers: Fall of Cybertron - Transformers, + [Build(846, 181, BuildGeneration.HMS)] [OverridePackageVersion(587)] + Transformers3, /// - /// 860/004 + /// 860/002-004 /// - [Build(860, 4)] Hawken, + [Build(860, 860, 2, 4)] Hawken, /// /// Batman: Arkham City /// /// 805/101 /// - [Build(805, 101, BuildGeneration.RSS)] + [Build(805, 101)] [BuildEngineBranch(typeof(EngineBranchRSS))] Batman2, /// @@ -506,118 +552,161 @@ public enum BuildName /// 806/103 /// 807/137-138 /// - [Build(806, 103, BuildGeneration.RSS)] - [Build(807, 807, 137, 138, BuildGeneration.RSS)] + [Build(806, 103)] [Build(807, 807, 137, 138)] [BuildEngineBranch(typeof(EngineBranchRSS))] Batman3, /// /// 807/104 /// - [Build(807, 104, BuildGeneration.RSS)] + [Build(807, 104)] [BuildEngineBranch(typeof(EngineBranchRSS))] Batman3MP, - /// /// Batman: Arkham Knight /// /// 863/32995(227 & ~8000) /// - [Build(863, 32995, BuildGeneration.RSS)] - [OverridePackageVersion(863, 227)] + [Build(863, 32995)] [OverridePackageVersion(863, 227)] [BuildEngineBranch(typeof(EngineBranchRSS))] Batman4, /// + /// Rocket League + /// /// 867/009:032 /// Requires third-party decompression and decryption /// [Build(867, 868, 9u, 32u)] RocketLeague, /// - /// 904/009 + /// Battleborn + /// + /// 874/078 + /// + /// EngineVersion and CookerVersion are packed with the respective Licensee version. /// - [Build(904, 904, 09u, 014u)] SpecialForce2, + [Build(874, 78u)] Battleborn, + + /// + /// Special Force 2 + /// + /// 904/009 (Non-standard version, actual Epic version might be 692 or higher) + /// + [Build(904, 904, 09u, 014u)] + [OverridePackageVersion((uint)PackageObjectLegacyVersion.ProbeMaskReducedAndIgnoreMaskRemoved)] + SpecialForce2, } public BuildName Name { get; } - public uint Version { get; } - public uint? OverrideVersion { get; } - public uint LicenseeVersion { get; } - public ushort? OverrideLicenseeVersion { get; } + [Obsolete] public uint Version { get; } - /// - /// Is cooked for consoles. - /// - [Obsolete("See BuildFlags", true)] - public bool IsConsoleCompressed { get; } + [Obsolete] public uint LicenseeVersion { get; } - /// - /// Is cooked for Xenon(Xbox 360). Could be true on PC games. - /// - [Obsolete("See BuildFlags", true)] - public bool IsXenonCompressed { get; } + public uint? OverrideVersion { get; } + public ushort? OverrideLicenseeVersion { get; } public BuildGeneration Generation { get; } + [CanBeNull] public readonly Type EngineBranchType; - public readonly BuildFlags Flags; + [Obsolete("To be deprecated")] public readonly BuildFlags Flags; - public GameBuild(UnrealPackage package) + public GameBuild(uint overrideVersion, ushort overrideLicenseeVersion, BuildGeneration generation, + Type engineBranchType, + BuildFlags flags) { - if (UnrealConfig.Platform == UnrealConfig.CookedPlatform.Console) Flags |= BuildFlags.ConsoleCooked; + OverrideVersion = overrideVersion; + OverrideLicenseeVersion = overrideLicenseeVersion; + Generation = generation; + EngineBranchType = engineBranchType; + Flags = flags; + } - var builds = typeof(BuildName).GetFields(); - foreach (var build in builds) + public GameBuild(UnrealPackage package) + { + var buildInfo = FindBuildInfo(package, out var buildAttribute); + if (buildInfo == null) { - var buildAttributes = build.GetCustomAttributes(false); - var buildAttribute = buildAttributes.FirstOrDefault(attr => attr.Verify(this, package)); - if (buildAttribute == null) - continue; + Name = package.Summary.LicenseeVersion == 0 + ? BuildName.Default + : BuildName.Unknown; + return; + } - Version = package.Version; - LicenseeVersion = package.LicenseeVersion; - Flags = buildAttribute.Flags; + Name = (BuildName)Enum.Parse(typeof(BuildName), buildInfo.Name); + Version = package.Summary.Version; + LicenseeVersion = package.Summary.LicenseeVersion; + + if (buildAttribute != null) + { Generation = buildAttribute.Generation; + Flags = buildAttribute.Flags; + } - var overrideAttribute = build.GetCustomAttribute(false); - if (overrideAttribute != null) + var overrideAttribute = buildInfo.GetCustomAttribute(false); + if (overrideAttribute != null) + { + OverrideVersion = overrideAttribute.FixedVersion; + OverrideLicenseeVersion = overrideAttribute.FixedLicenseeVersion; + } + + var engineBranchAttribute = buildInfo.GetCustomAttribute(false); + if (engineBranchAttribute != null) + { + // We cannot create the instance here, because the instance itself may be dependent on GameBuild. + EngineBranchType = engineBranchAttribute.EngineBranchType; + } + } + + [CanBeNull] + private FieldInfo FindBuildInfo(UnrealPackage linker, [CanBeNull] out BuildAttribute buildAttribute) + { + buildAttribute = null; + + // Auto-detect + if (linker.BuildTarget == BuildName.Unset) + { + var builds = typeof(BuildName).GetFields(); + foreach (var build in builds) { - OverrideVersion = overrideAttribute.FixedVersion; - OverrideLicenseeVersion = overrideAttribute.FixedLicenseeVersion; - } + var buildAttributes = build.GetCustomAttributes(false); + buildAttribute = buildAttributes.FirstOrDefault(attr => attr.Verify(this, linker.Summary)); + if (buildAttribute == null) + continue; - Name = (BuildName)Enum.Parse(typeof(BuildName), build.Name); - if (package.Decoder != null) break; + return build; + } - var buildDecoderAttribute = build.GetCustomAttribute(false); - if (buildDecoderAttribute == null) - break; + return null; + } - package.Decoder = buildDecoderAttribute.CreateDecoder(); - break; + if (linker.BuildTarget != BuildName.Unknown) + { + string buildName = Enum.GetName(typeof(BuildName), linker.BuildTarget); + var build = typeof(BuildName).GetField(buildName); + return build; } - if (Name == BuildName.Unset) - Name = package.LicenseeVersion == 0 ? BuildName.Default : BuildName.Unknown; + return null; } 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 name) { - return b?.Name == name; + return b.Name == name; } public static bool operator !=(GameBuild b, BuildName name) { - return b?.Name != name; + return b.Name != name; } /// @@ -632,87 +721,320 @@ public override int GetHashCode() return (int)Name; } - public bool HasFlags(BuildFlags flags) + public override string ToString() { - return (Flags & flags) == flags; + return Name.ToString(); } } - public GameBuild Build { get; private set; } + public GameBuild.BuildName BuildTarget = GameBuild.BuildName.Unset; /// - /// Whether the package was serialized in BigEndian encoding. + /// The auto-detected (can be set before deserialization to override auto-detection). + /// This needs to be set to the correct build in order to load some game-specific data from packages. /// - public bool IsBigEndianEncoded { get; } + public GameBuild Build; /// - /// The bitflags of this package. + /// The branch that we are using to load the data contained within this package. /// - public uint PackageFlags; + public EngineBranch Branch; /// - /// Size of the Header. Basically points to the first Object in the package. + /// The platform that the cooker was cooking this package for. + /// Needs to be set to Console for decompressed .xxx packages etc. /// - public int HeaderSize { get; private set; } + public BuildPlatform CookerPlatform; - /// - /// The group the package is associated with in the Content Browser. - /// - public string Group; + public struct PackageFileEngineVersion : IUnrealDeserializableClass + { + public uint Major, Minor, Patch; + public uint Changelist; + public string Branch; - public struct TablesData : IUnrealSerializableClass + public void Deserialize(IUnrealStream stream) + { + Major = stream.ReadUInt16(); + Minor = stream.ReadUInt16(); + Patch = stream.ReadUInt16(); + Changelist = stream.ReadUInt32(); + Branch = stream.ReadText(); + } + + public override string ToString() + { + return $"{Major}.{Minor}.{Patch}"; + } + } + + public struct PackageFileSummary : IUnrealSerializableClass { - public int NamesCount; - public int NamesOffset { get; internal set; } + public uint Version; + public ushort LicenseeVersion; + + public uint UE4Version; + public uint UE4LicenseeVersion; - public int ExportsCount { get; internal set; } - public int ExportsOffset { get; internal set; } + public UnrealFlags PackageFlags; - public int ImportsCount { get; internal set; } - public int ImportsOffset { get; internal set; } + [Obsolete] + private const int VHeaderSize = 249; + public int HeaderSize; + [Obsolete] + private const int VFolderName = 269; + + /// + /// UPK content category e.g. Weapons, Sounds or Meshes. + /// + public string FolderName; + + public string LocalizationId; + + public int NameCount, NameOffset; + public int ExportCount, ExportOffset; + public int ImportCount, ImportOffset; + public int HeritageCount, HeritageOffset; + + /// + /// List of heritages. UE1 way of defining generations. + /// + public UArray Heritages; + + [Obsolete] private const int VDependsOffset = 415; public int DependsOffset; + public UGuid Guid; + public UArray Generations; + + private PackageFileEngineVersion PackageEngineVersion; + private PackageFileEngineVersion PackageCompatibleEngineVersion; + + [Obsolete] + private const int VEngineVersion = 245; + + [Obsolete] + public const int VCookerVersion = 277; + + public int EngineVersion; + public int CookerVersion; + + [Obsolete] + private const int VCompression = 334; + public uint CompressionFlags; + public UArray CompressedChunks; + + [Obsolete] + private const int VPackageSource = 482; + public uint PackageSource; + + [Obsolete] + private const int VAdditionalPackagesToCook = 516; + public UArray AdditionalPackagesToCook; + + [Obsolete] private const int VImportExportGuidsOffset = 623; public int ImportExportGuidsOffset; public int ImportGuidsCount; public int ExportGuidsCount; + [Obsolete] private const int VThumbnailTableOffset = 584; public int ThumbnailTableOffset; - public void Serialize(IUnrealStream stream) + [Obsolete] + private const int VTextureAllocations = 767; + + public int GatherableTextDataCount; + public int GatherableTextDataOffset; + + public int StringAssetReferencesCount; + public int StringAssetReferencesOffset; + + public int SearchableNamesOffset; + + public UGuid PersistentGuid; + public UGuid OwnerPersistentGuid; + + private void SetupBuild(UnrealPackage package) { - stream.Write(NamesCount); - stream.Write(NamesOffset); + // Auto-detect + if (package.Build == null) + { + package.Build = new GameBuild(package); + if (package.Build.Flags.HasFlag(BuildFlags.ConsoleCooked)) + { + package.CookerPlatform = BuildPlatform.Console; + } + } - stream.Write(ExportsCount); - stream.Write(ExportsOffset); + if (package.Build.OverrideVersion.HasValue) Version = package.Build.OverrideVersion.Value; + if (package.Build.OverrideLicenseeVersion.HasValue) + LicenseeVersion = package.Build.OverrideLicenseeVersion.Value; - stream.Write(ImportsCount); - stream.Write(ImportsOffset); + if (OverrideVersion != 0) Version = OverrideVersion; + if (OverrideLicenseeVersion != 0) LicenseeVersion = OverrideLicenseeVersion; + } - if (stream.Version >= 414) - stream.Write(DependsOffset); + // TODO: Re-use an instantiated branch if available and if the package's version and licensee are an identical match. + private void SetupBranch(UnrealPackage package) + { + if (package.Build.EngineBranchType != null) + { + package.Branch = (EngineBranch)Activator.CreateInstance(package.Build.EngineBranchType, + package.Build.Generation); + } + else if (package.Summary.UE4Version > 0) + { + package.Branch = new EngineBranchUE4(); + } + else + { + package.Branch = new DefaultEngineBranch(package.Build.Generation); + } + + package.Branch.Setup(package); + } + + public void Serialize(IUnrealStream stream) + { + throw new NotImplementedException(); } public void Deserialize(IUnrealStream stream) { + const short maxLegacyVersion = -7; + + // Read as one variable due Big Endian Encoding. + int legacyVersion = stream.ReadInt32(); + // FIXME: >= -7 is true for the game Quantum + if (legacyVersion < 0 && legacyVersion >= maxLegacyVersion) + { +#if UE4 + uint ue3Version = 0; + if (legacyVersion != -4) + { + ue3Version = stream.ReadUInt32(); + } + + UE4Version = stream.ReadUInt32(); + UE4LicenseeVersion = stream.ReadUInt32(); + + Version = ue3Version; + + // Really old, probably no longer in production files? Other than some UE4 assets found in the first public release + if (UE4Version >= 138 && UE4Version < 142) + { + stream.Skip(8); // CookedVersion, CookedLicenseeVersion + } + + if (legacyVersion <= -2) + { + // Read enum based version + if (legacyVersion == -2) + { + int count = stream.ReadInt32(); // Versions + stream.Skip(count * (4 + 4)); // Tag, Version + } + else if (legacyVersion >= -5) + { + int count = stream.ReadInt32(); + for (var i = 0; i < count; ++i) + { + stream.ReadStruct(out UGuid key); + stream.Read(out int version); + stream.Read(out string friendlyName); + } + } + else + { + int count = stream.ReadInt32(); + for (var i = 0; i < count; ++i) + { + stream.ReadStruct(out UGuid key); + stream.Read(out int version); + } + } + } +#else + throw new NotSupportedException("This version of the Unreal Engine 4 is not supported!"); +#endif + } + else + { + Version = (uint)legacyVersion; + } + + LicenseeVersion = (ushort)(Version >> 16); + Version &= 0xFFFFU; + Console.WriteLine("Package Version:" + Version + "/" + LicenseeVersion); + + SetupBuild(stream.Package); + Debug.Assert(stream.Package.Build != null); + Console.WriteLine("Build:" + stream.Package.Build); + + SetupBranch(stream.Package); + Debug.Assert(stream.Package.Branch != null); + Console.WriteLine("Branch:" + stream.Package.Branch); +#if BIOSHOCK + if (stream.Package.Build == GameBuild.BuildName.Bioshock_Infinite) + { + int unk = stream.ReadInt32(); + } +#endif +#if MKKE + if (stream.Package.Build == GameBuild.BuildName.MKKE) stream.Skip(8); +#endif +#if TRANSFORMERS + if (stream.Package.Build == BuildGeneration.HMS && + stream.LicenseeVersion >= 55) + { + if (stream.LicenseeVersion >= 181) stream.Skip(16); + + stream.Skip(4); + } +#endif + if (stream.Version >= VHeaderSize) + { + // Offset to the first class(not object) in the package. + HeaderSize = stream.ReadInt32(); + Console.WriteLine("Header Size: " + HeaderSize); + } + + if (stream.Version >= VFolderName) + { + FolderName = stream.ReadText(); + } + + PackageFlags = stream.ReadFlags32(); + Console.WriteLine("Package Flags:" + PackageFlags); #if HAWKEN if (stream.Package.Build == GameBuild.BuildName.Hawken && - stream.Package.LicenseeVersion >= 2) + stream.LicenseeVersion >= 2) stream.Skip(4); #endif - NamesCount = stream.ReadInt32(); - NamesOffset = stream.ReadInt32(); - ExportsCount = stream.ReadInt32(); - ExportsOffset = stream.ReadInt32(); + NameCount = stream.ReadInt32(); + NameOffset = stream.ReadInt32(); +#if UE4 + if (stream.UE4Version >= 516 && stream.Package.ContainsEditorData()) + { + LocalizationId = stream.ReadText(); + } + + if (stream.UE4Version >= 459) + { + GatherableTextDataCount = stream.ReadInt32(); + GatherableTextDataOffset = stream.ReadInt32(); + } +#endif + ExportCount = stream.ReadInt32(); + ExportOffset = stream.ReadInt32(); #if APB if (stream.Package.Build == GameBuild.BuildName.APB && - stream.Package.LicenseeVersion >= 28) + stream.LicenseeVersion >= 28) { - if (stream.Package.LicenseeVersion >= 29) + if (stream.LicenseeVersion >= 29) { stream.Skip(4); } @@ -720,33 +1042,65 @@ public void Deserialize(IUnrealStream stream) stream.Skip(20); } #endif - ImportsCount = stream.ReadInt32(); - ImportsOffset = stream.ReadInt32(); + ImportCount = stream.ReadInt32(); + ImportOffset = stream.ReadInt32(); - Console.WriteLine("Names Count:" + NamesCount + " Names Offset:" + NamesOffset - + " Exports Count:" + ExportsCount + " Exports Offset:" + ExportsOffset - + " Imports Count:" + ImportsCount + " Imports Offset:" + ImportsOffset + Console.WriteLine("Names Count:" + NameCount + " Names Offset:" + NameOffset + + " Exports Count:" + ExportCount + " Exports Offset:" + ExportOffset + + " Imports Count:" + ImportCount + " Imports Offset:" + ImportOffset ); + if (stream.Version < 68) + { + HeritageCount = stream.ReadInt32(); + Contract.Assert(HeritageCount > 0); + HeritageOffset = stream.ReadInt32(); + return; + } + if (stream.Version >= VDependsOffset) { DependsOffset = stream.ReadInt32(); } - -#if TRANSFORMERS - if (stream.Package.Build == GameBuild.BuildName.Transformers - && stream.Version < 535) +#if THIEF_DS || DEUSEX_IW + if (stream.Package.Build == GameBuild.BuildName.Thief_DS || + stream.Package.Build == GameBuild.BuildName.DeusEx_IW) { - return; + //stream.Skip( 4 ); + int unknown = stream.ReadInt32(); + Console.WriteLine("Unknown:" + unknown); } #endif - if (stream.Version >= VImportExportGuidsOffset +#if SPLINTERCELL + if (stream.Package.Build == GameBuild.BuildName.SC1 && + stream.LicenseeVersion >= 12) + { + // compiled-constant: 0xff0adde + stream.Read(out int uStack_10c); + + // An FString converted to an FArray? Concatenating appUserName, appComputerName, appBaseDir, and appTimestamp. + stream.ReadArray(out UArray iStack_fc); + } +#endif +#if BORDERLANDS + if (stream.Package.Build == GameBuild.BuildName.Borderlands) stream.Skip(4); +#endif + if (stream.UE4Version >= 384) + { + StringAssetReferencesCount = stream.ReadInt32(); + StringAssetReferencesOffset = stream.ReadInt32(); + } + + if (stream.UE4Version >= 510) + { + SearchableNamesOffset = stream.ReadInt32(); + } + + if (stream.Version >= VImportExportGuidsOffset && + stream.UE4Version == 0 // FIXME: Correct the output version of these games instead. #if BIOSHOCK && stream.Package.Build != GameBuild.BuildName.Bioshock_Infinite -#endif -#if TRANSFORMERS - && stream.Package.Build != GameBuild.BuildName.Transformers #endif ) { @@ -754,70 +1108,293 @@ public void Deserialize(IUnrealStream stream) ImportGuidsCount = stream.ReadInt32(); ExportGuidsCount = stream.ReadInt32(); } - +#if TRANSFORMERS + if (stream.Package.Build == BuildGeneration.HMS && + stream.Version >= 535) + { + // ThumbnailTableOffset? But if so, the partial-upgrade must have skipped @AdditionalPackagesToCook + stream.Skip(4); + return; + } +#endif +#if DD2 + // No version check found in the .exe + if (stream.Package.Build == GameBuild.BuildName.DD2 && PackageFlags.HasFlag(PackageFlag.Cooked)) + stream.Skip(4); +#endif if (stream.Version >= VThumbnailTableOffset) { + ThumbnailTableOffset = stream.ReadInt32(); + } +#if MKKE + if (stream.Package.Build == GameBuild.BuildName.MKKE) stream.Skip(4); +#endif +#if SPELLBORN + if (stream.Package.Build == GameBuild.BuildName.Spellborn + && stream.Version >= 148) + goto skipGuid; +#endif + stream.ReadStruct(out Guid); + Console.WriteLine("GUID:" + Guid); + skipGuid: +#if TERA + if (stream.Package.Build == GameBuild.BuildName.Tera) stream.Position -= 4; +#endif +#if MKKE + if (stream.Package.Build != GameBuild.BuildName.MKKE) + { +#endif + int generationCount = stream.ReadInt32(); + Contract.Assert(generationCount >= 0); + Console.WriteLine("Generations Count:" + generationCount); #if APB - if (stream.Package.Build == GameBuild.BuildName.DungeonDefenders2) stream.Skip(4); + // Guid, however only serialized for the first generation item. + if (stream.Package.Build == GameBuild.BuildName.APB && + stream.LicenseeVersion >= 32) + { + stream.Skip(16); + } #endif - ThumbnailTableOffset = stream.ReadInt32(); + stream.ReadArray(out Generations, generationCount); +#if MKKE + } +#endif +#if DNF + if (stream.Package.Build == GameBuild.BuildName.DNF && + stream.Version >= 151) + { + if (PackageFlags.HasFlags(0x20U)) + { + int buildMonth = stream.ReadInt32(); + int buildYear = stream.ReadInt32(); + int buildDay = stream.ReadInt32(); + int buildSeconds = stream.ReadInt32(); + } + + string dnfString = stream.ReadText(); + + // DLC package + if (PackageFlags.HasFlags(0x80U)) + { + // No additional data, just DLC authentication. + } + } +#endif + if (stream.Version >= VEngineVersion && + stream.UE4Version == 0) + { + // The Engine Version this package was created with + EngineVersion = stream.ReadInt32(); + Console.WriteLine("\tEngineVersion:" + EngineVersion); + } +#if UE4 + if (stream.Package.ContainsEditorData()) + { + if (stream.UE4Version >= 518) + { + stream.ReadStruct(out PersistentGuid); + if (stream.UE4Version < 520) + { + stream.ReadStruct(out OwnerPersistentGuid); + } + } + } + + if (stream.UE4Version >= 336) + { + // EngineVersion + PackageEngineVersion = new PackageFileEngineVersion(); + PackageEngineVersion.Deserialize(stream); + } + + if (stream.UE4Version >= 444) + { + // Compatible EngineVersion + PackageCompatibleEngineVersion = new PackageFileEngineVersion(); + PackageCompatibleEngineVersion.Deserialize(stream); + } +#endif + if (stream.Version >= VCookerVersion && + stream.UE4Version == 0) + { + // The Cooker Version this package was cooked with + CookerVersion = stream.ReadInt32(); + Console.WriteLine("CookerVersion:" + CookerVersion); + } + + // Read compressed info? + if (stream.Version >= VCompression) + { + CompressionFlags = stream.ReadUInt32(); + Console.WriteLine("CompressionFlags:" + CompressionFlags); + stream.ReadArray(out CompressedChunks); } - // Generations - // ... etc, see Deserialize() below - } - } + if (stream.Version >= VPackageSource) + { + PackageSource = stream.ReadUInt32(); + Console.WriteLine("PackageSource:" + PackageSource); + } +#if UE4 + if (stream.UE4Version > 0) + return; +#endif + if (stream.Version >= VAdditionalPackagesToCook) + { +#if TRANSFORMERS + if (stream.Package.Build == BuildGeneration.HMS) + { + return; + } +#endif + stream.ReadArray(out AdditionalPackagesToCook); +#if DCUO + if (stream.Package.Build == GameBuild.BuildName.DCUO) + { + var realNameOffset = (int)stream.Position; + Debug.Assert( + realNameOffset <= NameOffset, + "realNameOffset is > the parsed name offset for a DCUO package, we don't know where to go now!" + ); + + int offsetDif = NameOffset - realNameOffset; + NameOffset -= offsetDif; + ImportOffset -= offsetDif; + ExportOffset -= offsetDif; + DependsOffset = 0; // not working + ImportExportGuidsOffset -= offsetDif; + ThumbnailTableOffset -= offsetDif; + } +#endif + } +#if BORDERLANDS + if (stream.Package.Build == GameBuild.BuildName.Battleborn) + { + // FIXME: Package format is being deserialzied incorrectly and fails here. + stream.ReadUInt32(); + return; + } +#endif + if (stream.Version >= VTextureAllocations) + { + // TextureAllocations, TextureTypes + int count = stream.ReadInt32(); + for (var i = 0; i < count; i++) + { + stream.ReadInt32(); + stream.ReadInt32(); + stream.ReadInt32(); + stream.ReadUInt32(); + stream.ReadUInt32(); + int count2 = stream.ReadInt32(); + stream.Skip(count2 * 4); + } + } +#if ROCKETLEAGUE + if (stream.Package.Build == GameBuild.BuildName.RocketLeague + && PackageFlags.HasFlag(PackageFlag.Cooked)) + { + int garbageSize = stream.ReadInt32(); + Debug.WriteLine(garbageSize, "GarbageSize"); + int compressedChunkInfoOffset = stream.ReadInt32(); + Debug.WriteLine(compressedChunkInfoOffset, "CompressedChunkInfoOffset"); + int lastBlockSize = stream.ReadInt32(); + Debug.WriteLine(lastBlockSize, "LastBlockSize"); + Debug.Assert(stream.Position == NameOffset, "There is more data before the NameTable"); + // Data after this is encrypted + } +#endif + } + } + + public PackageFileSummary Summary; + + /// + /// Whether the package was serialized in BigEndian encoding. + /// + public bool IsBigEndianEncoded { get; } + + [Obsolete] + public const int VSIZEPREFIXDEPRECATED = 64; + + [Obsolete] + public const int VINDEXDEPRECATED = 178; + + [Obsolete] + public const int VDLLBIND = 655; + + [Obsolete] + public const int VCLASSGROUP = 789; + + [Obsolete] + public const int VCOOKEDPACKAGES = 277; + + public uint Version => Summary.Version; + + /// + /// For debugging purposes. Change this to override the present Version deserialized from the package. + /// + public static ushort OverrideVersion; + + public ushort LicenseeVersion => Summary.LicenseeVersion; - private TablesData _TablesData; - public TablesData Summary => _TablesData; + /// + /// For debugging purposes. Change this to override the present Version deserialized from the package. + /// + public static ushort OverrideLicenseeVersion; /// - /// The guid of this package. Used to test if the package on a client is equal to the one on a server. + /// The bitflags of this package. /// - [PublicAPI] - public string GUID { get; private set; } + [Obsolete("See Summary.PackageFlags")] public uint PackageFlags; /// - /// List of heritages. UE1 way of defining generations. + /// Size of the Header. Basically points to the first Object in the package. /// - private IList _Heritages; + [Obsolete("See Summary.HeaderSize")] + public int HeaderSize => Summary.HeaderSize; /// - /// List of package generations. + /// The group the package is associated with in the Content Browser. /// - [PublicAPI] - public UArray Generations => _Generations; + [Obsolete("See Summary.FolderName")] public string Group; + + /// + /// The guid of this package. Used to test if the package on a client is equal to the one on a server. + /// + [Obsolete("See Summary.Guid")] + public string GUID => Summary.Guid.ToString(); - private UArray _Generations; + /// + /// List of package generations. + /// + [Obsolete("See Summary.Generations")] + public UArray Generations => Summary.Generations; /// /// The Engine version the package was created with. /// - [DefaultValue(-1)] - [PublicAPI] - public int EngineVersion { get; private set; } + [Obsolete("See Summary.EngineVersion")] + public int EngineVersion => Summary.EngineVersion; /// /// The Cooker version the package was cooked with. /// - [PublicAPI] - public int CookerVersion { get; private set; } + [Obsolete("See Summary.CookerVersion")] + public int CookerVersion => Summary.CookerVersion; /// /// The type of compression the package is compressed with. /// - [PublicAPI] - public uint CompressionFlags { get; private set; } + [Obsolete("See Summary.CompressionFlags")] + public uint CompressionFlags => Summary.CompressionFlags; /// /// List of compressed chunks throughout the package. /// Null if package version less is than /// - [PublicAPI("UE Explorer requires 'get'")] - [CanBeNull] - public UArray CompressedChunks => _CompressedChunks; - - [CanBeNull] private UArray _CompressedChunks; + [Obsolete("See Summary.CompressedChunks")] + public UArray CompressedChunks => Summary.CompressedChunks; /// /// List of unique unreal names. @@ -842,8 +1419,6 @@ public void Deserialize(IUnrealStream stream) /// //public List Dependencies{ get; private set; } - #endregion - #region Initialized Members /// @@ -861,7 +1436,8 @@ public void Deserialize(IUnrealStream stream) [PublicAPI] public NativesTablePackage NTLPackage; - [PublicAPI] public IBufferDecoder Decoder; + [Obsolete("See UPackageStream.Decoder", true)] + public IBufferDecoder Decoder; #endregion @@ -909,386 +1485,104 @@ public UnrealPackage(UPackageStream stream) public void Serialize(IUnrealStream stream) { - // Serialize tables - var namesBuffer = new UObjectStream(stream); - foreach (var name in Names) name.Serialize(namesBuffer); - - var importsBuffer = new UObjectStream(stream); - foreach (var import in Imports) import.Serialize(importsBuffer); - - var exportsBuffer = new UObjectStream(stream); - foreach (var export in Exports) export.Serialize(exportsBuffer); - - stream.Seek(0, SeekOrigin.Begin); - stream.Write(Signature); - - // Serialize header - int version = (int)(Version & 0x0000FFFFU) | (LicenseeVersion << 16); - stream.Write(version); - - long headerSizePosition = stream.Position; - if (Version >= VHeaderSize) - { - stream.Write(HeaderSize); - if (Version >= VGroup) stream.Write(Group); - } - - stream.Write(PackageFlags); - - _TablesData.NamesCount = Names.Count; - _TablesData.ExportsCount = Exports.Count; - _TablesData.ImportsCount = Imports.Count; - - long tablesDataPosition = stream.Position; - _TablesData.Serialize(stream); - - // TODO: Serialize Heritages - - stream.Write(Guid.NewGuid().ToByteArray(), 0, 16); - //Generations.Serialize(stream); - - if (Version >= VEngineVersion) - { - stream.Write(EngineVersion); - if (Version >= VCOOKEDPACKAGES) - { - stream.Write(CookerVersion); - - if (Version >= VCompression) - if (IsCooked()) - stream.Write(CompressionFlags); - //CompressedChunks.Serialize(stream); - } - } - - // TODO: Unknown data - stream.Write((uint)0); - - // Serialize objects - - // Write tables - - // names - Console.WriteLine("Writing names at position " + stream.Position); - _TablesData.NamesOffset = (int)stream.Position; - byte[] namesBytes = namesBuffer.GetBuffer(); - stream.Write(namesBytes, 0, (int)namesBuffer.Length); - - // imports - Console.WriteLine("Writing imports at position " + stream.Position); - _TablesData.ImportsOffset = (int)stream.Position; - byte[] importsBytes = importsBuffer.GetBuffer(); - stream.Write(importsBytes, 0, (int)importsBuffer.Length); - - // exports - Console.WriteLine("Writing exports at position " + stream.Position); - - // Serialize tables data again now that offsets are known. - long currentPosition = stream.Position; - stream.Seek(tablesDataPosition, SeekOrigin.Begin); - _TablesData.Serialize(stream); - stream.Seek(currentPosition, SeekOrigin.Begin); + throw new NotImplementedException(); } - // TODO: Move to FilePackageSummary, but first we want to merge the support-* branches public void Deserialize(UPackageStream stream) { - // Read as one variable due Big Endian Encoding. - Version = stream.ReadUInt32(); - LicenseeVersion = (ushort)(Version >> 16); - Version &= 0xFFFFU; - Console.WriteLine("Package Version:" + Version + "/" + LicenseeVersion); - SetupBuild(stream); - stream.BuildDetected(Build); - Console.WriteLine("Build:" + Build.Name); - - if (Version >= VHeaderSize) - { -#if BIOSHOCK - if (Build == GameBuild.BuildName.Bioshock_Infinite) - { - int unk = stream.ReadInt32(); - } -#endif -#if MKKE - if (Build == GameBuild.BuildName.MKKE) stream.Skip(8); -#endif -#if TRANSFORMERS - if (Build == GameBuild.BuildName.Transformers - && LicenseeVersion >= 55) - { - if (LicenseeVersion >= 181) stream.Skip(16); - - stream.Skip(4); - } -#endif - // Offset to the first class(not object) in the package. - HeaderSize = stream.ReadInt32(); - Console.WriteLine("Header Size: " + HeaderSize); - } - - if (Version >= VGroup) - { - // UPK content category e.g. Weapons, Sounds or Meshes. - Group = stream.ReadText(); - } - - // Bitflags such as AllowDownload. - PackageFlags = stream.ReadUInt32(); - Console.WriteLine("Package Flags:" + PackageFlags); - - // Summary data such as ObjectCount. - _TablesData = new TablesData(); - _TablesData.Deserialize(stream); - if (Version < 68) - { - int heritageCount = stream.ReadInt32(); - int heritageOffset = stream.ReadInt32(); - - stream.Seek(heritageOffset, SeekOrigin.Begin); - _Heritages = new List(heritageCount); - for (var i = 0; i < heritageCount; ++i) _Heritages.Add(stream.ReadUShort()); - } - else - { -#if THIEF_DS || DEUSEX_IW - if (Build == GameBuild.BuildName.Thief_DS || - Build == GameBuild.BuildName.DeusEx_IW) - { - //stream.Skip( 4 ); - int unknown = stream.ReadInt32(); - Console.WriteLine("Unknown:" + unknown); - } -#endif -#if BORDERLANDS - if (Build == GameBuild.BuildName.Borderlands) stream.Skip(4); -#endif -#if MKKE - if (Build == GameBuild.BuildName.MKKE) stream.Skip(4); -#endif - if (Build == GameBuild.BuildName.Spellborn - && stream.Version >= 148) - goto skipGuid; - GUID = stream.ReadGuid().ToString(); - Console.WriteLine("GUID:" + GUID); - skipGuid: -#if TERA - if (Build == GameBuild.BuildName.Tera) stream.Position -= 4; -#endif -#if MKKE - if (Build != GameBuild.BuildName.MKKE) - { -#endif - int generationCount = stream.ReadInt32(); - Console.WriteLine("Generations Count:" + generationCount); -#if APB - // Guid, however only serialized for the first generation item. - if (stream.Package.Build == GameBuild.BuildName.APB && - stream.Package.LicenseeVersion >= 32) - { - stream.Skip(16); - } -#endif - stream.ReadArray(out _Generations, generationCount); -#if MKKE - } -#endif - if (Version >= VEngineVersion) - { - // The Engine Version this package was created with - EngineVersion = stream.ReadInt32(); - Console.WriteLine("EngineVersion:" + EngineVersion); - } - - if (Version >= VCOOKEDPACKAGES) - { - // The Cooker Version this package was cooked with - CookerVersion = stream.ReadInt32(); - Console.WriteLine("CookerVersion:" + CookerVersion); - } - - // Read compressed info? - if (Version >= VCompression) - { - CompressionFlags = stream.ReadUInt32(); - Console.WriteLine("CompressionFlags:" + CompressionFlags); - stream.ReadArray(out _CompressedChunks); - } - - if (Version >= VPackageSource) - { - uint packageSource = stream.ReadUInt32(); - Console.WriteLine("PackageSource:" + packageSource); - } - - if (Version >= VAdditionalPackagesToCook) - { - UArray additionalPackagesToCook; - stream.ReadArray(out additionalPackagesToCook); -#if DCUO - if (Build == GameBuild.BuildName.DCUO) - { - var realNameOffset = (int)stream.Position; - Debug.Assert( - realNameOffset <= _TablesData.NamesOffset, - "realNameOffset is > the parsed name offset for a DCUO package, we don't know where to go now!" - ); + Summary = new PackageFileSummary(); + Summary.Deserialize(stream); + BinaryMetaData.AddField(nameof(Summary), Summary, 0, stream.Position); - int offsetDif = _TablesData.NamesOffset - realNameOffset; - _TablesData.NamesOffset -= offsetDif; - _TablesData.ImportsOffset -= offsetDif; - _TablesData.ExportsOffset -= offsetDif; - _TablesData.DependsOffset = 0; // not working - _TablesData.ImportExportGuidsOffset -= offsetDif; - _TablesData.ThumbnailTableOffset -= offsetDif; - } -#endif - } + // FIXME: For backwards compatibility. + PackageFlags = (uint)Summary.PackageFlags; + Group = Summary.FolderName; + Branch.PostDeserializeSummary(this, stream, ref Summary); + Debug.Assert(Branch.Serializer != null, + "Branch.Serializer cannot be null. Did you forget to initialize the Serializer in PostDeserializeSummary?"); - if (Version >= VTextureAllocations) - { - // TextureAllocations, TextureTypes - int count = stream.ReadInt32(); - for (var i = 0; i < count; i++) - { - stream.ReadInt32(); - stream.ReadInt32(); - stream.ReadInt32(); - stream.ReadUInt32(); - stream.ReadUInt32(); - int count2 = stream.ReadInt32(); - stream.Skip(count2 * 4); - } - } - } -#if ROCKETLEAGUE - if (Build == GameBuild.BuildName.RocketLeague - && IsCooked()) - { - int garbageSize = stream.ReadInt32(); - Debug.WriteLine(garbageSize, "GarbageSize"); - int compressedChunkInfoOffset = stream.ReadInt32(); - Debug.WriteLine(compressedChunkInfoOffset, "CompressedChunkInfoOffset"); - int lastBlockSize = stream.ReadInt32(); - Debug.WriteLine(lastBlockSize, "LastBlockSize"); - Debug.Assert(stream.Position == _TablesData.NamesOffset, "There is more data before the NameTable"); - // Data after this is encrypted - } -#endif // We can't continue without decompressing. - if (CompressionFlags != 0 || (_CompressedChunks != null && _CompressedChunks.Any())) + if (CompressedChunks != null && CompressedChunks.Any()) { - // HACK: To fool UE Explorer - if (_CompressedChunks.Capacity == 0) _CompressedChunks.Capacity = 1; return; } -#if AA2 - if (Build == GameBuild.BuildName.AA2 - // Note: Never true, AA2 is not a detected build for packages with LicenseeVersion 27 or less - // But we'll preserve this nonetheless - && LicenseeVersion >= 19) - { - bool isEncrypted = stream.ReadInt32() > 0; - if (isEncrypted) - { - // TODO: Use a stream wrapper instead; but this is blocked by an overly intertwined use of PackageStream. - if (LicenseeVersion >= 33) - { - var decoder = new CryptoDecoderAA2(); - Decoder = decoder; - } - else - { - var decoder = new CryptoDecoderWithKeyAA2(); - Decoder = decoder; - - long nonePosition = _TablesData.NamesOffset; - stream.Seek(nonePosition, SeekOrigin.Begin); - byte scrambledNoneLength = stream.ReadByte(); - decoder.Key = scrambledNoneLength; - stream.Seek(nonePosition, SeekOrigin.Begin); - byte unscrambledNoneLength = stream.ReadByte(); - Debug.Assert((unscrambledNoneLength & 0x3F) == 5); - } - } - - // Always one - //int unkCount = stream.ReadInt32(); - //for (var i = 0; i < unkCount; i++) - //{ - // // All zero - // stream.Skip(24); - // // Always identical to the package's GUID - // var guid = stream.ReadGuid(); - //} - - //// Always one - //int unk2Count = stream.ReadInt32(); - //for (var i = 0; i < unk2Count; i++) - //{ - // // All zero - // stream.Skip(12); - //} - } -#endif - // Read the name table #if TERA - if (Build == GameBuild.BuildName.Tera) _TablesData.NamesCount = Generations.Last().NamesCount; + if (Build == GameBuild.BuildName.Tera) Summary.NameCount = Generations.Last().NameCount; #endif - if (_TablesData.NamesCount > 0) + // Read the name table + if (Summary.NameCount > 0) { - stream.Seek(_TablesData.NamesOffset, SeekOrigin.Begin); - Names = new List(_TablesData.NamesCount); - for (var i = 0; i < _TablesData.NamesCount; ++i) + stream.Seek(Summary.NameOffset, SeekOrigin.Begin); + Names = new List(Summary.NameCount); + for (var i = 0; i < Summary.NameCount; ++i) { var nameEntry = new UNameTableItem { Offset = (int)stream.Position, Index = i }; - nameEntry.Deserialize(stream); + Branch.Serializer.Deserialize(stream, nameEntry); nameEntry.Size = (int)(stream.Position - nameEntry.Offset); Names.Add(nameEntry); } + + BinaryMetaData.AddField(nameof(Names), Names, Summary.NameOffset, stream.Position - Summary.NameOffset); + #if SPELLBORN // WTF were they thinking? Change DRFORTHEWIN to None if (Build == GameBuild.BuildName.Spellborn && Names[0].Name == "DRFORTHEWIN") Names[0].Name = "None"; // False?? - //Debug.Assert(stream.Position == _TablesData.ImportsOffset); + //Debug.Assert(stream.Position == Summary.ImportsOffset); #endif } + // Read Heritages + if (Summary.HeritageCount > 0) + { + stream.Seek(Summary.HeritageOffset, SeekOrigin.Begin); + stream.ReadArray(out Summary.Heritages, Summary.HeritageCount); + + BinaryMetaData.AddField(nameof(Summary.Heritages), Summary.Heritages, Summary.HeritageOffset, + stream.Position - Summary.HeritageOffset); + } + // Read Import Table - if (_TablesData.ImportsCount > 0) + if (Summary.ImportCount > 0) { - stream.Seek(_TablesData.ImportsOffset, SeekOrigin.Begin); - Imports = new List(_TablesData.ImportsCount); - for (var i = 0; i < _TablesData.ImportsCount; ++i) + stream.Seek(Summary.ImportOffset, SeekOrigin.Begin); + Imports = new List(Summary.ImportCount); + for (var i = 0; i < Summary.ImportCount; ++i) { var imp = new UImportTableItem { Offset = (int)stream.Position, Index = i, Owner = this }; - imp.Deserialize(stream); + Branch.Serializer.Deserialize(stream, imp); imp.Size = (int)(stream.Position - imp.Offset); Imports.Add(imp); } + + BinaryMetaData.AddField(nameof(Imports), Imports, Summary.ImportOffset, + stream.Position - Summary.ImportOffset); } // Read Export Table - if (_TablesData.ExportsCount > 0) + if (Summary.ExportCount > 0) { - stream.Seek(_TablesData.ExportsOffset, SeekOrigin.Begin); - Exports = new List(_TablesData.ExportsCount); - for (var i = 0; i < _TablesData.ExportsCount; ++i) + stream.Seek(Summary.ExportOffset, SeekOrigin.Begin); + Exports = new List(Summary.ExportCount); + for (var i = 0; i < Summary.ExportCount; ++i) { var exp = new UExportTableItem { Offset = (int)stream.Position, Index = i, Owner = this }; - exp.Deserialize(stream); + Branch.Serializer.Deserialize(stream, exp); exp.Size = (int)(stream.Position - exp.Offset); Exports.Add(exp); } - if (_TablesData.DependsOffset > 0) + BinaryMetaData.AddField(nameof(Exports), Exports, Summary.ExportOffset, + stream.Position - Summary.ExportOffset); + + if (Summary.DependsOffset > 0) { try { - stream.Seek(_TablesData.DependsOffset, SeekOrigin.Begin); - int dependsCount = _TablesData.ExportsCount; + stream.Seek(Summary.DependsOffset, SeekOrigin.Begin); + int dependsCount = Summary.ExportCount; #if BIOSHOCK // FIXME: Version? if (Build == GameBuild.BuildName.Bioshock_Infinite) @@ -1309,6 +1603,9 @@ public void Deserialize(UPackageStream stream) dependsMap.Add(imports); } + + BinaryMetaData.AddField(nameof(dependsMap), dependsMap, Summary.DependsOffset, + stream.Position - Summary.DependsOffset); } catch (Exception ex) { @@ -1322,22 +1619,28 @@ public void Deserialize(UPackageStream stream) } } - if (_TablesData.ImportExportGuidsOffset > 0) + if (Summary.ImportExportGuidsOffset > 0) { try { - for (var i = 0; i < _TablesData.ImportGuidsCount; ++i) + for (var i = 0; i < Summary.ImportGuidsCount; ++i) { string levelName = stream.ReadText(); int guidCount = stream.ReadInt32(); stream.Skip(guidCount * 16); } - for (var i = 0; i < _TablesData.ExportGuidsCount; ++i) + for (var i = 0; i < Summary.ExportGuidsCount; ++i) { - var objectGuid = stream.ReadGuid(); + stream.ReadStruct(out UGuid objectGuid); int exportIndex = stream.ReadInt32(); } + + if (stream.Position != Summary.ImportExportGuidsOffset) + { + BinaryMetaData.AddField("ImportExportGuids", null, Summary.ImportExportGuidsOffset, + stream.Position - Summary.ImportExportGuidsOffset); + } } catch (Exception ex) { @@ -1345,17 +1648,19 @@ public void Deserialize(UPackageStream stream) Console.Error.WriteLine("Couldn't parse ImportExportGuidsTable"); Console.Error.WriteLine(ex.ToString()); #if STRICT - throw new UnrealException("Couldn't parse ImportExportGuidsTable", ex); + throw new UnrealException("Couldn't parse ImportExportGuidsTable", ex); #endif } } - if (_TablesData.ThumbnailTableOffset != 0) + if (Summary.ThumbnailTableOffset != 0) { try { int thumbnailCount = stream.ReadInt32(); // TODO: Serialize + BinaryMetaData.AddField("Thumbnails", null, Summary.ThumbnailTableOffset, + stream.Position - Summary.ThumbnailTableOffset); } catch (Exception ex) { @@ -1369,18 +1674,9 @@ public void Deserialize(UPackageStream stream) } Debug.Assert(stream.Position <= int.MaxValue); - HeaderSize = (int)stream.Position; - } - - private void SetupBuild(UPackageStream stream) - { - Build = new GameBuild(this); + if (Summary.HeaderSize == 0) Summary.HeaderSize = (int)stream.Position; - 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; + Branch.PostDeserializePackage(stream.Package, stream); } /// @@ -1404,7 +1700,7 @@ public void InitializeExportObjects(InitFlags initFlags = InitFlags.All) /// Constructs all import objects. /// /// If TRUE initialize all constructed objects. - [PublicAPI] + [Obsolete("Pending deprecation")] public void InitializeImportObjects(bool initialize = true) { Objects = new List(Imports.Count); @@ -1425,7 +1721,10 @@ public void InitializePackage(InitFlags initFlags = InitFlags.All) { if ((initFlags & InitFlags.RegisterClasses) != 0) RegisterExportedClassTypes(); - if ((initFlags & InitFlags.Construct) == 0) return; + if ((initFlags & InitFlags.Construct) == 0) + { + return; + } ConstructObjects(); if ((initFlags & InitFlags.Deserialize) == 0) @@ -1435,21 +1734,19 @@ public void InitializePackage(InitFlags initFlags = InitFlags.All) { DeserializeObjects(); } - catch + catch (Exception ex) { - throw new DeserializingObjectsException(); + throw new UnrealException("Deserialization", ex); } try { if ((initFlags & InitFlags.Link) != 0) LinkObjects(); } - catch + catch (Exception ex) { - throw new LinkingObjectsException(); + throw new UnrealException("Linking", ex); } - - DisposeStream(); } /// @@ -1577,13 +1874,15 @@ private void LinkObjects() try { if (!(exp.Object is UnknownObject)) exp.Object.PostInitialize(); - - OnNotifyPackageEvent(new PackageEventArgs(PackageEventArgs.Id.Object)); } catch (InvalidCastException) { Console.WriteLine("InvalidCastException occurred on object: " + exp.Object); } + finally + { + OnNotifyPackageEvent(new PackageEventArgs(PackageEventArgs.Id.Object)); + } } private void RegisterExportedClassTypes() @@ -1600,13 +1899,45 @@ private void RegisterExportedClassTypes() #region Methods - private void CreateObject(UObjectTableItem table) + // Create pseudo objects for imports so that we have non-null references to imports. + private void CreateObject(UImportTableItem item) + { + var classType = GetClassType(item.ClassName); + item.Object = (UObject)Activator.CreateInstance(classType); + AddObject(item.Object, item); + OnNotifyPackageEvent(new PackageEventArgs(PackageEventArgs.Id.Object)); + } + + private void CreateObject(UExportTableItem item) { - var classType = GetClassType(table.ClassName); - table.Object = classType == null - ? new UnknownObject() - : (UObject)Activator.CreateInstance(classType); - AddObject(table.Object, table); + var objectClass = item.Class; + var classType = GetClassType(objectClass != null ? objectClass.ObjectName : "Class"); + // Try one of the "super" classes for unregistered classes. + loop: + if (objectClass != null && classType == typeof(UnknownObject)) + { + switch (objectClass) + { + case UExportTableItem classExp: + var super = classExp.Super; + switch (super) + { + //case UImportTableItem superImport: + // CreateObject(superImport); + // return; + + case UExportTableItem superExport: + objectClass = superExport; + classType = GetClassType(objectClass.ObjectName); + goto loop; + } + + break; + } + } + + item.Object = (UObject)Activator.CreateInstance(classType); + AddObject(item.Object, item); OnNotifyPackageEvent(new PackageEventArgs(PackageEventArgs.Id.Object)); } @@ -1629,7 +1960,7 @@ private void AddObject(UObject obj, UObjectTableItem table) public void WritePackageFlags() { Stream.Position = 8; - Stream.UW.Write(PackageFlags); + Stream.Writer.Write(PackageFlags); } [PublicAPI] @@ -1646,10 +1977,11 @@ public void AddClassType(string className, Type classObject) } [PublicAPI] + [NotNull] public Type GetClassType(string className) { _ClassTypes.TryGetValue(className.ToLower(), out var classType); - return classType; + return classType ?? typeof(UnknownObject); } [PublicAPI] @@ -1738,6 +2070,11 @@ public UObject FindObject(string objectName, Type classType, bool checkForSubcla [PublicAPI] public UObject FindObjectByGroup(string objectGroup) { + if (Objects == null) + { + return null; + } + string[] groups = objectGroup.Split('.'); UObject lastObj = null; for (var i = 0; i < groups.Length; ++i) @@ -1760,98 +2097,105 @@ public UObject FindObjectByGroup(string objectGroup) } /// - /// Checks whether this package is marked with @flag. + /// If true, the package won't have any editor data such as HideCategories, ScriptText etc. + /// + /// However this condition is not only determined by the package flags property. + /// Thus it is necessary to explicitly indicate this state. /// - /// The enum @flag to test. - /// Whether this package is marked with @flag. - [PublicAPI] - public bool HasPackageFlag(PackageFlags flag) + /// Whether package is cooked for consoles. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool IsConsoleCooked() { - return (PackageFlags & (uint)flag) != 0; + return Summary.PackageFlags.HasFlag(PackageFlag.Cooked) + && CookerPlatform == BuildPlatform.Console; } /// - /// Checks whether this package is marked with @flag. + /// Checks whether this package is marked with @flags. /// - /// The uint @flag to test + /// The enum @flag to test. /// Whether this package is marked with @flag. - [PublicAPI] - public bool HasPackageFlag(uint flag) + [Obsolete("See Summary.PackageFlags.HasFlag")] + public bool HasPackageFlag(PackageFlags flags) { - return (PackageFlags & flag) != 0; + return Summary.PackageFlags.HasFlags((uint)flags); } /// - /// Tests the packageflags of this UELib.UnrealPackage instance whether it is cooked. + /// Checks whether this package is marked with @flags. /// - /// True if cooked or False if not. - [PublicAPI] - public bool IsCooked() + /// The uint @flag to test + /// Whether this package is marked with @flag. + [Obsolete("See Summary.PackageFlags.HasFlag")] + public bool HasPackageFlag(uint flags) { - return HasPackageFlag(Flags.PackageFlags.Cooked) && Version >= VCOOKEDPACKAGES; + return (PackageFlags & flags) != 0; } /// - /// If true, the package won't have any editor data such as HideCategories, ScriptText etc. - /// - /// However this condition is not only determined by the package flags property. - /// Thus it is necessary to explicitly indicate this state. + /// Tests the packageflags of this UELib.UnrealPackage instance whether it is cooked. /// - /// Whether package is cooked for consoles. - [PublicAPI] - public bool IsConsoleCooked() + /// True if cooked or False if not. + [Obsolete] + public bool IsCooked() { - return IsCooked() && Build.Flags.HasFlag(BuildFlags.ConsoleCooked); + return Summary.PackageFlags.HasFlag(PackageFlag.Cooked); } /// /// Checks for the Map flag in PackageFlags. /// /// Whether if this package is a map. - [PublicAPI] + [Obsolete] public bool IsMap() { - return HasPackageFlag(Flags.PackageFlags.Map); + return Summary.PackageFlags.HasFlag(PackageFlag.ContainsMap); } /// /// Checks if this package contains code classes. /// /// Whether if this package contains code classes. - [PublicAPI] + [Obsolete] public bool IsScript() { - return HasPackageFlag(Flags.PackageFlags.Script); + return Summary.PackageFlags.HasFlag(PackageFlag.ContainsScript); } /// /// Checks if this package was built using the debug configuration. /// /// Whether if this package was built in debug configuration. - [PublicAPI] + [Obsolete] public bool IsDebug() { - return HasPackageFlag(Flags.PackageFlags.Debug); + return Summary.PackageFlags.HasFlag(PackageFlag.ContainsDebugData); } /// /// Checks for the Stripped flag in PackageFlags. /// /// Whether if this package is stripped. - [PublicAPI] + [Obsolete] public bool IsStripped() { - return HasPackageFlag(Flags.PackageFlags.Stripped); + return Summary.PackageFlags.HasFlag(PackageFlag.StrippedSource); } /// /// Tests the packageflags of this UELib.UnrealPackage instance whether it is encrypted. /// /// True if encrypted or False if not. - [PublicAPI] + [Obsolete] public bool IsEncrypted() { - return HasPackageFlag(Flags.PackageFlags.Encrypted); + return Summary.PackageFlags.HasFlag(PackageFlag.Encrypted); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool ContainsEditorData() + { + return Summary.UE4Version > 0 && !Summary.PackageFlags.HasFlag(PackageFlag.FilterEditorOnly); } #region IBuffered @@ -1860,31 +2204,27 @@ public byte[] CopyBuffer() { var buff = new byte[HeaderSize]; Stream.Seek(0, SeekOrigin.Begin); - Stream.Read(buff, 0, (int)HeaderSize); + Stream.Read(buff, 0, HeaderSize); if (Stream.BigEndianCode) Array.Reverse(buff); return buff; } - public IUnrealStream GetBuffer() { return Stream; } - public int GetBufferPosition() { return 0; } - public int GetBufferSize() { - return (int)HeaderSize; + return HeaderSize; } - public string GetBufferId(bool fullName = false) { return fullName ? FullPackageName : PackageName; @@ -1901,9 +2241,6 @@ public override string ToString() /// public void Dispose() { - Console.WriteLine("Disposing {0}", PackageName); - - DisposeStream(); if (Objects != null && Objects.Any()) { foreach (var obj in Objects) obj.Dispose(); @@ -1911,15 +2248,12 @@ public void Dispose() Objects.Clear(); Objects = null; } - } - private void DisposeStream() - { if (Stream == null) return; - Console.WriteLine("Disposing package stream"); - Stream.Dispose(); + Stream.Close(); + Stream = null; } #endregion diff --git a/src/UnrealPackageCompression.cs b/src/UnrealPackageCompression.cs index c7053d80..3824eecc 100644 --- a/src/UnrealPackageCompression.cs +++ b/src/UnrealPackageCompression.cs @@ -1,6 +1,6 @@ using System.Diagnostics.CodeAnalysis; using UELib.Annotations; -using UELib.Core.Types; +using UELib.Flags; namespace UELib { @@ -10,46 +10,70 @@ namespace UELib [PublicAPI] public class CompressedChunk : IUnrealSerializableClass { - public int UncompressedOffset; - public int UncompressedSize; - public int CompressedOffset; - public int CompressedSize; + private long _UncompressedOffset; + private int _UncompressedSize; + private long _CompressedOffset; + private int _CompressedSize; + + public long UncompressedOffset + { + get => _UncompressedOffset; + set => _UncompressedOffset = value; + } + + public int UncompressedSize + { + get => _UncompressedSize; + set => _UncompressedSize = value; + } + + public long CompressedOffset + { + get => _CompressedOffset; + set => _CompressedOffset = value; + } + + public int CompressedSize + { + get => _CompressedSize; + set => _CompressedSize = value; + } public void Serialize(IUnrealStream stream) { #if ROCKETLEAGUE if (stream.Package.Build == UnrealPackage.GameBuild.BuildName.RocketLeague - && stream.Package.LicenseeVersion >= 22) + && stream.LicenseeVersion >= 22) { - stream.Write((long)UncompressedOffset); - stream.Write((long)CompressedOffset); + stream.Write(_UncompressedOffset); + stream.Write(_CompressedOffset); goto streamStandardSize; } #endif - stream.Write(UncompressedOffset); - stream.Write(UncompressedSize); + stream.Write((int)_UncompressedOffset); + stream.Write(_UncompressedSize); streamStandardSize: - stream.Write(CompressedOffset); - stream.Write(CompressedSize); + stream.Write((int)_CompressedOffset); + stream.Write(_CompressedSize); } public void Deserialize(IUnrealStream stream) { #if ROCKETLEAGUE if (stream.Package.Build == UnrealPackage.GameBuild.BuildName.RocketLeague - && stream.Package.LicenseeVersion >= 22) + && stream.LicenseeVersion >= 22) { - UncompressedOffset = (int)stream.ReadInt64(); - CompressedOffset = (int)stream.ReadInt64(); + _UncompressedOffset = stream.ReadInt64(); + _CompressedOffset = stream.ReadInt64(); goto streamStandardSize; } #endif - UncompressedOffset = stream.ReadInt32(); - CompressedOffset = stream.ReadInt32(); + _UncompressedOffset = stream.ReadInt32(); + _CompressedOffset = stream.ReadInt32(); streamStandardSize: - UncompressedSize = stream.ReadInt32(); - CompressedSize = stream.ReadInt32(); + _UncompressedSize = stream.ReadInt32(); + _CompressedSize = stream.ReadInt32(); } } @@ -104,5 +128,10 @@ public void Deserialize(IUnrealStream stream) CompressedSize = stream.ReadInt32(); UncompressedSize = stream.ReadInt32(); } + + public int Decompress(byte[] compressedData, int index, CompressionFlags flags) + { + return UncompressedSize; + } } -} \ No newline at end of file +} diff --git a/src/UnrealScript/PropertyDisplay.cs b/src/UnrealScript/PropertyDisplay.cs index a8a56770..f41816da 100644 --- a/src/UnrealScript/PropertyDisplay.cs +++ b/src/UnrealScript/PropertyDisplay.cs @@ -1,7 +1,7 @@ using System.Globalization; using System.Runtime.CompilerServices; using System.Text; -using UELib.Core.Types; +using UELib.Core; namespace UELib.UnrealScript { @@ -10,8 +10,6 @@ public static class PropertyDisplay /// /// Recodes escaped characters /// https://stackoverflow.com/a/14087738/617087 - /// - /// FIXME: Escape \n in UE3 /// public static string FormatLiteral(string input) { @@ -21,11 +19,24 @@ public static string FormatLiteral(string input) { switch (c) { - case '\"': literal.Append("\\\""); break; - case '\\': literal.Append(@"\\"); break; - default: literal.Append(c); break; + case '\"': + literal.Append("\\\""); + break; + case '\\': + literal.Append(@"\\"); + break; + case '\n': + literal.Append(@"\n"); + break; + case '\r': + literal.Append(@"\r"); + break; + default: + literal.Append(c); + break; } } + literal.Append("\""); return literal.ToString(); } @@ -42,35 +53,80 @@ public static string FormatLiteral(byte input) return input.ToString(CultureInfo.InvariantCulture); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string FormatLiteral(short input) + { + return input.ToString("D", CultureInfo.InvariantCulture); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string FormatLiteral(ushort input) + { + return input.ToString("D", CultureInfo.InvariantCulture); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static string FormatLiteral(int input) { - return input.ToString(CultureInfo.InvariantCulture); + return input.ToString("D", CultureInfo.InvariantCulture); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static string FormatLiteral(long input) + public static string FormatLiteral(uint input) { - return input.ToString(CultureInfo.InvariantCulture); + return input.ToString("D", CultureInfo.InvariantCulture); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static string FormatLiteral(float input) { - return input.ToString(CultureInfo.InvariantCulture); + return input.ToString("F7", CultureInfo.InvariantCulture); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string FormatLiteral(long input) + { + return input.ToString("F15", CultureInfo.InvariantCulture); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string FormatLiteral(ulong input) + { + return input.ToString("F15", CultureInfo.InvariantCulture); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string FormatLiteral(UObject input) + { + return input != null + ? input.GetReferencePath() + : "none"; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static string FormatLiteral(UColor input) + public static string FormatExport(float input) { - // No parenthesis, may eventually change - return $"B={FormatLiteral(input.B)},G={FormatLiteral(input.G)},R={FormatLiteral(input.R)},A={FormatLiteral(input.A)}"; + return input.ToString("+00000.000000;-00000.000000", CultureInfo.InvariantCulture); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static string FormatStructLiteral(string args) + public static string FormatExport(ref UVector input) + { + return $"{FormatExport(input.X)}," + + $"{FormatExport(input.Y)}," + + $"{FormatExport(input.Z)}"; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string FormatOffset(short input) + { + return $"{input:X3}h"; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string FormatOffset(int input) { - return $"({args})"; + return $"{input:X4}h"; } } -} \ No newline at end of file +} diff --git a/src/UnrealStream.cs b/src/UnrealStream.cs index 5426b401..e0657f2e 100644 --- a/src/UnrealStream.cs +++ b/src/UnrealStream.cs @@ -4,225 +4,248 @@ using System.IO; using System.Linq; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; using System.Text; using UELib.Annotations; +using UELib.Branch; using UELib.Core; +using UELib.Decoding; +using UELib.Flags; namespace UELib { - public interface IUnrealStream : IDisposable + public interface IUnrealArchive { UnrealPackage Package { get; } - UnrealReader UR { get; } - UnrealWriter UW { get; } - uint Version { get; } + uint LicenseeVersion { get; } + uint UE4Version { get; } - string ReadText(); - string ReadASCIIString(); - - int ReadObjectIndex(); - UObject ReadObject(); + bool BigEndianCode { get; } + } - [PublicAPI("UE Explorer")] - UObject ParseObject(int index); + public interface IUnrealStream : IUnrealArchive, IDisposable + { + UnrealPackage Package { get; } - int ReadNameIndex(); + [Obsolete("To be deprecated")] UnrealReader UR { get; } + [Obsolete("To be deprecated")] UnrealWriter UW { get; } - [Obsolete] - int ReadNameIndex(out int num); + [CanBeNull] IBufferDecoder Decoder { get; set; } + IPackageSerializer Serializer { get; set; } - [PublicAPI("UE Explorer")] - string ParseName(int index); + long Position { get; set; } - /// - /// Reads the next 1-5 (compact) consecutive bytes as an index. - /// - int ReadIndex(); + // HACK: To be deprecated, but for now we need this to help us retrieve the absolute position when serializing within an UObject's buffer. + long AbsolutePosition { get; set; } - float ReadFloat(); + [Obsolete("use ReadObject or ReadIndex instead")] + int ReadObjectIndex(); - byte ReadByte(); + [Obsolete("UE Explorer")] + UObject ParseObject(int index); - short ReadInt16(); - ushort ReadUInt16(); + [Obsolete("use ReadName instead")] + int ReadNameIndex(); - int ReadInt32(); - uint ReadUInt32(); + [Obsolete("use ReadName instead")] + int ReadNameIndex(out int num); - long ReadInt64(); - ulong ReadUInt64(); + [Obsolete("UE Explorer")] + string ParseName(int index); void Skip(int bytes); - // Stream - long Length { get; } - long Position { get; set; } - long LastPosition { get; set; } int Read(byte[] buffer, int index, int count); long Seek(long offset, SeekOrigin origin); } public class UnrealWriter : BinaryWriter { - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", - "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes")] - private IUnrealStream _UnrealStream; + private readonly byte[] _IndexBuffer = new byte[5]; - private uint _Version => _UnrealStream.Version; + public UnrealWriter(IUnrealArchive archive, Stream baseStream) : base(baseStream) => Archive = archive; - public UnrealWriter(Stream stream) : base(stream) - { - _UnrealStream = stream as IUnrealStream; - } + public IUnrealArchive Archive { get; private set; } - public void WriteText(string s) + [Obsolete("See WriteString")] + public void WriteText(string s) => WriteString(s); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsUnicode(string s) => s.Any(c => c >= 127); + + public void WriteString(string s) { - Write(s.Length); - Write(s.ToCharArray(), 0, 0); - Write('\0'); + if (!IsUnicode(s)) + { + int length = s.Length + 1; + WriteIndex(length); + foreach (byte c in s) + { + BaseStream.WriteByte(c); + } + + BaseStream.WriteByte(0); + } + else + { + int length = s.Length + 1; + WriteIndex(-length); + foreach (char c in s) + { + Write((short)c); + } + + BaseStream.WriteByte(0); + BaseStream.WriteByte(0); + } } - // TODO: Add support for Unicode, and Unreal Engine 1 & 2. - public void WriteString(string s) + public void WriteCompactIndex(int index) { - WriteIndex(s.Length + 1); - byte[] bytes = Encoding.ASCII.GetBytes(s); - Write(bytes, 0, bytes.Count()); - Write((byte)0); + bool isPositive = index >= 0; + index = Math.Abs(index); + byte b0 = (byte)((isPositive ? 0 : 0x80) + (index < 0x40 ? index : (index & 0x3F) + 0x40)); + _IndexBuffer[0] = b0; + BaseStream.Write(_IndexBuffer, 0, 1); + if ((b0 & 0x40) != 0) + { + index >>= 6; + byte b1 = (byte)(index < 0x80 ? index : (index & 0x7F) + 0x80); + _IndexBuffer[1] = b1; + BaseStream.Write(_IndexBuffer, 1, 1); + if ((b1 & 0x80) != 0) + { + index >>= 7; + byte b2 = (byte)(index < 0x80 ? index : (index & 0x7F) + 0x80); + _IndexBuffer[2] = b1; + BaseStream.Write(_IndexBuffer, 2, 1); + if ((b2 & 0x80) != 0) + { + index >>= 7; + byte b3 = (byte)(index < 0x80 ? index : (index & 0x7F) + 0x80); + _IndexBuffer[3] = b1; + BaseStream.Write(_IndexBuffer, 3, 1); + if ((b3 & 0x80) != 0) + { + _IndexBuffer[4] = (byte)(index >> 7); + BaseStream.Write(_IndexBuffer, 4, 1); + } + } + } + } } public void WriteIndex(int index) { - if (_Version >= UnrealPackage.VINDEXDEPRECATED) + if (Archive.Version >= (uint)PackageObjectLegacyVersion.CompactIndexDeprecated) + { Write(index); - else - throw new InvalidDataException("UE1 and UE2 are not supported for writing indexes!"); + return; + } + + WriteCompactIndex(index); } protected override void Dispose(bool disposing) { base.Dispose(disposing); if (!disposing) + { return; + } - _UnrealStream = null; + Archive = null; } } - public interface IUnrealArchive - { - UnrealPackage Package { get; } - uint Version { get; } - - bool BigEndianCode { get; } - long LastPosition { get; set; } - } - /// - /// Wrapper for Streams with specific functions for deserializing UELib.UnrealPackage. + /// Wrapper for Streams with specific functions for deserializing UELib.UnrealPackage. /// public class UnrealReader : BinaryReader { - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", - "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes")] - protected IUnrealArchive _Archive; + private readonly byte[] _IndexBuffer = new byte[5]; // Dirty hack to implement crypto-reading, pending overhaul ;) - public UnrealReader(IUnrealArchive archive, Stream baseStream) : base(baseStream) - { - _Archive = archive; - } + public UnrealReader(IUnrealArchive archive, Stream baseStream) : base(baseStream) => Archive = archive; - /// - /// Reads a string that was serialized for Unreal Packages, these strings use a positive or negative size to: - /// - indicate that the bytes were encoded in ASCII. - /// - indicate that the bytes were encoded in Unicode. - /// - /// A string in either ASCII or Unicode. - public string ReadText() + public IUnrealArchive Archive { get; private set; } + + /// Reads a string from the current stream. + /// The length in characters; a negative length indicates an unicode string. + /// The string being read with the null termination cut off. + public string ReadString(int length) { -#if BINARYMETADATA - long lastPosition = BaseStream.Position; -#endif - int unfixedSize = ReadIndex(); -#if BIOSHOCK - if (_Archive.Package.Build == BuildGeneration.Vengeance && - _Archive.Version >= 135) - { - unfixedSize = -unfixedSize; - } -#endif - int size = unfixedSize < 0 - ? -unfixedSize - : unfixedSize; - if (unfixedSize > 0) // ANSI + int size = length < 0 + ? -length + : length; + if (length > 0) // ANSI { - // We don't want to use base.Read here because it may reverse the buffer if we are using BigEndianOrder - // TODO: Optimize - var chars = new byte[size]; - for (var i = 0; i < chars.Length; ++i) + byte[] chars = new byte[size]; + for (int i = 0; i < chars.Length; ++i) { - byte c = ReadByte(); - chars[i] = c; + BaseStream.Read(chars, i, 1); } -#if BINARYMETADATA - _Archive.LastPosition = lastPosition; -#endif + return chars[size - 1] == '\0' ? Encoding.ASCII.GetString(chars, 0, chars.Length - 1) : Encoding.ASCII.GetString(chars, 0, chars.Length); } - if (unfixedSize < 0) // UNICODE + if (length < 0) // UNICODE { - var chars = new char[size]; - for (var i = 0; i < chars.Length; ++i) + char[] chars = new char[size]; + for (int i = 0; i < chars.Length; ++i) { - var w = (char)ReadInt16(); + char w = (char)ReadInt16(); chars[i] = w; } -#if BINARYMETADATA - _Archive.LastPosition = lastPosition; -#endif + return chars[size - 1] == '\0' ? new string(chars, 0, chars.Length - 1) : new string(chars); } -#if BINARYMETADATA - _Archive.LastPosition = lastPosition; -#endif + return string.Empty; } - public string ReadAnsi() + /// Reads a length prefixed string from the current stream. + /// The string being read with the null termination cut off. + public override string ReadString() { -#if BINARYMETADATA - long lastPosition = BaseStream.Position; + int unfixedSize = ReadIndex(); +#if BIOSHOCK + // TODO: Make this a build option instead. + if (Archive.Package.Build == BuildGeneration.Vengeance && + Archive.Version >= 135) + { + unfixedSize = -unfixedSize; + } #endif + return ReadString(unfixedSize); + } + + [Obsolete("See ReadString")] + public string ReadText() => ReadString(); + + public string ReadAnsi() + { var strBytes = new List(); - nextChar: - byte c = ReadByte(); + nextChar: + byte c = (byte)BaseStream.ReadByte(); if (c != '\0') { strBytes.Add(c); goto nextChar; } -#if BINARYMETADATA - _Archive.LastPosition = lastPosition; -#endif + string s = Encoding.UTF8.GetString(strBytes.ToArray()); return s; } public string ReadUnicode() { -#if BINARYMETADATA - long lastPosition = BaseStream.Position; -#endif var strBytes = new List(); - nextWord: + nextWord: short w = ReadInt16(); if (w != 0) { @@ -230,104 +253,70 @@ public string ReadUnicode() strBytes.Add((byte)(w & 0x00FF)); goto nextWord; } -#if BINARYMETADATA - _Archive.LastPosition = lastPosition; -#endif + string s = Encoding.Unicode.GetString(strBytes.ToArray()); return s; } - /// - /// Unreal Engine 1 and 2 - /// Compact indices exist so that small numbers can be stored efficiently. - /// An index named "Index" is stored as a series of 1-5 consecutive bytes. - /// - /// Unreal Engine 3 - /// The index is based on a Int32 - /// - public int ReadIndex() + /// Reads a compact 1-5-byte signed integer from the current stream. + /// A 4-byte signed integer read from the current stream. + public int ReadCompactIndex() { - if (_Archive.Version >= UnrealPackage.VINDEXDEPRECATED) return ReadInt32(); -#if BINARYMETADATA - long lastPosition = BaseStream.Position; -#endif - const byte isIndiced = 0x40; // 7th bit - const byte isNegative = 0x80; // 8th bit - const byte value = 0xFF - isIndiced - isNegative; // 3F - const byte isProceeded = 0x80; // 8th bit - const byte proceededValue = 0xFF - isProceeded; // 7F - - var index = 0; - byte b0 = ReadByte(); - if ((b0 & isIndiced) != 0) + int index = 0; + BaseStream.Read(_IndexBuffer, 0, 1); + byte b0 = _IndexBuffer[0]; + if ((b0 & 0x40) != 0) { - byte b1 = ReadByte(); - if ((b1 & isProceeded) != 0) + BaseStream.Read(_IndexBuffer, 1, 1); + byte b1 = _IndexBuffer[1]; + if ((b1 & 0x80) != 0) { - byte b2 = ReadByte(); - if ((b2 & isProceeded) != 0) + BaseStream.Read(_IndexBuffer, 2, 1); + byte b2 = _IndexBuffer[2]; + if ((b2 & 0x80) != 0) { - byte b3 = ReadByte(); - if ((b3 & isProceeded) != 0) + BaseStream.Read(_IndexBuffer, 3, 1); + byte b3 = _IndexBuffer[3]; + if ((b3 & 0x80) != 0) { - byte b4 = ReadByte(); + BaseStream.Read(_IndexBuffer, 4, 1); + byte b4 = _IndexBuffer[4]; index = b4; } - index = (index << 7) + (b3 & proceededValue); + index = (index << 7) + (b3 & 0x7F); } - index = (index << 7) + (b2 & proceededValue); + index = (index << 7) + (b2 & 0x7F); } - index = (index << 7) + (b1 & proceededValue); + index = (index << 7) + (b1 & 0x7F); } -#if BINARYMETADATA - _Archive.LastPosition = lastPosition; -#endif - return (b0 & isNegative) != 0 // The value is negative or positive?. - ? -((index << 6) + (b0 & value)) - : (index << 6) + (b0 & value); + + index = (index << 6) + (b0 & 0x3F); + return (b0 & 0x80) != 0 ? -index : index; } - public long ReadNameIndex() - { -#if BINARYMETADATA - long lastPosition = BaseStream.Position; -#endif - int index = ReadIndex(); - if (_Archive.Version >= UName.VNameNumbered -#if BIOSHOCK - || _Archive.Package.Build == UnrealPackage.GameBuild.BuildName.BioShock -#endif - ) - { - uint num = ReadUInt32() - 1; -#if BINARYMETADATA - _Archive.LastPosition = lastPosition; -#endif - return (long)((ulong)num << 32) | (uint)index; - } + /// Reads an index from the current stream. + /// A 4-byte signed integer read from the current stream. + public int ReadIndex() => + Archive.Version >= (uint)PackageObjectLegacyVersion.CompactIndexDeprecated + ? ReadInt32() + : ReadCompactIndex(); - return index; - } + [Obsolete] + public long ReadNameIndex() => (uint)ReadNameIndex(out int n) | ((long)n << 32); public int ReadNameIndex(out int num) { -#if BINARYMETADATA - long lastPosition = BaseStream.Position; -#endif int index = ReadIndex(); - if (_Archive.Version >= UName.VNameNumbered + if (Archive.Version >= (uint)PackageObjectLegacyVersion.NumberAddedToName #if BIOSHOCK - || _Archive.Package.Build == UnrealPackage.GameBuild.BuildName.BioShock + || Archive.Package.Build == UnrealPackage.GameBuild.BuildName.BioShock #endif ) { num = ReadInt32() - 1; -#if BINARYMETADATA - _Archive.LastPosition = lastPosition; -#endif return index; } @@ -335,12 +324,15 @@ public int ReadNameIndex(out int num) return index; } - [PublicAPI("UE Explorer - Hex Viewer")] + [Obsolete("UE Explorer - Hex Viewer")] public static int ReadIndexFromBuffer(byte[] value, IUnrealStream stream) { - if (stream.Version >= UnrealPackage.VINDEXDEPRECATED) return BitConverter.ToInt32(value, 0); + if (stream.Version >= UnrealPackage.VINDEXDEPRECATED) + { + return BitConverter.ToInt32(value, 0); + } - var index = 0; + int index = 0; byte b0 = value[0]; if ((b0 & 0x40) != 0) { @@ -371,52 +363,48 @@ public static int ReadIndexFromBuffer(byte[] value, IUnrealStream stream) : (index << 6) + (b0 & 0x3F); } - public Guid ReadGuid() - { - // A, B, C, D - var guidBuffer = new byte[16]; - Read(guidBuffer, 0, 16); - var g = new Guid(guidBuffer); - return g; - } - protected override void Dispose(bool disposing) { base.Dispose(disposing); if (!disposing) + { return; + } - _Archive = null; + Archive = null; } } public class UPackageFileStream : FileStream { - public UnrealPackage Package { get; protected set; } - protected UPackageFileStream(string path, FileMode mode, FileAccess access, FileShare share) : base(path, mode, access, share) { } + [CanBeNull] public IBufferDecoder Decoder { get; set; } + [CanBeNull] public IPackageSerializer Serializer { get; set; } + public override int Read(byte[] buffer, int index, int count) { long p = Position; int length = base.Read(buffer, index, count); - Package.Decoder?.DecodeRead(p, buffer, index, count); + Decoder?.DecodeRead(p, buffer, index, count); return length; } public override int ReadByte() { - if (Package.Decoder == null) + if (Decoder == null) + { return base.ReadByte(); + } unsafe { long p = Position; - var b = (byte)base.ReadByte(); - Package.Decoder?.DecodeByte(p, &b); + byte b = (byte)base.ReadByte(); + Decoder?.DecodeByte(p, &b); return b; } } @@ -424,30 +412,73 @@ public override int ReadByte() public class UPackageStream : UPackageFileStream, IUnrealStream, IUnrealArchive { - public uint Version => Package?.Version ?? 0; + public UPackageStream(string path, FileMode mode, FileAccess access) : base(path, mode, access, + FileShare.ReadWrite) + { + Reader = null; + Writer = null; + InitBuffer(); + } - public UnrealReader UR { get; private set; } - public UnrealWriter UW { get; private set; } + private UnrealReader Reader { get; set; } - public long LastPosition { get; set; } + internal UnrealWriter Writer { get; set; } - public bool BigEndianCode { get; private set; } + public long LastPosition { get; set; } public bool IsChunked => Package.CompressedChunks != null && Package.CompressedChunks.Any(); + public UnrealPackage Package { get; private set; } - public UPackageStream(string path, FileMode mode, FileAccess access) : base(path, mode, access, - FileShare.ReadWrite) + public uint Version => Package.Version; + public uint LicenseeVersion => Package.LicenseeVersion; + public uint UE4Version => Package.Summary.UE4Version; + + UnrealReader IUnrealStream.UR => Reader; + + UnrealWriter IUnrealStream.UW => Writer; + + public long AbsolutePosition { - UR = null; - UW = null; - InitBuffer(); + get => Position; + set => Position = value; + } + + public bool BigEndianCode { get; private set; } + + public override int Read(byte[] buffer, int index, int count) + { + int length = base.Read(buffer, index, count); + if (BigEndianCode && length > 1) + { + Array.Reverse(buffer, 0, length); + } + + return length; } + public int ReadObjectIndex() => Reader.ReadIndex(); + + public UObject ParseObject(int index) => Package.GetIndexObject(index); + + public int ReadNameIndex() => Reader.ReadNameIndex(out int _); + + public string ParseName(int index) => Package.GetIndexName(index); + + public int ReadNameIndex(out int num) => Reader.ReadNameIndex(out num); + + public void Skip(int bytes) => Position += bytes; + private void InitBuffer() { - if (CanRead && UR == null) UR = new UnrealReader(this, this); + if (CanRead && Reader == null) + { + Reader = new UnrealReader(this, this); + } - if (CanWrite && UW == null) UW = new UnrealWriter(this); + if (CanWrite && Writer == null) + { + Writer = new UnrealWriter(this, this); + } } public void PostInit(UnrealPackage package) @@ -455,13 +486,15 @@ public void PostInit(UnrealPackage package) Package = package; if (!CanRead) + { return; + } - package.Decoder?.PreDecode(this); + Decoder?.PreDecode(this); - var bytes = new byte[4]; + byte[] bytes = new byte[4]; base.Read(bytes, 0, 4); - var readSignature = BitConverter.ToUInt32(bytes, 0); + uint readSignature = BitConverter.ToUInt32(bytes, 0); if (readSignature == UnrealPackage.Signature_BigEndian) { Console.WriteLine("Encoding:BigEndian"); @@ -471,356 +504,262 @@ public void PostInit(UnrealPackage package) if (!UnrealConfig.SuppressSignature && readSignature != UnrealPackage.Signature && readSignature != UnrealPackage.Signature_BigEndian) + { throw new FileLoadException(package.PackageName + " isn't an UnrealPackage!"); + } Position = 4; } - /// - /// Called as soon the build for @Package is detected. - /// - /// - public void BuildDetected(UnrealPackage.GameBuild build) - { - Package.Decoder?.DecodeBuild(this, build); - } - - public override int Read(byte[] buffer, int index, int count) + public int EndianAgnosticRead(byte[] buffer, int index, int count) { -#if BINARYMETADATA - LastPosition = Position; -#endif int length = base.Read(buffer, index, count); - if (BigEndianCode && length > 1) Array.Reverse(buffer, 0, length); return length; } - public new byte ReadByte() - { -#if BINARYMETADATA - LastPosition = Position; -#endif - return (byte)base.ReadByte(); - } + public new byte ReadByte() => (byte)base.ReadByte(); - public float ReadFloat() + protected override void Dispose(bool disposing) { - return UR.ReadSingle(); + base.Dispose(disposing); + if (disposing) + { + Reader = null; + Writer = null; + } } + } - #region Macros + public class UObjectStream : MemoryStream, IUnrealStream + { + private readonly long _ObjectPositionInPackage; - public ushort ReadUShort() - { - return UR.ReadUInt16(); - } + private long _PeekStartPosition; - public uint ReadUInt32() + public UObjectStream(IUnrealStream stream) { - return UR.ReadUInt32(); - } + _ObjectPositionInPackage = stream.Position; - public ulong ReadUInt64() - { - return UR.ReadUInt64(); + Writer = null; + Reader = null; + Package = stream.Package; + InitBuffer(); } - public short ReadInt16() + public UObjectStream(IUnrealStream str, byte[] buffer) : base(buffer, true) { - return UR.ReadInt16(); - } + _ObjectPositionInPackage = str.Position; - public ushort ReadUInt16() - { - return UR.ReadUInt16(); + Writer = null; + Reader = null; + Package = str.Package; + InitBuffer(); } - public int ReadInt32() - { - return UR.ReadInt32(); - } + public string Name => Package.Stream.Name; - public long ReadInt64() - { - return UR.ReadInt64(); - } + private UnrealReader Reader { get; set; } - public string ReadText() - { - return UR.ReadText(); - } + private UnrealWriter Writer { get; set; } - public string ReadASCIIString() - { - return UR.ReadAnsi(); - } + public long LastPosition { get; set; } + public UnrealPackage Package { get; } - public int ReadIndex() - { - return UR.ReadIndex(); - } + public uint Version => Package.Version; + public uint LicenseeVersion => Package.LicenseeVersion; + public uint UE4Version => Package.Summary.UE4Version; - public int ReadObjectIndex() - { - return UR.ReadIndex(); - } + public IBufferDecoder Decoder { get; set; } + public IPackageSerializer Serializer { get; set; } + UnrealReader IUnrealStream.UR => Reader; + UnrealWriter IUnrealStream.UW => Writer; - public UObject ReadObject() + public long AbsolutePosition { - return Package.GetIndexObject(ReadObjectIndex()); + get => Position + _ObjectPositionInPackage; + set => Position = value - _ObjectPositionInPackage; } - public UObject ParseObject(int index) - { - return Package.GetIndexObject(index); - } + public bool BigEndianCode => Package.Stream.BigEndianCode; - public int ReadNameIndex() + public override int Read(byte[] buffer, int index, int count) { - return (int)UR.ReadNameIndex(); - } + int r = base.Read(buffer, index, count); + if (BigEndianCode && r > 1) + { + Array.Reverse(buffer, 0, r); + } - public string ParseName(int index) - { - return Package.GetIndexName(index); + return r; } - public int ReadNameIndex(out int num) - { - return UR.ReadNameIndex(out num); - } + public int ReadObjectIndex() => Reader.ReadIndex(); - public Guid ReadGuid() - { - return UR.ReadGuid(); - } + public UObject ParseObject(int index) => Package.GetIndexObject(index); - #endregion + public int ReadNameIndex() => Reader.ReadNameIndex(out int _); - public void Skip(int bytes) - { - Position += bytes; - } + public string ParseName(int index) => Package.GetIndexName(index); - protected override void Dispose(bool disposing) + public int ReadNameIndex(out int num) => Reader.ReadNameIndex(out num); + + public void Skip(int bytes) => Position += bytes; + + private void InitBuffer() { - if (!disposing) - return; + if (CanRead && Reader == null) + { + Reader = new UnrealReader(this, this); + } - UR = null; - UW = null; + if (CanWrite && Writer == null) + { + Writer = new UnrealWriter(this, this); + } } - } - public class UObjectStream : MemoryStream, IUnrealStream, IUnrealArchive - { - public string Name => Package.Stream.Name; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public new byte ReadByte() => Reader.ReadByte(); - public UnrealPackage Package { get; } + /// + /// Start peeking, without advancing the stream position. + /// + public void StartPeek() => _PeekStartPosition = Position; - public uint Version => Package?.Version ?? 0; - - public UnrealReader UR { get; private set; } - public UnrealWriter UW { get; private set; } - - private long _PeekStartPosition; - public long LastPosition { get; set; } - - public bool BigEndianCode => Package.Stream.BigEndianCode; - - public UObjectStream(IUnrealStream stream) - { - UW = null; - UR = null; - Package = stream.Package; - InitBuffer(); - } - - public UObjectStream(IUnrealStream str, byte[] buffer) : base(buffer, true) - { - UW = null; - UR = null; - Package = str.Package; - InitBuffer(); - } - - private void InitBuffer() - { - if (CanRead && UR == null) UR = new UnrealReader(this, this); - - if (CanWrite && UW == null) UW = new UnrealWriter(this); - } - - public override int Read(byte[] buffer, int index, int count) - { -#if BINARYMETADATA - LastPosition = Position; -#endif - int r = base.Read(buffer, index, count); - if (BigEndianCode && r > 1) Array.Reverse(buffer, 0, r); - - return r; - } - - public float ReadFloat() + public void StartPeek(long position) { - return UR.ReadSingle(); + _PeekStartPosition = Position; + Position = position; } - #region Macros - - public new byte ReadByte() - { -#if BINARYMETADATA - LastPosition = Position; -#endif - return UR.ReadByte(); - } + /// + /// Stop peeking, the original position is restored. + /// + public void EndPeek() => Position = _PeekStartPosition; - public ushort ReadUShort() + internal void DisposeBuffer() { - return UR.ReadUInt16(); - } + if (Reader != null) + { + Reader.Dispose(); + Reader = null; + } - public uint ReadUInt32() - { - return UR.ReadUInt32(); + if (Writer != null) + { + Writer.Dispose(); + Writer = null; + } } + } - public ulong ReadUInt64() - { - return UR.ReadUInt64(); - } + public sealed class UObjectRecordStream : UObjectStream + { + private long _LastRecordPosition; - public short ReadInt16() - { - return UR.ReadInt16(); - } + public UObjectRecordStream(IUnrealStream stream, byte[] buffer) : base(stream, buffer) => + BinaryMetaData = new BinaryMetaData(); - public ushort ReadUInt16() - { - return UR.ReadUInt16(); - } + public BinaryMetaData BinaryMetaData { get; } - public int ReadInt32() + /// + /// TODO: Move this feature into a stream. + /// Outputs the present position and the value of the parsed object. + /// Only called in the DEBUGBUILD! + /// + /// The struct that was read from the previous buffer position. + /// The struct's value that was read. + [Conditional("BINARYMETADATA")] + public void Record(string varName, object varObject = null) { - return UR.ReadInt32(); + long size = Position - _LastRecordPosition; + BinaryMetaData.AddField(varName, varObject, _LastRecordPosition, size); + _LastRecordPosition = Position; } - public long ReadInt64() - { - return UR.ReadInt64(); - } + [Conditional("BINARYMETADATA")] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ConformRecordPosition() => _LastRecordPosition = Position; + } - public string ReadText() + /// + /// Methods that shouldn't be duplicated between UObjectStream and UPackageStream. + /// + [Obsolete("Pending deprecation")] + public static class UnrealStreamImplementations + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static byte ReadByte(this IUnrealStream stream) { - return UR.ReadText(); + byte[] buffer = new byte[1]; + stream.Read(buffer, 0, 1); + return buffer[0]; } - public string ReadASCIIString() - { - return UR.ReadAnsi(); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ushort ReadUShort(this IUnrealStream stream) => stream.UR.ReadUInt16(); - public int ReadIndex() - { - return UR.ReadIndex(); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint ReadUInt32(this IUnrealStream stream) => stream.UR.ReadUInt32(); - public int ReadObjectIndex() - { - return UR.ReadIndex(); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ulong ReadUInt64(this IUnrealStream stream) => stream.UR.ReadUInt64(); - public UObject ReadObject() - { - return Package.GetIndexObject(UR.ReadIndex()); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static short ReadInt16(this IUnrealStream stream) => stream.UR.ReadInt16(); - public UObject ParseObject(int index) - { - return Package.GetIndexObject(index); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ushort ReadUInt16(this IUnrealStream stream) => stream.UR.ReadUInt16(); - public int ReadNameIndex() - { - return (int)UR.ReadNameIndex(); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int ReadInt32(this IUnrealStream stream) => stream.UR.ReadInt32(); - public string ParseName(int index) - { - return Package.GetIndexName(index); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static long ReadInt64(this IUnrealStream stream) => stream.UR.ReadInt64(); - public int ReadNameIndex(out int num) - { - return UR.ReadNameIndex(out num); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float ReadFloat(this IUnrealStream stream) => stream.UR.ReadSingle(); - public Guid ReadGuid() - { - return UR.ReadGuid(); - } + [Obsolete("See ReadString")] + public static string ReadText(this IUnrealStream stream) => stream.UR.ReadString(); - #endregion + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string ReadString(this IUnrealStream stream) => stream.UR.ReadString(); - public void Skip(int bytes) - { - Position += bytes; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string ReadAnsiNullString(this IUnrealStream stream) => stream.UR.ReadAnsi(); - /// - /// Start peeking, without advancing the stream position. - /// - public void StartPeek() - { - _PeekStartPosition = Position; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string ReadUnicodeNullString(this IUnrealStream stream) => stream.UR.ReadUnicode(); - /// - /// Stop peeking, the original position is restored. - /// - public void EndPeek() + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool ReadBool(this IUnrealStream stream) { - Position = _PeekStartPosition; + int value = stream.ReadInt32(); + Debug.Assert(value <= 1, $"Unexpected value '{value}' for a boolean"); + return Convert.ToBoolean(value); } - internal void DisposeBuffer() - { - if (UR != null) - { - UR.Dispose(); - UR = null; - } - - if (UW != null) - { - UW.Dispose(); - UW = null; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int ReadIndex(this IUnrealStream stream) => stream.UR.ReadIndex(); - Dispose(); - } - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static UObject ReadObject(this IUnrealStream stream) => + stream.Package.GetIndexObject(stream.ReadIndex()); - /// - /// Methods that shouldn't be duplicated between UObjectStream and UPackageStream. - /// - [Obsolete("Don't use directly, in 2.0 these are implemented once in a single shared IUnrealStream")] - public static class UnrealStreamImplementations - { [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T ReadObject(this IUnrealStream stream) where T : UObject - { - return (T)stream.Package.GetIndexObject(stream.ReadIndex()); - } + public static T ReadObject(this IUnrealStream stream) where T : UObject => + (T)stream.Package.GetIndexObject(stream.ReadIndex()); [Obsolete("To be deprecated")] public static string ReadName(this IUnrealStream stream) { int index = stream.ReadNameIndex(out int num); string name = stream.Package.GetIndexName(index); - if (num > UName.Numeric) name += $"_{num}"; + if (num > UName.Numeric) + { + name += $"_{num}"; + } return name; } @@ -834,260 +773,308 @@ public static UName ReadNameReference(this IUnrealStream stream) } /// - /// Use this to read a compact integer for Arrays/Maps. - /// TODO: Use a custom PackageStream and override ReadLength. + /// Use this to read a compact integer for Arrays/Maps. + /// TODO: Use a custom PackageStream and override ReadLength. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int ReadLength(this IUnrealStream stream) { - return stream.UR.ReadIndex(); +#if VANGUARD + if (stream.Package.Build == UnrealPackage.GameBuild.BuildName.Vanguard_SOH) + { + return stream.ReadInt32(); + } +#endif + return stream.ReadIndex(); } public static void ReadArray(this IUnrealStream stream, out UArray array) - where T : IUnrealSerializableClass, new() + where T : IUnrealDeserializableClass, new() { -#if BINARYMETADATA - long position = stream.Position; -#endif int c = stream.ReadLength(); array = new UArray(c); - for (var i = 0; i < c; ++i) + for (int i = 0; i < c; ++i) { var element = new T(); element.Deserialize(stream); array.Add(element); } -#if BINARYMETADATA - stream.LastPosition = position; -#endif } public static void ReadArray(this IUnrealStream stream, out UArray array, int count) - where T : IUnrealSerializableClass, new() + where T : IUnrealDeserializableClass, new() { -#if BINARYMETADATA - long position = stream.Position; -#endif array = new UArray(count); - for (var i = 0; i < count; ++i) + for (int i = 0; i < count; ++i) { var element = new T(); element.Deserialize(stream); array.Add(element); } -#if BINARYMETADATA - stream.LastPosition = position; -#endif } - public static void ReadAtomicStruct(this IUnrealStream stream, out T item) - where T : IUnrealAtomicStruct + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ReadStruct(this IUnrealStream stream, out T item) + where T : struct, IUnrealDeserializableClass { - int structSize = Marshal.SizeOf(); - var data = new byte[structSize]; + item = new T(); + item.Deserialize(stream); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ReadStruct(this IUnrealStream stream, T item) + where T : struct, IUnrealDeserializableClass => + item.Deserialize(stream); + + public static unsafe void ReadStructMarshal(this IUnrealStream stream, out T item) + where T : unmanaged, IUnrealAtomicStruct + { + int structSize = sizeof(T); + byte[] data = new byte[structSize]; stream.Read(data, 0, structSize); - var handle = GCHandle.Alloc(data, GCHandleType.Pinned); - item = Marshal.PtrToStructure(handle.AddrOfPinnedObject()); - handle.Free(); + fixed (byte* ptr = &data[0]) + { + item = *(T*)ptr; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ReadClass(this IUnrealStream stream, out T item) + where T : class, IUnrealDeserializableClass, new() + { + item = new T(); + item.Deserialize(stream); } // Can't seem to overload this :( - public static void ReadMarshalArray(this IUnrealStream stream, out UArray array, int count) - where T : IUnrealAtomicStruct + public static unsafe void ReadArrayMarshal(this IUnrealStream stream, out UArray array, int count) + where T : unmanaged, IUnrealAtomicStruct { -#if BINARYMETADATA - long position = stream.Position; -#endif - var structType = typeof(T); - int structSize = Marshal.SizeOf(structType); + int structSize = sizeof(T); array = new UArray(count); - for (var i = 0; i < count; ++i) + byte[] data = new byte[structSize * count]; + stream.Read(data, 0, structSize * count); + + for (int i = 0; i < count; ++i) + { + fixed (byte* ptr = &data[i * structSize]) + { + var element = *(T*)ptr; + array.Add(element); + } + } + } + + public static void ReadArray(this IUnrealStream stream, out UArray array) + { + int c = stream.ReadLength(); + array = new UArray(c); + for (int i = 0; i < c; ++i) { - //ReadAtomicStruct(stream, out T element); - var data = new byte[structSize]; - stream.Read(data, 0, structSize); - var handle = GCHandle.Alloc(data, GCHandleType.Pinned); - var element = (T)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), structType); - handle.Free(); + stream.Read(out byte element); + array.Add(element); + } + } + + public static void ReadArray(this IUnrealStream stream, out UArray array) + { + int c = stream.ReadLength(); + array = new UArray(c); + for (int i = 0; i < c; ++i) + { + stream.Read(out int element); + array.Add(element); + } + } + + public static void ReadArray(this IUnrealStream stream, out UArray array) + { + int c = stream.ReadLength(); + array = new UArray(c); + for (int i = 0; i < c; ++i) + { + Read(stream, out float element); array.Add(element); } -#if BINARYMETADATA - stream.LastPosition = position; -#endif } public static void ReadArray(this IUnrealStream stream, out UArray array) { -#if BINARYMETADATA - long position = stream.Position; -#endif int c = stream.ReadLength(); array = new UArray(c); - for (var i = 0; i < c; ++i) + for (int i = 0; i < c; ++i) { string element = stream.ReadText(); array.Add(element); } -#if BINARYMETADATA - stream.LastPosition = position; -#endif } public static void ReadArray(this IUnrealStream stream, out UArray array) { -#if BINARYMETADATA - long position = stream.Position; -#endif int c = stream.ReadLength(); array = new UArray(c); - for (var i = 0; i < c; ++i) + for (int i = 0; i < c; ++i) { - UName element = stream.ReadNameReference(); + var element = stream.ReadNameReference(); array.Add(element); } -#if BINARYMETADATA - stream.LastPosition = position; -#endif } public static void ReadArray(this IUnrealStream stream, out UArray array) { -#if BINARYMETADATA - long position = stream.Position; -#endif int c = stream.ReadLength(); array = new UArray(c); - for (var i = 0; i < c; ++i) + for (int i = 0; i < c; ++i) { - var element = stream.ReadObject(); + var element = stream.ReadObject(); array.Add(element); } -#if BINARYMETADATA - stream.LastPosition = position; -#endif } public static void ReadArray(this IUnrealStream stream, out UArray array, int count) { -#if BINARYMETADATA - long position = stream.Position; -#endif array = new UArray(count); - for (var i = 0; i < count; ++i) + for (int i = 0; i < count; ++i) { - var element = stream.ReadObject(); + var element = stream.ReadObject(); array.Add(element); } -#if BINARYMETADATA - stream.LastPosition = position; -#endif + } + + 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 ushort key); + Read(stream, out ushort value); + map.Add(key, value); + } } public static void ReadMap(this IUnrealStream stream, out UMap map) - where TKey : UName + where TKey : UName where TValue : UObject { -#if BINARYMETADATA - long position = stream.Position; -#endif int c = stream.ReadLength(); map = new UMap(c); - for (var i = 0; i < c; ++i) + for (int i = 0; i < c; ++i) { - Read(stream, out var key); - Read(stream, out var value); - map.Add((TKey)key, (TValue)value); + Read(stream, out UName key); + Read(stream, out TValue value); + map.Add((TKey)key, value); } -#if BINARYMETADATA - stream.LastPosition = position; -#endif } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Read(this IUnrealStream stream, out UObject value) - where T : UObject + [Obsolete("See UGuid")] + public static Guid ReadGuid(this IUnrealStream stream) { - value = ReadObject(stream); + // A, B, C, D + byte[] guidBuffer = new byte[16]; + stream.Read(guidBuffer, 0, 16); + var guid = new Guid(guidBuffer); + return guid; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Read(this IUnrealStream stream, out UName value) - where T : UName + public static UnrealFlags ReadFlags32(this IUnrealStream stream) + where TEnum : Enum { - value = ReadNameReference(stream); + stream.Package.Branch.EnumFlagsMap.TryGetValue(typeof(TEnum), out ulong[] enumMap); + Debug.Assert(enumMap != null, nameof(enumMap) + " != null"); + + ulong flags = stream.ReadUInt32(); + return new UnrealFlags(flags, ref enumMap); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Read(this IUnrealStream stream, out UName value) + public static UnrealFlags ReadFlags64(this IUnrealStream stream) + where TEnum : Enum { - value = ReadNameReference(stream); + stream.Package.Branch.EnumFlagsMap.TryGetValue(typeof(TEnum), out ulong[] enumMap); + Debug.Assert(enumMap != null, nameof(enumMap) + " != null"); + + ulong flags = stream.ReadUInt64(); + return new UnrealFlags(flags, ref enumMap); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Read(this IUnrealStream stream, out UArray array) - where T : IUnrealSerializableClass, new() + public static void VersionedDeserialize(this IUnrealStream stream, IUnrealDeserializableClass obj) { - ReadArray(stream, out array); + Debug.Assert(stream.Package.Branch.Serializer != null, "stream.Package.Branch.Serializer != null"); + stream.Package.Branch.Serializer.Deserialize(stream, obj); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Read(this IUnrealStream stream, out UArray array) - { + public static void Read(this IUnrealStream stream, out T value) + where T : UObject => + value = ReadObject(stream); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Read(this IUnrealStream stream, out UBulkData value) + where T : unmanaged => + ReadStruct(stream, out value); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Read(this IUnrealStream stream, out UObject value) => value = ReadObject(stream); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Read(this IUnrealStream stream, out string value) => value = stream.ReadText(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Read(this IUnrealStream stream, out UName value) => value = ReadNameReference(stream); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Read(this IUnrealStream stream, out UArray array) + where T : IUnrealSerializableClass, new() => ReadArray(stream, out array); - } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Read(this IUnrealStream stream, out UArray array) => ReadArray(stream, out array); [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Read(this IUnrealStream stream, out UMap map) where TKey : UName - where TValue : UObject - { + where TValue : UObject => ReadMap(stream, out map); - } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Read(this IUnrealStream stream, out byte value) - { - value = stream.ReadByte(); - } + public static void Read(this IUnrealStream stream, out UMap map) => ReadMap(stream, out map); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Read(this IUnrealStream stream, out short value) - { - value = stream.ReadInt16(); - } + public static void Read(this IUnrealStream stream, byte[] data) => stream.Read(data, 0, data.Length); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Read(this IUnrealStream stream, out ushort value) - { - value = stream.ReadUInt16(); - } + public static void Read(this IUnrealStream stream, out byte value) => value = stream.ReadByte(); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Read(this IUnrealStream stream, out int value) - { - value = stream.ReadInt32(); - } + public static void Read(this IUnrealStream stream, out short value) => value = stream.ReadInt16(); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Read(this IUnrealStream stream, out uint value) - { - value = stream.ReadUInt32(); - } + public static void Read(this IUnrealStream stream, out ushort value) => value = stream.ReadUInt16(); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Read(this IUnrealStream stream, out long value) - { - value = stream.ReadInt64(); - } + public static void Read(this IUnrealStream stream, out int value) => value = stream.ReadInt32(); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Read(this IUnrealStream stream, out ulong value) - { - value = stream.ReadUInt64(); - } + public static void Read(this IUnrealStream stream, out uint value) => value = stream.ReadUInt32(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Read(this IUnrealStream stream, out long value) => value = stream.ReadInt64(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Read(this IUnrealStream stream, out ulong value) => value = stream.ReadUInt64(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Read(this IUnrealStream stream, out float value) => value = stream.ReadFloat(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Read(this IUnrealStream stream, out bool value) => value = ReadBool(stream); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void WriteArray(this IUnrealStream stream, ref UArray array) + public static void WriteString(this IUnrealStream stream, string value) => stream.UW.WriteString(value); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteArray(this IUnrealStream stream, in UArray array) where T : IUnrealSerializableClass { Debug.Assert(array != null); @@ -1098,84 +1085,102 @@ public static void WriteArray(this IUnrealStream stream, ref UArray array) } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteStruct(this IUnrealStream stream, ref T item) + where T : struct, IUnrealSerializableClass => + item.Serialize(stream); + + public static unsafe void WriteStructMarshal(this IUnrealStream stream, ref T item) + where T : unmanaged, IUnrealAtomicStruct + { + int structSize = sizeof(T); + byte[] data = new byte[structSize]; + + fixed (void* ptr = &item) + { + //Marshal.Copy((IntPtr)ptr, data, 0, structSize); + fixed (byte* dataPtr = data) + { + Unsafe.CopyBlock(dataPtr, ptr, (uint)structSize); + } + } + stream.Write(data, 0, data.Length); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteClass(this IUnrealStream stream, ref T item) + where T : class, IUnrealSerializableClass, new() => + item.Serialize(stream); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Write(this IUnrealStream stream, byte[] data) => stream.Write(data, 0, data.Length); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Write(this IUnrealStream stream, string value) => stream.UW.WriteString(value); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Write(this IUnrealStream stream, UName name) { stream.UW.WriteIndex(name.Index); - if (stream.Version < UName.VNameNumbered) return; + if (stream.Version < (uint)PackageObjectLegacyVersion.NumberAddedToName) + { + return; + } + stream.UW.Write((uint)name.Number + 1); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Write(this IUnrealStream stream, UObject obj) - { + public static void Write(this IUnrealStream stream, ref UBulkData value) + where T : unmanaged => + WriteStruct(stream, ref value); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Write(this IUnrealStream stream, UObject obj) => stream.UW.WriteIndex(obj != null ? (int)obj : 0); - } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Write(this IUnrealStream stream, ref UArray array) - where T : IUnrealSerializableClass - { - WriteArray(stream, ref array); - } + where T : IUnrealSerializableClass => + WriteArray(stream, in array); + + //[MethodImpl(MethodImplOptions.AggressiveInlining)] + //public static void Write(this IUnrealStream stream, ref T item) + // where T : struct, IUnrealSerializableClass => + // stream.WriteStruct(ref item); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Write(this IUnrealStream stream, byte value) => stream.UW.Write(value); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Write(this IUnrealStream stream, byte value) - { - stream.UW.Write(value); - } + public static void Write(this IUnrealStream stream, short value) => stream.UW.Write(value); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Write(this IUnrealStream stream, short value) - { - stream.UW.Write(value); - } + public static void Write(this IUnrealStream stream, ushort value) => stream.UW.Write(value); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Write(this IUnrealStream stream, ushort value) - { - stream.UW.Write(value); - } + public static void Write(this IUnrealStream stream, int value) => stream.UW.Write(value); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Write(this IUnrealStream stream, int value) - { - stream.UW.Write(value); - } + public static void Write(this IUnrealStream stream, uint value) => stream.UW.Write(value); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Write(this IUnrealStream stream, uint value) - { - stream.UW.Write(value); - } + public static void Write(this IUnrealStream stream, long value) => stream.UW.Write(value); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Write(this IUnrealStream stream, long value) - { - stream.UW.Write(value); - } + public static void Write(this IUnrealStream stream, ulong value) => stream.UW.Write(value); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Write(this IUnrealStream stream, ulong value) - { - stream.UW.Write(value); - } + public static void Write(this IUnrealStream stream, float value) => stream.UW.Write(value); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Write(this IUnrealStream stream, byte[] buffer, int index, int count) - { - stream.UW.Write(buffer, index, count); - } + public static void Write(this IUnrealStream stream, bool value) => stream.UW.Write(Convert.ToInt32(value)); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void WriteIndex(this IUnrealStream stream, int index) - { - stream.UW.WriteIndex(index); - } + public static void Write(this IUnrealStream stream, byte[] buffer, int index, int count) => + stream.UW.Write(buffer, index, count); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Write(this IUnrealStream stream, string s) - { - stream.UW.WriteString(s); - } + public static void WriteIndex(this IUnrealStream stream, int index) => stream.UW.WriteIndex(index); } -} \ No newline at end of file +} diff --git a/src/UnrealTokens.cs b/src/UnrealTokens.cs index adef027a..fcb765da 100644 --- a/src/UnrealTokens.cs +++ b/src/UnrealTokens.cs @@ -5,203 +5,484 @@ /// public enum ExprToken : ushort { - // ValidateObject - // ResizeString - LocalVariable = 0x00, - InstanceVariable = 0x01, - DefaultVariable = 0x02, // default.Property - /// - /// UE1: ??? - /// UE2: Deprecated (Bad Expr Token) - /// UE3: Introduced in a late UDK build. - /// - StateVariable = 0x03, - Return = 0x04, // return EXPRESSION - Switch = 0x05, // switch (CONDITION) - Jump = 0x06, // goto CODEOFFSET - JumpIfNot = 0x07, // if( !CONDITION ) goto CODEOFFSET; - Stop = 0x08, // Stop (State) - Assert = 0x09, // assert (CONDITION) - Case = 0x0A, // case CONDITION: - Nothing = 0x0B, - LabelTable = 0x0C, - GotoLabel = 0x0D, // goto EXPRESSION - EatString = 0x0E, - EatReturnValue = 0x0E, // Formerly known as EatString - Let = 0x0F, // A = B - DynArrayElement = 0x10, // Array[EXPRESSION] - New = 0x11, // new(OUTER) CLASS... - ClassContext = 0x12, // Class'Path'.static.Function() - MetaCast = 0x13, // (CLASS) - - /// - /// UE1: BeginFunction - /// UE2: LetBool - /// - BeginFunction = 0x14, - LetBool = 0x14, - - /// - /// UE1: ??? - /// UE2: LineNumber (early UE2)? - /// UE2X: Deprecated (Bad Expr Token) - /// UE3: EndParmValue - /// - LineNumber = 0x15, - EndParmValue = 0x15, - EndFunctionParms = 0x16, // ) - Self = 0x17, // Self - Skip = 0x18, - Context = 0x19, // A.B - ArrayElement = 0x1A, // A[x] - VirtualFunction = 0x1B, // F(...) - FinalFunction = 0x1C, // F(...) - IntConst = 0x1D, - FloatConst = 0x1E, - StringConst = 0x1F, // "String" - ObjectConst = 0x20, - NameConst = 0x21, // 'Name' - RotationConst = 0x22, - VectorConst = 0x23, - ByteConst = 0x24, - IntZero = 0x25, - IntOne = 0x26, - True = 0x27, - False = 0x28, - NativeParm = 0x29, // A (Native) - NoObject = 0x2A, // None - /// - /// UE1: A string size cast - /// UE2: Deprecated (Bad Expr Token) - /// - CastStringSize = 0x2B, - IntConstByte = 0x2C, // 0-9 (<= 255) - BoolVariable = 0x2D, // B (Bool) - DynamicCast = 0x2E, // A(B) - Iterator = 0x2F, // ForEach - IteratorPop = 0x30, // Break (Implied/Explicit) - IteratorNext = 0x31, // Continue (Implied/Explicit) - StructCmpEq = 0x32, // A == B - StructCmpNE = 0x33, // A != B - // UnicodeStringConst - UniStringConst = 0x34, // "UNICODE" - - // Note: These byte-codes have shifted since UE3 and have therefor incorrect values assigned. - #region FixedByteCodes - /// - /// UE1: ??? - /// UE2: RangeConst or Deprecated (Bad Expr Token) - /// UE3: ??? - /// - RangeConst = 0x35, - StructMember = 0x36, // Struct.Property - DynArrayLength = 0x37, // ARRAY.Length - GlobalFunction = 0x38, // Global. - - /// - /// Redefined(RotatorToVector) - /// - /// UE1: RotatorToVector cast. - /// UE2+: Followed by any of the CastTokens to free space for other tokens, most are unused from 0x39 to 0x3F. + /// + /// A reference to a local variable (declared in a function) /// - PrimitiveCast = 0x39, // TYPE(EXPRESSION) - #endregion + LocalVariable, + + /// + /// A reference to a variable (declared in a class), equivalent to an explicit "self.Field" expression. + /// + InstanceVariable, + + /// + /// default.Property + /// + DefaultVariable, + + /// + /// A reference to a local variable (declared in a state) + /// + StateVariable, + + /// + /// return expr; + /// + Return, + + /// + /// switch (c) + /// + Switch, + + /// + /// goto offset; + /// + Jump, + + /// + /// if (!c) goto offset; + /// + JumpIfNot, + + /// + /// stop; + /// + Stop, + + /// + /// assert(c); + /// + Assert, /// - /// Redefined(DynArrayRemove) - /// - /// UE1: ByteToInt cast. - /// UE2: ReturnNothing (Deprecated) - /// UE3: ReturnNothing if previous token is a ReturnToken, DynArrayRemove when not. + /// case/default: expr + /// + Case, + + /// + /// May indicate an optional parameter with no default assignment, or an empty argument in a function call. + /// + Nothing, + + /// + /// label: + /// + LabelTable, + + /// + /// goto 'label' or name expr; + /// + GotoLabel, + + ValidateObject, + EatString, + EatReturnValue, + + /// + /// expr = expr; + /// + Let, + + /// + /// DynamicArray[expr] + /// + DynArrayElement, + + /// + /// new (expr) expr... + /// + New, + + /// + /// expr.expr i.e. Class'Package.Group.Object'.default/static/const.Field + /// + ClassContext, + + /// + /// (expr) + /// + MetaCast, + + /// + /// Contains the size of elements in the function's stack. + /// + BeginFunction, + + /// + /// expr = expr; + /// + LetBool, + + /// + /// Marks the current script line number. + /// + LineNumber, + + /// + /// Indicates the end of a default parameter assignment. + /// + EndParmValue, + + /// + /// Indicates the end of a function call. + /// + EndFunctionParms, + + /// + /// self + /// + Self, + + /// + /// Marks a code size for the VM to skip, used in operators to skip a redundant conditional check. + /// + Skip, + + /// + /// expr.expr + /// + Context, + + /// + /// FixedArray[expr] + /// + ArrayElement, + + /// + /// Object.VirtualFunction(params...) + /// + VirtualFunction, + + /// + /// Object.FinalFunction(params...) + /// + FinalFunction, + + /// + /// 0xFFFFFFFF + /// + IntConst, + + /// + /// 0.0f + /// + FloatConst, + + /// + /// "String" + /// + StringConst, + + /// + /// Class'Package.Group.Object' + /// + ObjectConst, + + /// + /// 'Name' + /// + NameConst, + + /// + /// rot(0, 0, 0) + /// + RotationConst, + + /// + /// vect(0f, 0f, 0f) + /// + VectorConst, + + /// + /// 0xFF + /// + ByteConst, + + /// + /// 0 + /// + IntZero, + + /// + /// 1 + /// + IntOne, + + /// + /// true + /// + True, + + /// + /// false + /// + False, + + /// + /// A reference to a native/intrinsic parameter. + /// + NativeParm, + + /// + /// none + /// + NoObject, + + /// + /// (string) + /// + ResizeString, + + /// + /// byte(0xFF) + /// + IntConstByte, + + /// + /// (bool) + /// + BoolVariable, + + /// + /// Class(Expr) + /// + DynamicCast, + + /// + /// foreach expr + /// + Iterator, + + /// + /// break; + /// + IteratorPop, + + /// + /// continue; /// - ReturnNothing = 0x3A, + IteratorNext, - // UE2:ReturnNothing (Deprecated) - DelegateCmpEq = 0x3B, - DelegateCmpNE = 0x3C, - DelegateFunctionCmpEq = 0x3D, - DelegateFunctionCmpNE = 0x3E, - NoDelegate = 0x3F, + /// + /// A == B + /// + StructCmpEq, + + /// + /// A != B + /// + StructCmpNe, + + StructConst, - // Note: These byte-codes have shifted since UE3 and have therefor incorrect values assigned. - #region FixedByteCodes - DynArrayInsert = 0x40, - DynArrayRemove = 0x41, - DebugInfo = 0x42, - DelegateFunction = 0x43, - DelegateProperty = 0x44, - LetDelegate = 0x45, /// - /// UE3: An alternative to Else-If statements using A ? B : C; + /// "Unicode characters" /// - Conditional = 0x46, // CONDITION ? TRUE_LET : FALSE_LET + UnicodeStringConst, + /// - /// Redefined(ObjectToBool,DynArrayFind) + /// rng(a, b) + /// + RangeConst, + + /// + /// Struct.Member + /// + StructMember, + + /// + /// Array.Length /// - /// UE1: As an ObjectToBool cast. - /// UE2: As an indicator of a function's end(unless preceded by PrimitiveCast then it is treat as an ObjectToBool). - /// UE3: See DynArrayFind(See EndOfScript). + /// aka DynArrayCount /// - FunctionEnd = 0x47, + DynArrayLength, + /// - /// Find an item within an Array. + /// global.VirtualFunction(params...) /// - DynArrayFind = 0x47, // ARRAY.Find( EXPRESSION ) + GlobalFunction, + /// - /// UE3: Find an item within a struct in an Array. + /// primitiveKeyword(Expr) /// - DynArrayFindStruct = 0x48, // ARRAY.Find( EXPRESSION, EXPRESSION ) + PrimitiveCast, + /// - /// In some Unreal Engine 2 games, see Conditional for Unreal Engine 3. + /// return; + /// + ReturnNothing, + + /// + /// expr == expr + /// + DelegateCmpEq, + + /// + /// expr != expr + /// + DelegateCmpNe, + + /// + /// expr == expr + /// + DelegateFunctionCmpEq, + + /// + /// expr != expr + /// + DelegateFunctionCmpNe, + + /// + /// none + /// + EmptyDelegate, + + /// + /// DynamicArray.Insert(...) + /// + DynArrayInsert, + + /// + /// DynamicArray.Remove(...) + /// + DynArrayRemove, + + /// + /// Marks the current line number and the control context. + /// + DebugInfo, + + /// + /// A reference to a declared delegate function. + /// + DelegateFunction, + + /// + /// A reference to to a declared delegate property. + /// + DelegateProperty, + + /// + /// expr = expr; + /// + LetDelegate, + + /// + /// expr ? expr : expr + /// + Conditional, + + /// + /// Array.Find(expr) + /// + DynArrayFind, + + /// + /// ArrayOfStructs.Find(expr, expr) + /// + DynArrayFindStruct, + + /// + /// A reference to an out variable /// - /// An alternative to Else-If statements using A ? B : C;. - /// - Eval = 0x48, // See Conditional - /// - /// UE3: Reference to a property with the Out modifier. - /// - OutVariable = 0x49, - /// - /// UE3: Default value of a parameter property. - /// - DefaultParmValue = 0x4A, // PARAMETER = EXPRESSION - /// - /// UE3: No parameter value was given e.g: Foo( Foo,, Foo ); - /// - EmptyParmValue = 0x4B, // Empty argument, Call(Parm1,,Parm2) - InstanceDelegate = 0x4C, - VarInt = 0x4D, // Found in Borderlands 2 - VarFloat = 0x4E, // Found in Borderlands 2 - VarByte = 0x4F, // Found in Borderlands 2 - VarBool = 0x50, // Found in Borderlands 2 - VarObject = 0x51, // Found in Borderlands 2 - StringRef = 0x50, // Found in Mirrors Edge - UndefinedVariable = 0x51, // Found in Gears of War - InterfaceContext = 0x52, - InterfaceCast = 0x53, - EndOfScript = 0x54, - DynArrayAdd = 0x55, - DynArrayAddItem = 0x56, - DynArrayRemoveItem = 0x57, - DynArrayInsertItem = 0x58, - DynArrayIterator = 0x59, - DynArraySort = 0x5A, - FilterEditorOnly = 0x5B, // filtereditoronly { BLOCK } - Unused5C = 0x5C, - Unused5D = 0x5D, - Unused5E = 0x5E, - Unused5F = 0x5F, - #endregion + /// + /// (out int parameter) + /// + /// + OutVariable, + + /// + /// (int parameter = expr) + /// + DefaultParmValue, + + /// + /// Indicates an empty argument was passed to a function call e.g. Call(1,, 1) + /// + EmptyParmValue, + + /// + /// A reference to a delegate (but by name). + /// + InstanceDelegate, + + /// + /// expr.expr + /// + InterfaceContext, + + /// + /// InterfaceClass(expr) + /// + InterfaceCast, + + /// + /// 0xFFFF + /// + PointerConst, + + /// + /// Indicates the end of the script. + /// + EndOfScript, + + /// + /// DynamicArray.Add(expr) + /// + DynArrayAdd, + + /// + /// DynamicArray.AddItem(expr) + /// + DynArrayAddItem, + + /// + /// DynamicArray.AddItem(expr) + /// + DynArrayRemoveItem, + + /// + /// DynamicArray.InsertItem(expr, expr) + /// + DynArrayInsertItem, + + /// + /// foreach expr (expr, expr?) + /// + DynArrayIterator, + + /// + /// DynamicArray.Sort(expr) + /// + DynArraySort, + + /// + /// DynamicArray.Empty(expr?) + /// + DynArrayEmpty, + + /// + /// JumpIfNot statement + /// + /// FilterEditorOnly + /// { + /// BLOCK + /// } + /// + FilterEditorOnly, + + NativeFunction, ExtendedNative = 0x60, FirstNative = 0x70, MaxNative = 0x1000, - - MaxNonNative = ExtendedNative - 1, - InternalUnresolved = MaxNonNative, - Unused = InternalUnresolved, } + /// + /// These tokens begin at-non-zero because they were once part of < . + /// Handled by token: + /// public enum CastToken : byte { None = 0x00, @@ -212,22 +493,26 @@ public enum CastToken : byte InterfaceToBool = 0x38, #endregion - RotatorToVector = 0x39, // Redefined - ByteToInt = 0x3A, // Redefined(ReturnNothing) + RotatorToVector = 0x39, + ByteToInt = 0x3A, ByteToBool = 0x3B, ByteToFloat = 0x3C, IntToByte = 0x3D, IntToBool = 0x3E, IntToFloat = 0x3F, - BoolToByte = 0x40, // Redefined - BoolToInt = 0x41, // Redefined - BoolToFloat = 0x42, // Redefined - FloatToByte = 0x43, // Redefined - FloatToInt = 0x44, // Redefined - FloatToBool = 0x45, // Redefined + BoolToByte = 0x40, + BoolToInt = 0x41, + BoolToFloat = 0x42, + FloatToByte = 0x43, + FloatToInt = 0x44, + FloatToBool = 0x45, + /// + /// UE1: StringToName + /// UE2: Deprecated? + /// ObjectToInterface = 0x46, - ObjectToBool = 0x47, // Redefined - NameToBool = 0x48, // Redefined + ObjectToBool = 0x47, + NameToBool = 0x48, StringToByte = 0x49, StringToInt = 0x4A, StringToBool = 0x4B, @@ -288,4 +573,4 @@ public enum DebugInfo OperEFP = EFPOper, IterEFP = EFPIter, } -} \ No newline at end of file +} diff --git a/src/UnrealTypes.cs b/src/UnrealTypes.cs index 5eabd03f..a3010704 100644 --- a/src/UnrealTypes.cs +++ b/src/UnrealTypes.cs @@ -12,25 +12,24 @@ public enum PropertyType : byte FloatProperty = 4, ObjectProperty = 5, // Object, Component, Interface NameProperty = 6, - StringProperty = 7, // (Fixed String UE1) - DelegateProperty = 7, // (Delegate UE2+) - ClassProperty = 8, // Deprecated??? - ArrayProperty = 9, // Dynamic Array + StringProperty = 7, // <= UE1, fixed string + DelegateProperty = StringProperty, // >= UE2, displaced StringProperty + ClassProperty = 8, + ArrayProperty = 9, // >= UT, dynamic array StructProperty = 10, // Struct, Pointer // 11, 12 moved to hardcoded structs. - StrProperty = 13, // Dynamic string(UE2+) - MapProperty = 14, - FixedArrayProperty = 15, // Fixed Array + StrProperty = 13, // >= UT, dynamic string + MapProperty = 14, // >= UT + FixedArrayProperty = 15, // >= UT, fixed array, < UE3 + PointerProperty = 16, // >= UE2.5 (UT2004), < UE3 #if BIOSHOCK QwordProperty, // (UE3, Bioshock Infinite) XWeakReferenceProperty, #endif - // Temp - PointerProperty, // (UE2) - InterfaceProperty, // (UE3) - ComponentProperty, // (UE3) + InterfaceProperty, // >= UE3, displaced FixedArrayProperty, actual value 15, but we don't need the value for UE3 types. + ComponentProperty, // >= UE3 StructOffset = (1 + ComponentProperty), @@ -53,5 +52,10 @@ public enum PropertyType : byte TwoVectors = (16 + StructOffset), //InterpCurve = (17 + PropertyType.StructOffset) //InterpCurvePoint = (18 + PropertyType.StructOffset) + PointRegion = (19 + StructOffset), + + // Auto-conversions for old (<= 61) "StructName"s + Rotation = Rotator, + Region = PointRegion, } -} \ No newline at end of file +} diff --git a/src/app.config b/src/app.config new file mode 100644 index 00000000..1696df66 --- /dev/null +++ b/src/app.config @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file