From d768e859168f6340985a0bd124aed38261536a57 Mon Sep 17 00:00:00 2001 From: Razmoth <12517189-Razmoth@users.noreply.gitlab.com> Date: Sun, 15 Jan 2023 17:11:32 +0400 Subject: [PATCH] New game entries. --- AssetStudio/BundleFile.cs | 14 ++++-- AssetStudio/Crypto/OPFPUtils.cs | 40 +++++++++++++++ AssetStudio/FileReader.cs | 6 +++ AssetStudio/GameManager.cs | 6 ++- AssetStudio/ImportHelper.cs | 86 +++++++++++++++++++++++++++++++-- 5 files changed, 141 insertions(+), 11 deletions(-) create mode 100644 AssetStudio/Crypto/OPFPUtils.cs diff --git a/AssetStudio/BundleFile.cs b/AssetStudio/BundleFile.cs index e86e235..a85713b 100644 --- a/AssetStudio/BundleFile.cs +++ b/AssetStudio/BundleFile.cs @@ -156,7 +156,7 @@ namespace AssetStudio return header; } - private void ReadHeaderAndBlocksInfo(EndianBinaryReader reader) + private void ReadHeaderAndBlocksInfo(FileReader reader) { if (m_Header.version >= 4) { @@ -208,7 +208,7 @@ namespace AssetStudio return blocksStream; } - private void ReadBlocksAndDirectory(EndianBinaryReader reader, Stream blocksStream) + private void ReadBlocksAndDirectory(FileReader reader, Stream blocksStream) { var isCompressed = m_Header.signature == "UnityWeb"; foreach (var blockInfo in m_BlocksInfo) @@ -278,7 +278,7 @@ namespace AssetStudio XORShift128.Init = false; } - private void ReadHeader(EndianBinaryReader reader) + private void ReadHeader(FileReader reader) { if (Game.Type.IsBH3() && XORShift128.Init) { @@ -309,7 +309,7 @@ namespace AssetStudio } } - private void ReadBlocksInfoAndDirectory(EndianBinaryReader reader) + private void ReadBlocksInfoAndDirectory(FileReader reader) { if (Game.Type.IsCNUnity()) { @@ -430,7 +430,7 @@ namespace AssetStudio } } - private void ReadBlocks(EndianBinaryReader reader, Stream blocksStream) + private void ReadBlocks(FileReader reader, Stream blocksStream) { for (int i = 0; i < m_BlocksInfo.Length; i++) { @@ -464,6 +464,10 @@ namespace AssetStudio { CNUnity.DecryptBlock(compressedBytesSpan, compressedSize, i); } + if (Game.Type.IsOPFP()) + { + OPFPUtils.Decrypt(compressedBytesSpan, reader.FullPath); + } var uncompressedSize = (int)blockInfo.uncompressedSize; var uncompressedBytes = BigArrayPool.Shared.Rent(uncompressedSize); var uncompressedBytesSpan = uncompressedBytes.AsSpan(0, uncompressedSize); diff --git a/AssetStudio/Crypto/OPFPUtils.cs b/AssetStudio/Crypto/OPFPUtils.cs new file mode 100644 index 0000000..0e245d8 --- /dev/null +++ b/AssetStudio/Crypto/OPFPUtils.cs @@ -0,0 +1,40 @@ +using System; + +namespace AssetStudio +{ + public static class OPFPUtils + { + public static readonly string[] EncrytpedFolders = { "UI/", "Atlas/", "UITexture/", "DynamicAtlas/" }; + + public static void Decrypt(Span data, string path) + { + if (IsEncryptionBundle(path, out var key)) + { + data[0] ^= key; + for (int i = 1; i < data.Length; i++) + { + data[i] ^= data[i - 1]; + } + } + } + private static bool IsEncryptionBundle(string path, out byte key) + { + path = path.Replace("\\", "/"); + foreach(var encryptedFolder in EncrytpedFolders) + { + var index = path.IndexOf(encryptedFolder, 0, path.Length, StringComparison.OrdinalIgnoreCase); + if (index != -1) + { + var assetPath = path[index..]; + if (assetPath.StartsWith(encryptedFolder, StringComparison.OrdinalIgnoreCase)) + { + key = (byte)assetPath.Length; + return true; + } + } + } + key = 0x00; + return false; + } + } +} diff --git a/AssetStudio/FileReader.cs b/AssetStudio/FileReader.cs index ceda873..9be4dcf 100644 --- a/AssetStudio/FileReader.cs +++ b/AssetStudio/FileReader.cs @@ -148,6 +148,12 @@ namespace AssetStudio case GameType.OPFP: reader = ParseOPFP(reader); break; + case GameType.AlchemyStars: + reader = ParseAlchemyStars(reader); + break; + case GameType.FantasyOfWind: + reader = DecryptFantasyOfWind(reader); + break; } } if (reader.FileType == FileType.BundleFile && game.Type.IsBlockFile()) diff --git a/AssetStudio/GameManager.cs b/AssetStudio/GameManager.cs index 2f958ba..f8cb44e 100644 --- a/AssetStudio/GameManager.cs +++ b/AssetStudio/GameManager.cs @@ -27,6 +27,8 @@ namespace AssetStudio Games.Add(index++, new Game(GameType.Naraka)); Games.Add(index++, new Game(GameType.EnsembleStars)); Games.Add(index++, new Game(GameType.OPFP)); + Games.Add(index++, new Game(GameType.AlchemyStars)); + Games.Add(index++, new Game(GameType.FantasyOfWind)); } public static Game GetGame(int index) { @@ -123,7 +125,9 @@ namespace AssetStudio Naraka, CNUnity, EnsembleStars, - OPFP + OPFP, + AlchemyStars, + FantasyOfWind } public static class GameTypes diff --git a/AssetStudio/ImportHelper.cs b/AssetStudio/ImportHelper.cs index 23282b4..44ec32e 100644 --- a/AssetStudio/ImportHelper.cs +++ b/AssetStudio/ImportHelper.cs @@ -7,6 +7,7 @@ using System.Diagnostics; using System.IO; using System.IO.Compression; using System.Linq; +using System.Text; using static AssetStudio.BundleFile; using static AssetStudio.Crypto; @@ -273,16 +274,91 @@ namespace AssetStudio public static FileReader ParseOPFP(FileReader reader) { - MemoryStream ms = new(); - - var data = reader.ReadBytes((int)reader.BaseStream.Length); + var stream = reader.BaseStream; + var data = reader.ReadBytes(0x1000); var idx = data.Search("UnityFS"); if (idx != -1) { - var count = data.Length - idx; - ms = new(data, idx, count); + stream = new BlockStream(stream, idx); } + return new FileReader(reader.FullPath, stream); + } + + public static FileReader ParseAlchemyStars(FileReader reader) + { + var stream = reader.BaseStream; + var data = reader.ReadBytes(0x1000); + var idx = data.Search("UnityFS"); + if (idx != -1) + { + var idx2 = data[(idx + 1)..].Search("UnityFS"); + if (idx2 != -1) + { + stream = new BlockStream(stream, idx + idx2 + 1); + } + else + { + stream = new BlockStream(stream, idx); + } + } + + return new FileReader(reader.FullPath, stream); + } + + public static FileReader DecryptFantasyOfWind(FileReader reader) + { + byte[] encryptKeyName = Encoding.UTF8.GetBytes("28856"); + const int MinLength = 0xC8; + const int KeyLength = 8; + const int EnLength = 0x32; + const int StartEnd = 0x14; + const int HeadLength = 5; + + var signature = reader.ReadStringToNull(HeadLength); + if (string.Compare(signature, "K9999") > 0 || reader.Length <= MinLength) + { + reader.Position = 0; + return reader; + } + + reader.Position = reader.Length + ~StartEnd; + var keyLength = reader.ReadByte(); + reader.Position = reader.Length - StartEnd - 2; + var enLength = reader.ReadByte(); + + var enKeyPos = (byte)((keyLength % KeyLength) + KeyLength); + var encryptedLength = (byte)((enLength % EnLength) + EnLength); + + reader.Position = reader.Length - StartEnd - enKeyPos; + var encryptKey = reader.ReadBytes(KeyLength); + + var subByte = (byte)(reader.Length - StartEnd - KeyLength - (keyLength % KeyLength)); + for (var i = 0; i < KeyLength; i++) + { + if (encryptKey[i] == 0) + { + encryptKey[i] = (byte)(subByte + i); + } + } + + var key = new byte[encryptKeyName.Length + KeyLength]; + encryptKeyName.CopyTo(key.AsMemory(0)); + encryptKey.CopyTo(key.AsMemory(encryptKeyName.Length)); + + reader.Position = HeadLength; + var data = reader.ReadBytes(encryptedLength); + for (int i = 0; i < encryptedLength; i++) + { + data[i] ^= key[i % key.Length]; + } + + MemoryStream ms = new(); + ms.Write(Encoding.UTF8.GetBytes("Unity")); + ms.Write(data); + reader.BaseStream.CopyTo(ms); + ms.Position = 0; + return new FileReader(reader.FullPath, ms); } }