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 static string Packages_Path {
}
}
- ///
- /// 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 static byte[] TestUC3 {
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 @@ public static explicit operator string(UObject obj)
{
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 static explicit operator int(UName a)
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 ObjectFlagsHO : ulong
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 @@ public BuildAttribute(int minVersion, int maxVersion, uint minLicensee, uint max
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 T FindObject(string objectName, bool checkForSubclass = false) where T
[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