From c339a055b45400fc6f117fd35a35706203188da5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Jan 2024 00:37:24 +0000 Subject: [PATCH 01/11] Bump actions/setup-dotnet from 3 to 4 Bumps [actions/setup-dotnet](https://github.com/actions/setup-dotnet) from 3 to 4. - [Release notes](https://github.com/actions/setup-dotnet/releases) - [Commits](https://github.com/actions/setup-dotnet/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/setup-dotnet dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 06c469a8..e535d8e4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -16,7 +16,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Setup .NET - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4 with: # Legacy versions not supported? :( # 5.0 will likely not work yet due legacy dependencies... From e84188736af47840eb872e42135b541d4520dacc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Feb 2024 00:15:12 +0000 Subject: [PATCH 02/11] Bump NuGet/setup-nuget from 1 to 2 Bumps [NuGet/setup-nuget](https://github.com/nuget/setup-nuget) from 1 to 2. - [Release notes](https://github.com/nuget/setup-nuget/releases) - [Commits](https://github.com/nuget/setup-nuget/compare/v1...v2) --- updated-dependencies: - dependency-name: NuGet/setup-nuget dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 47a11edc..d81d27b6 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -18,7 +18,7 @@ jobs: uses: microsoft/setup-msbuild@v1.3 - name: Setup NuGet - uses: NuGet/setup-nuget@v1 + uses: NuGet/setup-nuget@v2 - name: Restore dependencies run: nuget restore src/Eliot.UELib.csproj From f97401ba0262595256700484e54702e25166e464 Mon Sep 17 00:00:00 2001 From: Eliot Date: Wed, 3 Apr 2024 00:06:02 +0200 Subject: [PATCH 03/11] Update README.md --- README.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b1167fc4..c72eea6e 100644 --- a/README.md +++ b/README.md @@ -8,11 +8,18 @@ Its main purpose is to decompile the UnrealScript byte-code to its original sour It accomplishes this by reading the necessary Unreal data classes such as: - UObject, UField, UConst, UEnum, UProperty, UStruct, UFunction, UState, - UClass, UTextBuffer, UMetaData, UFont, USound, UPackage + UObject, UField, UConst, UEnum, UProperty, UStruct, UFunction, UState, UClass, + UTextBuffer, UMetaData, UPackage Classes such as UStruct, UState, UClass, and UFunction contain the UnrealScript byte-code which we can deserialize in order to re-construct the byte-codes to its original UnrealScript source. +Additionally UELib is also capable of deserializing of many more data classes such as: + + UFont, USound, UPalette, UTexture, + UTexture2D, UTexture2DDynamic, UTexture2DComposite, UTexture3D, + UTextureCube, UTextureFlipBook, UTextureMovie + UPrimitive, UPolys + ## How to use To use this library you will need [.NET Framework 4.8](https://dotnet.microsoft.com/en-us/download/dotnet-framework/net48) (The library will move to .NET 6 or higher at version 2.0) From 119fcb49984c02eeb85cf985c56ec2abe092f268 Mon Sep 17 00:00:00 2001 From: Eliot Date: Wed, 3 Apr 2024 00:50:29 +0200 Subject: [PATCH 04/11] Update publish.yml --- .github/workflows/publish.yml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index d81d27b6..b2298cd7 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -3,9 +3,8 @@ name: Build .NET Framework and Publish to NuGet on: workflow_dispatch: push: - tags: - - 'release-*' - - 'hotfix-*' + tags: + - '^[0-9]+\.[0-9]+\.[0-9]+$' jobs: build: @@ -24,7 +23,7 @@ jobs: run: nuget restore src/Eliot.UELib.csproj - name: Build - run: msbuild src/Eliot.UELib.csproj -t:rebuild -property:Configuration=Publish + run: msbuild src/Eliot.UELib.csproj -t:rebuild -property:Configuration=Remease - name: Publish Eliot.UELib uses: alirezanet/publish-nuget@v3.1.0 From 4a88e0a59ad9476e8d2615113c23968930a20fe6 Mon Sep 17 00:00:00 2001 From: UnDrew Date: Sat, 6 Apr 2024 20:42:42 +0300 Subject: [PATCH 05/11] Support for AHIT packages: - Added AHIT constant and game build. - This only gets applied from 1.0 upwards, as anything before that doesn't seem to have any changes to Core types. - UClass now reads the Unused Optional Functions list they implemented, thus the rest of the properties that follow it are correctly aligned. - Added support for AHIT-specific specifiers, which will now decompile correctly. --- src/Core/Classes/Props/UPropertyDecompiler.cs | 22 +++++++- src/Core/Classes/UClass.cs | 15 ++++-- src/Core/Classes/UClassDecompiler.cs | 14 ++++++ src/Core/Classes/UFunctionDecompiler.cs | 50 +++++++++++++++---- src/Eliot.UELib.csproj | 10 ++-- src/UnrealFlags.cs | 17 +++++++ src/UnrealPackage.cs | 7 +++ 7 files changed, 117 insertions(+), 18 deletions(-) diff --git a/src/Core/Classes/Props/UPropertyDecompiler.cs b/src/Core/Classes/Props/UPropertyDecompiler.cs index b5173501..3d52a2d9 100644 --- a/src/Core/Classes/Props/UPropertyDecompiler.cs +++ b/src/Core/Classes/Props/UPropertyDecompiler.cs @@ -317,6 +317,22 @@ public string FormatFlags() output += "serializetext "; copyFlags &= ~(ulong)Flags.PropertyFlagsLO.SerializeText; } + +#if AHIT + if (Package.Build == UnrealPackage.GameBuild.BuildName.AHIT) + { + if (HasPropertyFlag(Flags.PropertyFlagsHO.AHIT_Serialize)) + { + output += "serialize "; + copyFlags &= ~(ulong)Flags.PropertyFlagsHO.AHIT_Serialize << 32; + } + if (HasPropertyFlag(Flags.PropertyFlagsLO.AHIT_Bitwise)) + { + output += "bitwise "; + copyFlags &= ~(ulong)Flags.PropertyFlagsLO.AHIT_Bitwise; + } + } +#endif } if ((PropertyFlags & (ulong)Flags.PropertyFlagsLO.Native) != 0) @@ -408,7 +424,11 @@ public string FormatFlags() } } - if ((PropertyFlags & (ulong)Flags.PropertyFlagsLO.EdFindable) != 0) + if ((PropertyFlags & (ulong)Flags.PropertyFlagsLO.EdFindable) != 0 +#if AHIT + && Package.Build != UnrealPackage.GameBuild.BuildName.AHIT +#endif + ) { copyFlags &= ~(ulong)Flags.PropertyFlagsLO.EdFindable; output += "edfindable "; diff --git a/src/Core/Classes/UClass.cs b/src/Core/Classes/UClass.cs index 40538a02..e1756427 100644 --- a/src/Core/Classes/UClass.cs +++ b/src/Core/Classes/UClass.cs @@ -211,6 +211,15 @@ protected override void Deserialize() if (Package.Version >= 369) DeserializeInterfaces(); } +#if AHIT + if (Package.Build == UnrealPackage.GameBuild.BuildName.AHIT && Package.Version >= 878) + { + // AHIT auto-generates a list of unused function names for its optional interface functions. + // Seems to have been added in 878, during the modding beta between 1.Nov.17 and 6.Jan.18. + DeserializeGroup("UnusedOptionalInterfaceFunctions"); + } +#endif + if (!Package.IsConsoleCooked() && !Package.Build.Flags.HasFlag(BuildFlags.XenonCooked)) { if (Package.Version >= 603 @@ -511,9 +520,9 @@ protected override void FindChildren() States.Insert(0, (UState)child); } - #endregion +#endregion - #region Methods +#region Methods private IList DeserializeGroup(string groupName = "List", int count = -1) { @@ -562,6 +571,6 @@ public bool IsClassWithin() return Within != null && !string.Equals(Within.Name, "Object", StringComparison.OrdinalIgnoreCase); } - #endregion +#endregion } } \ No newline at end of file diff --git a/src/Core/Classes/UClassDecompiler.cs b/src/Core/Classes/UClassDecompiler.cs index d0335451..36cc4a0d 100644 --- a/src/Core/Classes/UClassDecompiler.cs +++ b/src/Core/Classes/UClassDecompiler.cs @@ -423,6 +423,20 @@ private string FormatFlags() } } +#if AHIT + if (Package.Build == UnrealPackage.GameBuild.BuildName.AHIT) + { + if ((ClassFlags & (uint)Flags.ClassFlags.AHIT_AlwaysLoaded) != 0) + { + output += "\r\n\tAlwaysLoaded"; + } + if ((ClassFlags & (uint)Flags.ClassFlags.AHIT_IterOptimized) != 0) + { + output += "\r\n\tIterationOptimized"; + } + } +#endif + output += FormatNameGroup("dontsortcategories", DontSortCategories); output += FormatNameGroup("hidecategories", HideCategories); output += FormatNameGroup("classgroup", ClassGroups); diff --git a/src/Core/Classes/UFunctionDecompiler.cs b/src/Core/Classes/UFunctionDecompiler.cs index cb371d81..22e8d0af 100644 --- a/src/Core/Classes/UFunctionDecompiler.cs +++ b/src/Core/Classes/UFunctionDecompiler.cs @@ -104,20 +104,44 @@ private string FormatFlags() output += "noexport "; } - if (HasFunctionFlag(Flags.FunctionFlags.K2Call)) +#if AHIT + if (Package.Build == UnrealPackage.GameBuild.BuildName.AHIT) { - output += "k2call "; - } + if (HasFunctionFlag(Flags.FunctionFlags.AHIT_Optional)) + { + output += "optional "; // optional interface functions use this. + } - if (HasFunctionFlag(Flags.FunctionFlags.K2Override)) - { - output += "k2override "; - } + if (HasFunctionFlag(Flags.FunctionFlags.AHIT_Multicast)) + { + output += "multicast "; + } - if (HasFunctionFlag(Flags.FunctionFlags.K2Pure)) + if (HasFunctionFlag(Flags.FunctionFlags.AHIT_NoOwnerRepl)) + { + output += "NoOwnerReplication "; + } + } + else // For AHIT, don't write these K2 specifiers, since they overlap with its custom flags. { - output += "k2pure "; +#endif + if (HasFunctionFlag(Flags.FunctionFlags.K2Call)) + { + output += "k2call "; + } + + if (HasFunctionFlag(Flags.FunctionFlags.K2Override)) + { + output += "k2override "; + } + + if (HasFunctionFlag(Flags.FunctionFlags.K2Pure)) + { + output += "k2pure "; + } +#if AHIT } +#endif if (HasFunctionFlag(Flags.FunctionFlags.Invariant)) { @@ -185,6 +209,14 @@ private string FormatFlags() output += "function "; } +#if AHIT + // Needs to be after function/event/operator/etc. + if (Package.Build == UnrealPackage.GameBuild.BuildName.AHIT && HasFunctionFlag(Flags.FunctionFlags.AHIT_EditorOnly)) + { + output += "editoronly "; + } +#endif + return output; } diff --git a/src/Eliot.UELib.csproj b/src/Eliot.UELib.csproj index 5fdb3a3b..fd9e0cc0 100644 --- a/src/Eliot.UELib.csproj +++ b/src/Eliot.UELib.csproj @@ -44,7 +44,7 @@ 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 + 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 AHIT prompt 0 AnyCPU @@ -62,7 +62,7 @@ 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 + 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 AHIT prompt 4 @@ -74,7 +74,7 @@ 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 + 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 AHIT false @@ -86,7 +86,7 @@ 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 + 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 AHIT true AnyCPU @@ -98,7 +98,7 @@ 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 + 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 AHIT true 0 full diff --git a/src/UnrealFlags.cs b/src/UnrealFlags.cs index efca6f0b..31f1fe8c 100644 --- a/src/UnrealFlags.cs +++ b/src/UnrealFlags.cs @@ -217,6 +217,12 @@ public enum ObjectFlagsHO : ulong K2Call = 0x04000000U, K2Override = 0x08000000U, // K2Call? K2Pure = 0x10000000U, +#if AHIT + AHIT_Multicast = 0x04000000U, + AHIT_NoOwnerRepl = 0x08000000U, + AHIT_Optional = 0x10000000U, + AHIT_EditorOnly = 0x20000000U, +#endif } /// @@ -297,6 +303,9 @@ public enum ObjectFlagsHO : ulong EditInline = 0x04000000U, EdFindable = 0x08000000U, +#if AHIT + AHIT_Bitwise = 0x08000000U, +#endif EditInlineUse = 0x10000000U, Deprecated = 0x20000000U, @@ -345,6 +354,9 @@ public enum ObjectFlagsHO : ulong // GAP! CrossLevelPassive = 0x00001000U, CrossLevelActive = 0x00002000U, +#if AHIT + AHIT_Serialize = 0x00004000U, +#endif #if BIOSHOCK BIOINF_Unk1 = 0x00080000U, @@ -403,6 +415,11 @@ public enum StateFlags : uint CollapseCategories = 0x00002000U, ExportStructs = 0x00004000U, // @Removed(UE3 in early but not latest) +#if AHIT + AHIT_AlwaysLoaded = 0x00008000U, + AHIT_IterOptimized = 0x00010000U, +#endif + Instanced = 0x00200000U, // @Removed(UE3) HideDropDown = 0x00400000U, // @Redefined(UE3, HasComponents), @Moved(UE3, HideDropDown2) ParseConfig = 0x01000000U, // @Redefined(UE3, Deprecated) diff --git a/src/UnrealPackage.cs b/src/UnrealPackage.cs index b6eee5dd..645de380 100644 --- a/src/UnrealPackage.cs +++ b/src/UnrealPackage.cs @@ -536,6 +536,13 @@ public enum BuildName /// 904/009 /// [Build(904, 904, 09u, 014u)] SpecialForce2, + + /// + /// A Hat in Time + /// 877:893/005 + /// The earliest version in which I found any custom specifiers is 1.0 (877). + /// + [Build(877, 893, 5, 5)] AHIT, } public BuildName Name { get; } From b3cb40efd25e726f82e9458c40c044d37b7bd9dc Mon Sep 17 00:00:00 2001 From: UnDrew Date: Sat, 6 Apr 2024 20:42:42 +0300 Subject: [PATCH 06/11] Support for AHIT packages: - Added AHIT constant and game build. - This only gets applied from 1.0 upwards, as anything before that doesn't seem to have any changes to Core types. - UClass now reads the Unused Optional Functions list they implemented, thus the rest of the properties that follow it are correctly aligned. - Added support for AHIT-specific specifiers, which will now decompile correctly. --- src/Core/Classes/Props/UPropertyDecompiler.cs | 22 +++++++++- src/Core/Classes/UClass.cs | 10 +++++ src/Core/Classes/UClassDecompiler.cs | 14 ++++++ src/Core/Classes/UFunctionDecompiler.cs | 43 ++++++++++++++++--- src/Eliot.UELib.csproj | 2 +- src/UnrealFlags.cs | 17 ++++++++ src/UnrealPackage.cs | 9 ++++ 7 files changed, 110 insertions(+), 7 deletions(-) diff --git a/src/Core/Classes/Props/UPropertyDecompiler.cs b/src/Core/Classes/Props/UPropertyDecompiler.cs index 4e14c173..922b72e1 100644 --- a/src/Core/Classes/Props/UPropertyDecompiler.cs +++ b/src/Core/Classes/Props/UPropertyDecompiler.cs @@ -302,6 +302,22 @@ public string FormatFlags() output += "serializetext "; copyFlags &= ~(ulong)Flags.PropertyFlagsLO.SerializeText; } + +#if AHIT + if (Package.Build == UnrealPackage.GameBuild.BuildName.AHIT) + { + if (HasPropertyFlag(Flags.PropertyFlagsHO.AHIT_Serialize)) + { + output += "serialize "; + copyFlags &= ~(ulong)Flags.PropertyFlagsHO.AHIT_Serialize << 32; + } + if (HasPropertyFlag(Flags.PropertyFlagsLO.AHIT_Bitwise)) + { + output += "bitwise "; + copyFlags &= ~(ulong)Flags.PropertyFlagsLO.AHIT_Bitwise; + } + } +#endif } if ((PropertyFlags & (ulong)Flags.PropertyFlagsLO.Native) != 0) @@ -393,7 +409,11 @@ public string FormatFlags() } } - if ((PropertyFlags & (ulong)Flags.PropertyFlagsLO.EdFindable) != 0) + if ((PropertyFlags & (ulong)Flags.PropertyFlagsLO.EdFindable) != 0 +#if AHIT + && Package.Build != UnrealPackage.GameBuild.BuildName.AHIT +#endif + ) { copyFlags &= ~(ulong)Flags.PropertyFlagsLO.EdFindable; output += "edfindable "; diff --git a/src/Core/Classes/UClass.cs b/src/Core/Classes/UClass.cs index 723aa3fb..54c44bb6 100644 --- a/src/Core/Classes/UClass.cs +++ b/src/Core/Classes/UClass.cs @@ -261,6 +261,16 @@ protected override void Deserialize() Record(nameof(classGeneratedBy), classGeneratedBy); } #endif + +#if AHIT + if (Package.Build == UnrealPackage.GameBuild.BuildName.AHIT && _Buffer.Version >= 878) + { + // AHIT auto-generates a list of unused function names for its optional interface functions. + // Seems to have been added in 878, during the modding beta between 1.Nov.17 and 6.Jan.18. + DeserializeGroup("UnusedOptionalInterfaceFunctions"); + } +#endif + if (!Package.IsConsoleCooked() && !Package.Build.Flags.HasFlag(BuildFlags.XenonCooked)) { if (_Buffer.Version >= 603 && _Buffer.UE4Version < 113 diff --git a/src/Core/Classes/UClassDecompiler.cs b/src/Core/Classes/UClassDecompiler.cs index 4530b7e5..754e770c 100644 --- a/src/Core/Classes/UClassDecompiler.cs +++ b/src/Core/Classes/UClassDecompiler.cs @@ -376,6 +376,20 @@ private string FormatFlags() } #endif +#if AHIT + if (Package.Build == UnrealPackage.GameBuild.BuildName.AHIT) + { + if ((ClassFlags & (uint)Flags.ClassFlags.AHIT_AlwaysLoaded) != 0) + { + output += "\r\n\tAlwaysLoaded"; + } + if ((ClassFlags & (uint)Flags.ClassFlags.AHIT_IterOptimized) != 0) + { + output += "\r\n\tIterationOptimized"; + } + } +#endif + output += FormatNameGroup("classgroup", ClassGroups); output += FormatNameGroup("autoexpandcategories", AutoExpandCategories); output += FormatNameGroup("autocollapsecategories", AutoCollapseCategories); diff --git a/src/Core/Classes/UFunctionDecompiler.cs b/src/Core/Classes/UFunctionDecompiler.cs index d4ff6bdd..ddb75d98 100644 --- a/src/Core/Classes/UFunctionDecompiler.cs +++ b/src/Core/Classes/UFunctionDecompiler.cs @@ -99,19 +99,44 @@ private string FormatFlags() { output += "noexport "; } + +#if AHIT + if (Package.Build == UnrealPackage.GameBuild.BuildName.AHIT) + { + if (HasFunctionFlag(Flags.FunctionFlags.AHIT_Optional)) + { + output += "optional "; // optional interface functions use this. + } + + if (HasFunctionFlag(Flags.FunctionFlags.AHIT_Multicast)) + { + output += "multicast "; + } + + if (HasFunctionFlag(Flags.FunctionFlags.AHIT_NoOwnerRepl)) + { + output += "NoOwnerReplication "; + } + } +#endif // FIXME: Version, added with one of the later UDK builds. - if (Package.Version >= 500) + if (Package.Version >= 500 +#if AHIT + // For AHIT, don't write these K2 specifiers, since they overlap with its custom flags. + && Package.Build != UnrealPackage.GameBuild.BuildName.AHIT +#endif + ) { 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)) { @@ -212,6 +237,14 @@ private string FormatFlags() output += "function "; } +#if AHIT + // Needs to be after function/event/operator/etc. + if (Package.Build == UnrealPackage.GameBuild.BuildName.AHIT && HasFunctionFlag(Flags.FunctionFlags.AHIT_EditorOnly)) + { + output += "editoronly "; + } +#endif + return output; } diff --git a/src/Eliot.UELib.csproj b/src/Eliot.UELib.csproj index 0a0da087..455ff894 100644 --- a/src/Eliot.UELib.csproj +++ b/src/Eliot.UELib.csproj @@ -1,6 +1,6 @@  - 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 + 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;AHIT net48 Library UELib diff --git a/src/UnrealFlags.cs b/src/UnrealFlags.cs index 7300a041..4c612209 100644 --- a/src/UnrealFlags.cs +++ b/src/UnrealFlags.cs @@ -344,6 +344,12 @@ public enum FunctionFlags : ulong K2Call = 0x04000000U, K2Override = 0x08000000U, K2Pure = 0x10000000U, +#if AHIT + AHIT_Multicast = 0x04000000U, + AHIT_NoOwnerRepl = 0x08000000U, + AHIT_Optional = 0x10000000U, + AHIT_EditorOnly = 0x20000000U, +#endif } /// @@ -424,6 +430,9 @@ public enum FunctionFlags : ulong EditInline = 0x04000000U, EdFindable = 0x08000000U, +#if AHIT + AHIT_Bitwise = 0x08000000U, +#endif EditInlineUse = 0x10000000U, Deprecated = 0x20000000U, @@ -472,6 +481,9 @@ public enum FunctionFlags : ulong // GAP! CrossLevelPassive = 0x00001000U, CrossLevelActive = 0x00002000U, +#if AHIT + AHIT_Serialize = 0x00004000U, +#endif #if BIOSHOCK BIOINF_Unk1 = 0x00080000U, @@ -530,6 +542,11 @@ public enum StateFlags : uint CollapseCategories = 0x00002000U, ExportStructs = 0x00004000U, // @Removed(UE3 in early but not latest) +#if AHIT + AHIT_AlwaysLoaded = 0x00008000U, + AHIT_IterOptimized = 0x00010000U, +#endif + Instanced = 0x00200000U, // @Removed(UE3) HideDropDown = 0x00400000U, // @Redefined(UE3, HasComponents), @Moved(UE3, HideDropDown2) ParseConfig = 0x01000000U, // @Redefined(UE3, Deprecated) diff --git a/src/UnrealPackage.cs b/src/UnrealPackage.cs index 5a1f9f23..af44b18c 100644 --- a/src/UnrealPackage.cs +++ b/src/UnrealPackage.cs @@ -594,6 +594,15 @@ public enum BuildName [Build(904, 904, 09u, 014u)] [OverridePackageVersion((uint)PackageObjectLegacyVersion.ProbeMaskReducedAndIgnoreMaskRemoved)] SpecialForce2, + + /// + /// A Hat in Time + /// + /// 877:893/005 + /// + /// The earliest available version with any custom specifiers is 1.0 (877). + /// + [Build(877, 893, 5, 5)] AHIT, } public BuildName Name { get; } From e10614578804f8d392ffa622d0de64adbad85853 Mon Sep 17 00:00:00 2001 From: UnDrew Date: Sun, 7 Apr 2024 10:08:49 +0300 Subject: [PATCH 07/11] Reverted some automatic indentation changes VS, what's wrong with you? --- src/Core/Classes/UClass.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Core/Classes/UClass.cs b/src/Core/Classes/UClass.cs index f3a0f4db..54c44bb6 100644 --- a/src/Core/Classes/UClass.cs +++ b/src/Core/Classes/UClass.cs @@ -608,9 +608,9 @@ protected override void FindChildren() States.Insert(0, (UState)child); } -#endregion + #endregion -#region Methods + #region Methods private IList DeserializeGroup(string groupName = "List", int count = -1) { @@ -659,7 +659,7 @@ public bool IsClassWithin() return Within != null && !string.Equals(Within.Name, "Object", StringComparison.OrdinalIgnoreCase); } -#endregion + #endregion } [UnrealRegisterClass] From 68cba6eaf271059e073f7bda666aaad038f69184 Mon Sep 17 00:00:00 2001 From: UnDrew Date: Sun, 7 Apr 2024 10:15:51 +0300 Subject: [PATCH 08/11] Removed accidental AHIT class specifier dupe --- src/Core/Classes/UClassDecompiler.cs | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/src/Core/Classes/UClassDecompiler.cs b/src/Core/Classes/UClassDecompiler.cs index 3a54a38a..754e770c 100644 --- a/src/Core/Classes/UClassDecompiler.cs +++ b/src/Core/Classes/UClassDecompiler.cs @@ -340,20 +340,6 @@ private string FormatFlags() // } //} -#if AHIT - if (Package.Build == UnrealPackage.GameBuild.BuildName.AHIT) - { - if ((ClassFlags & (uint)Flags.ClassFlags.AHIT_AlwaysLoaded) != 0) - { - output += "\r\n\tAlwaysLoaded"; - } - if ((ClassFlags & (uint)Flags.ClassFlags.AHIT_IterOptimized) != 0) - { - output += "\r\n\tIterationOptimized"; - } - } -#endif - output += FormatNameGroup("dontsortcategories", DontSortCategories); output += FormatNameGroup("hidecategories", HideCategories); // TODO: Decompile ShowCategories (but this is not possible without traversing the super chain) From de3cccec6ee05bfb13abed967eb1e7c3f005017d Mon Sep 17 00:00:00 2001 From: Eliot Date: Sun, 7 Apr 2024 16:46:04 +0200 Subject: [PATCH 09/11] Update UnrealPackage.cs --- src/UnrealPackage.cs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/UnrealPackage.cs b/src/UnrealPackage.cs index af44b18c..b7ab6ce1 100644 --- a/src/UnrealPackage.cs +++ b/src/UnrealPackage.cs @@ -586,6 +586,15 @@ public enum BuildName /// [Build(874, 78u)] Battleborn, + /// + /// A Hat in Time + /// + /// 877:893/005 + /// + /// The earliest available version with any custom specifiers is 1.0 (877) - Un-Drew. + /// + [Build(877, 893, 5, 5)] AHIT, + /// /// Special Force 2 /// @@ -594,15 +603,6 @@ public enum BuildName [Build(904, 904, 09u, 014u)] [OverridePackageVersion((uint)PackageObjectLegacyVersion.ProbeMaskReducedAndIgnoreMaskRemoved)] SpecialForce2, - - /// - /// A Hat in Time - /// - /// 877:893/005 - /// - /// The earliest available version with any custom specifiers is 1.0 (877). - /// - [Build(877, 893, 5, 5)] AHIT, } public BuildName Name { get; } @@ -2267,4 +2267,4 @@ public void Dispose() #endregion } -} \ No newline at end of file +} From 79fd5b22eacb31eceeb2bc081fd94dd0396a0cbe Mon Sep 17 00:00:00 2001 From: Eliot Date: Sun, 7 Apr 2024 23:41:43 +0200 Subject: [PATCH 10/11] Add "A Hat in Time" (UE3) --- CHANGELOG.md | 4 ++++ README.md | 1 + 2 files changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e68f3a0b..8bf9dd4a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# [1.5.0](https://github.com/EliotVU/Unreal-Library/releases/tag/1.5.0) + +* 1ef135d Improved support for A Hat in Time (UE3), contributed by @Un-Drew + # [1.4.0](https://github.com/EliotVU/Unreal-Library/releases/tag/1.4.0) Notable changes that affect UnrealScript output: diff --git a/README.md b/README.md index c72eea6e..d544761c 100644 --- a/README.md +++ b/README.md @@ -185,6 +185,7 @@ This is a table of games that are confirmed to be compatible with the current st | Orcs Must Die! Unchained | 20430 | 870/000 | | | Gal\*Gun: Double Peace | 10897 | 871/000 | | | [Might & Magic Heroes VII](https://en.wikipedia.org/wiki/Might_%26_Magic_Heroes_VII) | 12161 | 868/004 | (Signature and custom features are not supported) +| A Hat in Time | 12097 | 877-893/005 | | | Shadow Complex Remastered | 10897 | 893/001 | | | Soldier Front 2 | 6712 | 904/009 | | | Rise of the Triad | 10508 | Unknown | | From d4aca85a49ea0bc7312ecbe19395738741b22b24 Mon Sep 17 00:00:00 2001 From: Eliot Date: Mon, 8 Apr 2024 00:15:23 +0200 Subject: [PATCH 11/11] Bump version > 1.5.0; + minor changes. --- src/Branch/DefaultEngineBranch.cs | 2 ++ src/Core/Classes/UStruct.cs | 2 ++ src/Core/Tokens/ContextTokens.cs | 23 ++++++++++++----------- src/Eliot.UELib.csproj | 7 ++++--- src/Engine/Types/Poly.cs | 6 +++--- src/UnrealPackage.cs | 11 +++++++++-- 6 files changed, 32 insertions(+), 19 deletions(-) diff --git a/src/Branch/DefaultEngineBranch.cs b/src/Branch/DefaultEngineBranch.cs index 3054c87b..fe74cc6f 100644 --- a/src/Branch/DefaultEngineBranch.cs +++ b/src/Branch/DefaultEngineBranch.cs @@ -375,6 +375,8 @@ protected override TokenMap BuildTokenMap(UnrealPackage linker) tokenMap[0x4F] = typeof(LocalVariableToken); tokenMap[0x50] = typeof(LocalVariableToken); tokenMap[0x51] = typeof(LocalVariableToken); + + tokenMap[0x5B] = typeof(ByteConstToken); break; #endif #if BIOSHOCK diff --git a/src/Core/Classes/UStruct.cs b/src/Core/Classes/UStruct.cs index e4222cba..9cebe08e 100644 --- a/src/Core/Classes/UStruct.cs +++ b/src/Core/Classes/UStruct.cs @@ -69,6 +69,7 @@ public partial class UStruct : UField //protected uint _CodePosition; public long ScriptOffset { get; private set; } + public int ScriptSize { get; private set; } public UByteCodeDecompiler ByteCodeManager; @@ -233,6 +234,7 @@ protected override void Deserialize() } _Buffer.ConformRecordPosition(); + ScriptSize = (int)(_Buffer.Position - ScriptOffset); #if DNF if (Package.Build == UnrealPackage.GameBuild.BuildName.DNF) { diff --git a/src/Core/Tokens/ContextTokens.cs b/src/Core/Tokens/ContextTokens.cs index 9f7b18d0..89cf0d42 100644 --- a/src/Core/Tokens/ContextTokens.cs +++ b/src/Core/Tokens/ContextTokens.cs @@ -110,32 +110,33 @@ public override void Deserialize(IUnrealStream stream) Debug.Assert(Struct != null); } #endif - // TODO: Corrigate version. Definitely didn't exist in Roboblitz(369) - if (stream.Version > 369) + // TODO: Corrigate version. Definitely didn't exist in Roboblitz(369), first seen in MOHA(421). + if (stream.Version > 374) { Struct = stream.ReadObject(); Decompiler.AlignObjectSize(); Debug.Assert(Struct != null); #if MKKE - if (Package.Build != UnrealPackage.GameBuild.BuildName.MKKE) + if (Package.Build == UnrealPackage.GameBuild.BuildName.MKKE) { -#endif - // Copy? - stream.ReadByte(); - Decompiler.AlignSize(sizeof(byte)); -#if MKKE + goto skipToNext; } #endif + // Copy? + stream.ReadByte(); + Decompiler.AlignSize(sizeof(byte)); } - - // TODO: Corrigate version. Definitely didn't exist in MKKE(472), first seen in SWG(486). - if (stream.Version > 472) + + // TODO: Corrigate version. Definitely didn't exist in MKKE(472), first seen in FFOW(433). + if (stream.Version >= 433) { // Modification? stream.ReadByte(); Decompiler.AlignSize(sizeof(byte)); } + skipToNext: + // Pre-Context DeserializeNext(); } diff --git a/src/Eliot.UELib.csproj b/src/Eliot.UELib.csproj index 455ff894..57f0aec3 100644 --- a/src/Eliot.UELib.csproj +++ b/src/Eliot.UELib.csproj @@ -1,4 +1,4 @@ - + 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;AHIT net48 @@ -32,6 +32,7 @@ GlobalizationRules.ruleset + True none $(DefineConstants);TRACE; true @@ -70,10 +71,9 @@ - True Eliot.UELib $(AssemblyName) - $(VersionPrefix)1.4.0 + $(VersionPrefix)1.5.0 EliotVU $(AssemblyName) UnrealScript decompiler library for Unreal package files (.upk, .u, .uasset; etc), with support for Unreal Engine 1, 2, and 3. @@ -86,5 +86,6 @@ True True latest-recommended + Improved support for A Hat in Time (UE3) \ No newline at end of file diff --git a/src/Engine/Types/Poly.cs b/src/Engine/Types/Poly.cs index 9757c1f5..08b276e4 100644 --- a/src/Engine/Types/Poly.cs +++ b/src/Engine/Types/Poly.cs @@ -74,7 +74,7 @@ public TResult Accept(IVisitor visitor) public void Deserialize(IUnrealStream stream) { // Always 16 - int numVertices = stream.Version < (uint)PackageObjectLegacyVersion.FixedVerticesToArrayFromPoly + int verticesCount = stream.Version < (uint)PackageObjectLegacyVersion.FixedVerticesToArrayFromPoly ? stream.ReadIndex() : -1; @@ -89,7 +89,7 @@ public void Deserialize(IUnrealStream stream) } else { - stream.ReadArray(out Vertex, numVertices); + stream.ReadArray(out Vertex, verticesCount); } PolyFlags = stream.ReadUInt32(); @@ -170,4 +170,4 @@ public void Serialize(IUnrealStream stream) throw new NotImplementedException(); } } -} \ No newline at end of file +} diff --git a/src/UnrealPackage.cs b/src/UnrealPackage.cs index b7ab6ce1..5e6880d7 100644 --- a/src/UnrealPackage.cs +++ b/src/UnrealPackage.cs @@ -1511,9 +1511,16 @@ public void Deserialize(UPackageStream stream) "Branch.Serializer cannot be null. Did you forget to initialize the Serializer in PostDeserializeSummary?"); // We can't continue without decompressing. - if (CompressedChunks != null && CompressedChunks.Any()) + if (Summary.CompressedChunks != null && + Summary.CompressedChunks.Any()) { - return; + if (Summary.CompressionFlags != 0) + { + return; + } + + // Flags 0? Let's pretend that we no longer possess any chunks. + Summary.CompressedChunks.Clear(); } #if TERA if (Build == GameBuild.BuildName.Tera) Summary.NameCount = Generations.Last().NameCount;