From c27ebec10ef640fb44028a6644fe9357d59ff94d Mon Sep 17 00:00:00 2001 From: Razmoth <32140579+Razmoth@users.noreply.github.com> Date: Wed, 28 Feb 2024 16:28:36 +0400 Subject: [PATCH] - [Core] Added new entries. --- AssetStudio.Utility/ShaderConverter.cs | 4 +- AssetStudio/BlbFile.cs | 2 +- AssetStudio/BundleFile.cs | 48 ++++++++- AssetStudio/GameManager.cs | 6 ++ AssetStudio/LZ4/LZ4.cs | 70 ++++++++++++ AssetStudio/LZ4/LZ4Inv.cs | 9 ++ AssetStudio/LZ4/LZ4Lit.cs | 8 ++ AssetStudio/LZ4/LZ4Utils.cs | 143 ------------------------- AssetStudio/MhyFile.cs | 4 +- 9 files changed, 141 insertions(+), 153 deletions(-) create mode 100644 AssetStudio/LZ4/LZ4.cs create mode 100644 AssetStudio/LZ4/LZ4Inv.cs create mode 100644 AssetStudio/LZ4/LZ4Lit.cs delete mode 100644 AssetStudio/LZ4/LZ4Utils.cs diff --git a/AssetStudio.Utility/ShaderConverter.cs b/AssetStudio.Utility/ShaderConverter.cs index 6669c41..5a55bc6 100644 --- a/AssetStudio.Utility/ShaderConverter.cs +++ b/AssetStudio.Utility/ShaderConverter.cs @@ -20,7 +20,7 @@ namespace AssetStudio if (shader.m_SubProgramBlob != null) //5.3 - 5.4 { var decompressedBytes = new byte[shader.decompressedSize]; - var numWrite = LZ4.Decompress(shader.m_SubProgramBlob, decompressedBytes); + var numWrite = LZ4.Instance.Decompress(shader.m_SubProgramBlob, decompressedBytes); if (numWrite != shader.decompressedSize) { throw new IOException($"Lz4 decompression error, write {numWrite} bytes but expected {shader.decompressedSize} bytes"); @@ -59,7 +59,7 @@ namespace AssetStudio } else { - var numWrite = LZ4.Decompress(shader.compressedBlob.AsSpan().Slice((int)offset, (int)compressedLength), decompressedBytes.AsSpan().Slice(0, (int)decompressedLength)); + var numWrite = LZ4.Instance.Decompress(shader.compressedBlob.AsSpan().Slice((int)offset, (int)compressedLength), decompressedBytes.AsSpan().Slice(0, (int)decompressedLength)); if (numWrite != decompressedLength) { throw new IOException($"Lz4 decompression error, write {numWrite} bytes but expected {decompressedLength} bytes"); diff --git a/AssetStudio/BlbFile.cs b/AssetStudio/BlbFile.cs index c8e051a..7bcf708 100644 --- a/AssetStudio/BlbFile.cs +++ b/AssetStudio/BlbFile.cs @@ -160,7 +160,7 @@ namespace AssetStudio var uncompressedBytesSpan = uncompressedBytes.AsSpan(0, uncompressedSize); reader.Read(compressedBytesSpan); - var numWrite = LZ4.Decompress(compressedBytesSpan, uncompressedBytesSpan); + var numWrite = LZ4.Instance.Decompress(compressedBytesSpan, uncompressedBytesSpan); if (numWrite != uncompressedSize) { throw new IOException($"Lz4 decompression error, write {numWrite} bytes but expected {uncompressedSize} bytes"); diff --git a/AssetStudio/BundleFile.cs b/AssetStudio/BundleFile.cs index 5b205b1..86c306f 100644 --- a/AssetStudio/BundleFile.cs +++ b/AssetStudio/BundleFile.cs @@ -37,7 +37,9 @@ namespace AssetStudio Lzham, Lz4Mr0k, Lz4Inv = 5, - Zstd = 5 + Zstd = 5, + Lz4Lit4 = 4, + Lz4Lit5 = 5, } public class BundleFile @@ -450,7 +452,15 @@ namespace AssetStudio try { var uncompressedBytesSpan = uncompressedBytes.AsSpan(0, (int)uncompressedSize); - var numWrite = LZ4.Decompress(blocksInfoBytesSpan, uncompressedBytesSpan); + if (Game.Type.IsPerpetualNovelty()) + { + var key = blocksInfoBytesSpan[1]; + for (int j = 0; j < Math.Min(0x32, blocksInfoBytesSpan.Length); j++) + { + blocksInfoBytesSpan[j] ^= key; + } + } + var numWrite = LZ4.Instance.Decompress(blocksInfoBytesSpan, uncompressedBytesSpan); if (numWrite != uncompressedSize) { throw new IOException($"Lz4 decompression error, write {numWrite} bytes but expected {uncompressedSize} bytes"); @@ -584,7 +594,7 @@ namespace AssetStudio { OPFPUtils.Decrypt(compressedBytesSpan, reader.FullPath); } - var numWrite = LZ4.Decompress(compressedBytesSpan, uncompressedBytesSpan); + var numWrite = LZ4.Instance.Decompress(compressedBytesSpan, uncompressedBytesSpan); if (numWrite != uncompressedSize) { throw new IOException($"Lz4 decompression error, write {numWrite} bytes but expected {uncompressedSize} bytes"); @@ -615,9 +625,37 @@ namespace AssetStudio if (i == 0) { FairGuardUtils.Decrypt(compressedBytesSpan); - } - var numWrite = LZ4Inv.Decompress(compressedBytesSpan, uncompressedBytesSpan); + + var numWrite = LZ4Inv.Instance.Decompress(compressedBytesSpan, uncompressedBytesSpan); + if (numWrite != uncompressedSize) + { + throw new IOException($"Lz4 decompression error, write {numWrite} bytes but expected {uncompressedSize} bytes"); + } + blocksStream.Write(uncompressedBytesSpan); + } + finally + { + ArrayPool.Shared.Return(compressedBytes, true); + ArrayPool.Shared.Return(uncompressedBytes, true); + } + break; + } + case CompressionType.Lz4Lit4 or CompressionType.Lz4Lit5 when Game.Type.IsExAstris(): + { + var compressedSize = (int)blockInfo.compressedSize; + var uncompressedSize = (int)blockInfo.uncompressedSize; + + var compressedBytes = ArrayPool.Shared.Rent(compressedSize); + var uncompressedBytes = ArrayPool.Shared.Rent(uncompressedSize); + + var compressedBytesSpan = compressedBytes.AsSpan(0, compressedSize); + var uncompressedBytesSpan = uncompressedBytes.AsSpan(0, uncompressedSize); + + try + { + reader.Read(compressedBytesSpan); + var numWrite = LZ4Lit.Instance.Decompress(compressedBytesSpan, uncompressedBytesSpan); if (numWrite != uncompressedSize) { throw new IOException($"Lz4 decompression error, write {numWrite} bytes but expected {uncompressedSize} bytes"); diff --git a/AssetStudio/GameManager.cs b/AssetStudio/GameManager.cs index 48cf9ba..bf595be 100644 --- a/AssetStudio/GameManager.cs +++ b/AssetStudio/GameManager.cs @@ -48,6 +48,8 @@ namespace AssetStudio Games.Add(index++, new Game(GameType.PartyAnimals)); Games.Add(index++, new Game(GameType.LoveAndDeepspace)); Games.Add(index++, new Game(GameType.SchoolGirlStrikers)); + Games.Add(index++, new Game(GameType.ExAstris)); + Games.Add(index++, new Game(GameType.PerpetualNovelty)); } public static Game GetGame(GameType gameType) => GetGame((int)gameType); public static Game GetGame(int index) @@ -167,6 +169,8 @@ namespace AssetStudio PartyAnimals, LoveAndDeepspace, SchoolGirlStrikers, + ExAstris, + PerpetualNovelty, } public static class GameTypes @@ -191,6 +195,8 @@ namespace AssetStudio public static bool IsNetEase(this GameType type) => type == GameType.NetEase; public static bool IsArknightsEndfield(this GameType type) => type == GameType.ArknightsEndfield; public static bool IsLoveAndDeepspace(this GameType type) => type == GameType.LoveAndDeepspace; + public static bool IsExAstris(this GameType type) => type == GameType.ExAstris; + public static bool IsPerpetualNovelty(this GameType type) => type == GameType.PerpetualNovelty; public static bool IsGIGroup(this GameType type) => type switch { GameType.GI or GameType.GI_Pack or GameType.GI_CB1 or GameType.GI_CB2 or GameType.GI_CB3 or GameType.GI_CB3Pre => true, diff --git a/AssetStudio/LZ4/LZ4.cs b/AssetStudio/LZ4/LZ4.cs new file mode 100644 index 0000000..3ee6af7 --- /dev/null +++ b/AssetStudio/LZ4/LZ4.cs @@ -0,0 +1,70 @@ +using System; + +namespace AssetStudio; +public class LZ4 +{ + public static LZ4 Instance => new(); + public virtual int Decompress(ReadOnlySpan cmp, Span dec) + { + int cmpPos = 0; + int decPos = 0; + + do + { + var (encCount, litCount) = GetLiteralToken(cmp, ref cmpPos); + + //Copy literal chunk + litCount = GetLength(litCount, cmp, ref cmpPos); + + cmp.Slice(cmpPos, litCount).CopyTo(dec.Slice(decPos)); + + cmpPos += litCount; + decPos += litCount; + + if (cmpPos >= cmp.Length) + { + break; + } + + //Copy compressed chunk + int back = GetChunkEnd(cmp, ref cmpPos); + + encCount = GetLength(encCount, cmp, ref cmpPos) + 4; + + int encPos = decPos - back; + + if (encCount <= back) + { + dec.Slice(encPos, encCount).CopyTo(dec.Slice(decPos)); + + decPos += encCount; + } + else + { + while (encCount-- > 0) + { + dec[decPos++] = dec[encPos++]; + } + } + } while (cmpPos < cmp.Length && + decPos < dec.Length); + + return decPos; + } + protected virtual (int encCount, int litCount) GetLiteralToken(ReadOnlySpan cmp, ref int cmpPos) => ((cmp[cmpPos] >> 0) & 0xf, (cmp[cmpPos++] >> 4) & 0xf); + protected virtual int GetChunkEnd(ReadOnlySpan cmp, ref int cmpPos) => cmp[cmpPos++] << 0 | cmp[cmpPos++] << 8; + protected virtual int GetLength(int length, ReadOnlySpan cmp, ref int cmpPos) + { + byte sum; + + if (length == 0xf) + { + do + { + length += sum = cmp[cmpPos++]; + } while (sum == 0xff); + } + + return length; + } +} \ No newline at end of file diff --git a/AssetStudio/LZ4/LZ4Inv.cs b/AssetStudio/LZ4/LZ4Inv.cs new file mode 100644 index 0000000..042fea2 --- /dev/null +++ b/AssetStudio/LZ4/LZ4Inv.cs @@ -0,0 +1,9 @@ +using System; + +namespace AssetStudio; +public class LZ4Inv : LZ4 +{ + public new static LZ4Inv Instance => new(); + protected override (int encCount, int litCount) GetLiteralToken(ReadOnlySpan cmp, ref int cmpPos) => ((cmp[cmpPos] >> 4) & 0xf, (cmp[cmpPos++] >> 0) & 0xf); + protected override int GetChunkEnd(ReadOnlySpan cmp, ref int cmpPos) => cmp[cmpPos++] << 8 | cmp[cmpPos++] << 0; +} \ No newline at end of file diff --git a/AssetStudio/LZ4/LZ4Lit.cs b/AssetStudio/LZ4/LZ4Lit.cs new file mode 100644 index 0000000..a8a0aee --- /dev/null +++ b/AssetStudio/LZ4/LZ4Lit.cs @@ -0,0 +1,8 @@ +using System; + +namespace AssetStudio; +public class LZ4Lit : LZ4 +{ + public new static LZ4Lit Instance => new(); + protected override (int encCount, int litCount) GetLiteralToken(ReadOnlySpan cmp, ref int cmpPos) => ((cmp[cmpPos] >> 4) & 0xf, (cmp[cmpPos++] >> 0) & 0xf); +} \ No newline at end of file diff --git a/AssetStudio/LZ4/LZ4Utils.cs b/AssetStudio/LZ4/LZ4Utils.cs deleted file mode 100644 index 0f22898..0000000 --- a/AssetStudio/LZ4/LZ4Utils.cs +++ /dev/null @@ -1,143 +0,0 @@ -using System; - -namespace AssetStudio; -public static class LZ4 -{ - public static int Decompress(ReadOnlySpan cmp, Span dec) - { - int cmpPos = 0; - int decPos = 0; - - // ReSharper disable once VariableHidesOuterVariable - int GetLength(int length, ReadOnlySpan cmp) - { - byte sum; - - if (length == 0xf) - { - do - { - length += sum = cmp[cmpPos++]; - } while (sum == 0xff); - } - - return length; - } - - do - { - byte token = cmp[cmpPos++]; - - int encCount = (token >> 0) & 0xf; - int litCount = (token >> 4) & 0xf; - - //Copy literal chunk - litCount = GetLength(litCount, cmp); - - cmp.Slice(cmpPos, litCount).CopyTo(dec.Slice(decPos)); - - cmpPos += litCount; - decPos += litCount; - - if (cmpPos >= cmp.Length) - { - break; - } - - //Copy compressed chunk - int back = cmp[cmpPos++] << 0 | - cmp[cmpPos++] << 8; - - encCount = GetLength(encCount, cmp) + 4; - - int encPos = decPos - back; - - if (encCount <= back) - { - dec.Slice(encPos, encCount).CopyTo(dec.Slice(decPos)); - - decPos += encCount; - } - else - { - while (encCount-- > 0) - { - dec[decPos++] = dec[encPos++]; - } - } - } while (cmpPos < cmp.Length && - decPos < dec.Length); - - return decPos; - } -} -public static class LZ4Inv -{ - public static int Decompress(ReadOnlySpan cmp, Span dec) - { - int cmpPos = 0; - int decPos = 0; - - // ReSharper disable once VariableHidesOuterVariable - int GetLength(int length, ReadOnlySpan cmp) - { - byte sum; - - if (length == 0xf) - { - do - { - length += sum = cmp[cmpPos++]; - } while (sum == 0xff); - } - - return length; - } - - do - { - byte token = cmp[cmpPos++]; - - int encCount = (token >> 4) & 0xf; - int litCount = (token >> 0) & 0xf; - - //Copy literal chunk - litCount = GetLength(litCount, cmp); - - cmp.Slice(cmpPos, litCount).CopyTo(dec.Slice(decPos)); - - cmpPos += litCount; - decPos += litCount; - - if (cmpPos >= cmp.Length) - { - break; - } - - //Copy compressed chunk - int back = cmp[cmpPos++] << 8 | - cmp[cmpPos++] << 0; - - encCount = GetLength(encCount, cmp) + 4; - - int encPos = decPos - back; - - if (encCount <= back) - { - dec.Slice(encPos, encCount).CopyTo(dec.Slice(decPos)); - - decPos += encCount; - } - else - { - while (encCount-- > 0) - { - dec[decPos++] = dec[encPos++]; - } - } - } while (cmpPos < cmp.Length && - decPos < dec.Length); - - return decPos; - } -} diff --git a/AssetStudio/MhyFile.cs b/AssetStudio/MhyFile.cs index 6e0e367..a11a3c8 100644 --- a/AssetStudio/MhyFile.cs +++ b/AssetStudio/MhyFile.cs @@ -63,7 +63,7 @@ namespace AssetStudio try { - var numWrite = LZ4.Decompress(compressedBlocksInfo, uncompressedBlocksInfoSpan); + var numWrite = LZ4.Instance.Decompress(compressedBlocksInfo, uncompressedBlocksInfoSpan); if (numWrite != m_Header.uncompressedBlocksInfoSize) { throw new IOException($"Lz4 decompression error, write {numWrite} bytes but expected {m_Header.uncompressedBlocksInfoSize} bytes"); @@ -144,7 +144,7 @@ namespace AssetStudio DescrambleEntry(compressedBytesSpan); Logger.Verbose($"Descrambled block signature {Convert.ToHexString(compressedBytes, 0, 4)}"); - var numWrite = LZ4.Decompress(compressedBytesSpan[0xC..], uncompressedBytesSpan); + var numWrite = LZ4.Instance.Decompress(compressedBytesSpan[0xC..], uncompressedBytesSpan); if (numWrite != uncompressedSize) { throw new IOException($"Lz4 decompression error, write {numWrite} bytes but expected {uncompressedSize} bytes");