From c20c07b5f283e620955f2cf0fa6af176e0f90fdd Mon Sep 17 00:00:00 2001 From: VaDiM Date: Thu, 24 Jul 2025 17:24:41 +0300 Subject: [PATCH] Update bundle reader - Replaced creation of a duplicated file/memory stream with OffsetStream. - Added separate processing of uncompressed bundles (including streamed bundles). They will be read directly from disk. - Added progress report on LZMA decompression process. --- AssetStudio/AssetsManager.cs | 14 +- .../7zip/Compress/LZMA/LzmaDecoder.cs | 5 + AssetStudio/BundleFile.cs | 238 +++++++++--------- AssetStudio/OffsetStream.cs | 12 +- AssetStudio/WebFile.cs | 9 +- AssetStudioCLI/Studio.cs | 27 +- AssetStudioGUI/Studio.cs | 29 ++- 7 files changed, 191 insertions(+), 143 deletions(-) diff --git a/AssetStudio/AssetsManager.cs b/AssetStudio/AssetsManager.cs index 691490e..f7c729a 100644 --- a/AssetStudio/AssetsManager.cs +++ b/AssetStudio/AssetsManager.cs @@ -321,16 +321,18 @@ namespace AssetStudio Logger.Debug($"Bundle offset: {reader.Position}"); var bundleStream = new OffsetStream(reader); var bundleReader = new FileReader(reader.FullPath, bundleStream); + var isLoaded = false; try { var bundleFile = new BundleFile(bundleReader, CustomBlockInfoCompression, CustomBlockCompression, specifiedUnityVersion); - var isLoaded = LoadBundleFiles(bundleReader, bundleFile, originalPath); + isLoaded = LoadBundleFiles(bundleReader, bundleFile, originalPath); if (!isLoaded) return false; - while (bundleFile.IsMultiBundle && isLoaded) + while (bundleFile.IsDataAfterBundle && isLoaded) { + isLoaded = false; bundleStream.Offset = reader.Position; bundleReader = new FileReader($"{reader.FullPath}_0x{bundleStream.Offset:X}", bundleStream); if (bundleReader.FileType != FileType.BundleFile) @@ -345,7 +347,7 @@ namespace AssetStudio bundleReader.FileName = $"{reader.FileName}_0x{bundleStream.Offset:X}"; } Logger.Info($"[MultiBundle] Loading \"{reader.FileName}\" from offset: 0x{bundleStream.Offset:X}"); - bundleFile = new BundleFile(bundleReader, CustomBlockInfoCompression, CustomBlockCompression, specifiedUnityVersion); + bundleFile = new BundleFile(bundleReader, CustomBlockInfoCompression, CustomBlockCompression, specifiedUnityVersion, isMultiBundle: true); isLoaded = LoadBundleFiles(bundleReader, bundleFile, originalPath ?? reader.FullPath); } return isLoaded; @@ -367,7 +369,8 @@ namespace AssetStudio } finally { - bundleReader.Dispose(); + if (!isLoaded) + bundleReader.Dispose(); } } @@ -375,6 +378,9 @@ namespace AssetStudio { foreach (var file in bundleFile.fileList) { + if (file.stream == null) + continue; + file.stream.Position = 0; //go to file offset var dummyPath = Path.Combine(Path.GetDirectoryName(reader.FullPath), file.fileName); var subReader = new FileReader(dummyPath, file.stream); if (subReader.FileType == FileType.AssetsFile) diff --git a/AssetStudio/BundleCompression/SevenZipLzma/7zip/Compress/LZMA/LzmaDecoder.cs b/AssetStudio/BundleCompression/SevenZipLzma/7zip/Compress/LZMA/LzmaDecoder.cs index a9be39f..504db61 100644 --- a/AssetStudio/BundleCompression/SevenZipLzma/7zip/Compress/LZMA/LzmaDecoder.cs +++ b/AssetStudio/BundleCompression/SevenZipLzma/7zip/Compress/LZMA/LzmaDecoder.cs @@ -1,6 +1,7 @@ // LzmaDecoder.cs using System; +using AssetStudio; namespace SevenZip.Compression.LZMA { @@ -247,6 +248,8 @@ namespace SevenZip.Compression.LZMA m_OutWindow.PutByte(b); nowPos64++; } + + Progress.Reset(); while (nowPos64 < outSize64) { // UInt64 next = Math.Min(nowPos64 + (1 << 18), outSize64); @@ -338,6 +341,8 @@ namespace SevenZip.Compression.LZMA } m_OutWindow.CopyBlock(rep0, len); nowPos64 += len; + + Progress.Report((int)(nowPos64 * 100f / outSize64), 100); } } } diff --git a/AssetStudio/BundleFile.cs b/AssetStudio/BundleFile.cs index 484e65a..d1462b5 100644 --- a/AssetStudio/BundleFile.cs +++ b/AssetStudio/BundleFile.cs @@ -1,6 +1,7 @@ using System; using System.IO; using System.Linq; +using System.Collections.Generic; namespace AssetStudio { @@ -42,7 +43,7 @@ namespace AssetStudio public class BundleFile { - public readonly bool IsMultiBundle; + public readonly bool IsDataAfterBundle; public class Header { @@ -75,10 +76,11 @@ namespace AssetStudio private StorageBlock[] m_BlocksInfo; private Node[] m_DirectoryInfo; - public StreamFile[] fileList; + public List fileList; - public BundleFile(FileReader reader, CompressionType customBlockInfoCompression, CompressionType customBlockCompression, UnityVersion specUnityVer = null) + public BundleFile(FileReader reader, CompressionType customBlockInfoCompression, CompressionType customBlockCompression, UnityVersion specUnityVer = null, bool isMultiBundle = false) { + Stream blocksStream; m_Header = new Header(); m_Header.signature = reader.ReadStringToNull(); m_Header.version = reader.ReadUInt32(); @@ -98,11 +100,11 @@ namespace AssetStudio goto case "UnityFS"; } ReadHeaderAndBlocksInfo(reader); - using (var blocksStream = CreateBlocksStream(reader.FullPath)) + using (reader) { - ReadBlocksAndDirectory(reader, blocksStream); - ReadFiles(blocksStream, reader.FullPath); + blocksStream = ReadBlocksAndDirectory(reader); } + ReadFiles(blocksStream); break; case "UnityFS": ReadHeader(reader); @@ -115,7 +117,7 @@ namespace AssetStudio } else if (streamSize - bundleSize > 200) { - IsMultiBundle = true; + IsDataAfterBundle = true; } var unityVer = m_Header.unityRevision; @@ -133,11 +135,19 @@ namespace AssetStudio UnityCnCheck(reader, customBlockInfoCompression, unityVer); ReadBlocksInfoAndDirectory(reader, customBlockInfoCompression, unityVer); - using (var blocksStream = CreateBlocksStream(reader.FullPath)) + + if (!isMultiBundle && IsUncompressedBundle) { - ReadBlocks(reader, customBlockCompression, blocksStream); - ReadFiles(blocksStream, reader.FullPath); + ReadFiles(reader.BaseStream, reader.Position); + break; } + + blocksStream = ReadBlocks(reader, customBlockCompression); + ReadFiles(blocksStream); + + if (!IsDataAfterBundle) + reader.Close(); + break; } } @@ -156,7 +166,7 @@ namespace AssetStudio m_BlocksInfo = new StorageBlock[1]; for (int i = 0; i < levelCount; i++) { - var storageBlock = new StorageBlock() + var storageBlock = new StorageBlock { compressedSize = reader.ReadUInt32(), uncompressedSize = reader.ReadUInt32(), @@ -179,23 +189,15 @@ namespace AssetStudio private Stream CreateBlocksStream(string path) { - Stream blocksStream; var uncompressedSizeSum = m_BlocksInfo.Sum(x => x.uncompressedSize); - if (uncompressedSizeSum >= int.MaxValue) - { - /*var memoryMappedFile = MemoryMappedFile.CreateNew(null, uncompressedSizeSum); - assetsDataStream = memoryMappedFile.CreateViewStream();*/ - blocksStream = new FileStream(path + ".temp", FileMode.Create, FileAccess.ReadWrite, FileShare.None, 4096, FileOptions.DeleteOnClose); - } - else - { - blocksStream = new MemoryStream((int)uncompressedSizeSum); - } - return blocksStream; + return uncompressedSizeSum >= int.MaxValue + ? new FileStream(path + ".temp", FileMode.Create, FileAccess.ReadWrite, FileShare.None, 4096, FileOptions.DeleteOnClose) + : (Stream) new MemoryStream((int)uncompressedSizeSum); } - private void ReadBlocksAndDirectory(FileReader reader, Stream blocksStream) + private Stream ReadBlocksAndDirectory(FileReader reader) { + var blocksStream = CreateBlocksStream(reader.FullPath); var isCompressed = m_Header.signature == "UnityWeb"; foreach (var blockInfo in m_BlocksInfo) { @@ -213,10 +215,11 @@ namespace AssetStudio blocksStream.Write(uncompressedBytes, 0, uncompressedBytes.Length); } blocksStream.Position = 0; + var blocksReader = new EndianBinaryReader(blocksStream); var nodesCount = blocksReader.ReadInt32(); m_DirectoryInfo = new Node[nodesCount]; - for (int i = 0; i < nodesCount; i++) + for (var i = 0; i < nodesCount; i++) { m_DirectoryInfo[i] = new Node { @@ -225,33 +228,27 @@ namespace AssetStudio size = blocksReader.ReadUInt32() }; } + + return blocksStream; } - public void ReadFiles(Stream blocksStream, string path) + private void ReadFiles(Stream inputStream, long blocksOffset = 0) { - fileList = new StreamFile[m_DirectoryInfo.Length]; - for (int i = 0; i < m_DirectoryInfo.Length; i++) + fileList = new List(m_DirectoryInfo.Length); + foreach (var node in m_DirectoryInfo) { - var node = m_DirectoryInfo[i]; var file = new StreamFile(); - fileList[i] = file; + fileList.Add(file); file.path = node.path; file.fileName = Path.GetFileName(node.path); - if (node.size >= int.MaxValue) + try { - /*var memoryMappedFile = MemoryMappedFile.CreateNew(null, entryinfo_size); - file.stream = memoryMappedFile.CreateViewStream();*/ - var extractPath = path + "_unpacked" + Path.DirectorySeparatorChar; - Directory.CreateDirectory(extractPath); - file.stream = new FileStream(extractPath + file.fileName, FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite); + file.stream = new OffsetStream(inputStream, node.offset + blocksOffset, node.size); } - else + catch (IOException e) { - file.stream = new MemoryStream((int)node.size); + Logger.Warning($"Failed to access {file.fileName} file.\n{e}"); } - blocksStream.Position = node.offset; - blocksStream.CopyTo(file.stream, node.size); - file.stream.Position = 0; } } @@ -365,7 +362,7 @@ namespace AssetStudio if (numWrite != uncompressedSize) { var msg = $"{compressionType} blockInfo decompression error. {errorMsg}\nWrite {numWrite} bytes but expected {uncompressedSize} bytes."; - var exMsg = compressionType > CompressionType.Lz4HC + var exMsg = compressionType > CompressionType.Lz4HC || customBlockInfoCompression != CompressionType.Auto ? "Wrong compression type or blockInfo data might be encrypted." : "BlockInfo data might be encrypted."; throw new IOException($"{msg}\n{exMsg}\n"); @@ -376,7 +373,7 @@ namespace AssetStudio var uncompressedDataHash = blocksInfoReader.ReadBytes(16); var blocksInfoCount = blocksInfoReader.ReadInt32(); m_BlocksInfo = new StorageBlock[blocksInfoCount]; - for (int i = 0; i < blocksInfoCount; i++) + for (var i = 0; i < blocksInfoCount; i++) { m_BlocksInfo[i] = new StorageBlock { @@ -388,7 +385,7 @@ namespace AssetStudio var nodesCount = blocksInfoReader.ReadInt32(); m_DirectoryInfo = new Node[nodesCount]; - for (int i = 0; i < nodesCount; i++) + for (var i = 0; i < nodesCount; i++) { m_DirectoryInfo[i] = new Node { @@ -405,90 +402,105 @@ namespace AssetStudio } } - private void ReadBlocks(FileReader reader, CompressionType customBlockCompression, Stream blocksStream) + private Stream ReadBlocks(FileReader reader, CompressionType customBlockCompression) { - Logger.Debug($"Block compression: {(CompressionType)m_BlocksInfo.Max(x => x.flags)}"); + var blocksStream = CreateBlocksStream(reader.FullPath); + var blocksCompression = m_BlocksInfo.Max(x => (CompressionType)(x.flags & StorageBlockFlags.CompressionTypeMask)); + Logger.Debug($"BlockData compression: {blocksCompression}"); + Logger.Debug($"BlockData count: {m_BlocksInfo.Length}"); - var showCustomTypeWarning = true; - foreach (var blockInfo in m_BlocksInfo) + if (customBlockCompression == CompressionType.Auto) { - var compressionType = (CompressionType)(blockInfo.flags & StorageBlockFlags.CompressionTypeMask); - if (customBlockCompression == CompressionType.Auto) + if (blocksCompression > CompressionType.Lzham && Enum.IsDefined(typeof(CompressionType), blocksCompression)) { - if (showCustomTypeWarning && compressionType > CompressionType.Lzham && Enum.IsDefined(typeof(CompressionType), compressionType)) - { - Logger.Warning($"Non-standard block compression type: {(int)compressionType}. Trying to decompress as {compressionType} archive.."); - showCustomTypeWarning = false; - } - } - else if (compressionType != CompressionType.None) - { - compressionType = customBlockCompression; - if (showCustomTypeWarning) - { - Logger.Info($"Custom block compression type: {customBlockCompression}"); - showCustomTypeWarning = false; - } + Logger.Warning($"Non-standard block compression type: {(int)blocksCompression}. Trying to decompress as {blocksCompression} archive.."); } + } + else + { + Logger.Info($"Custom block compression type: {customBlockCompression}"); + blocksCompression = customBlockCompression; + } - long numWrite; - var errorMsg = string.Empty; - switch (compressionType) + byte[] sharedCompressedBuff = null; + byte[] sharedUncompressedBuff = null; + if (blocksCompression != CompressionType.Lzma && blocksCompression != CompressionType.Lzham) + { + var blockSize = (int)m_BlocksInfo.Max(x => x.uncompressedSize); + Logger.Debug($"BlockSize: {blockSize}"); + + sharedCompressedBuff = BigArrayPool.Shared.Rent(blockSize); + sharedUncompressedBuff = BigArrayPool.Shared.Rent(blockSize); + } + + try + { + foreach (var blockInfo in m_BlocksInfo) { - case CompressionType.None: + var compressionType = (CompressionType)(blockInfo.flags & StorageBlockFlags.CompressionTypeMask); + + if (customBlockCompression != CompressionType.Auto && compressionType > 0) { - reader.BaseStream.CopyTo(blocksStream, blockInfo.compressedSize); - numWrite = blockInfo.compressedSize; - break; + compressionType = customBlockCompression; } - case CompressionType.Lzma: + + long numWrite; + var errorMsg = string.Empty; + switch (compressionType) { - numWrite = BundleDecompressionHelper.DecompressLzmaStream(reader.BaseStream, blocksStream, blockInfo.compressedSize, blockInfo.uncompressedSize, ref errorMsg); - break; - } - case CompressionType.Lz4: - case CompressionType.Lz4HC: - case CompressionType.Zstd: - case CompressionType.Oodle: - { - var compressedSize = (int)blockInfo.compressedSize; - var uncompressedSize = (int)blockInfo.uncompressedSize; - - var compressedBytes = BigArrayPool.Shared.Rent(compressedSize); - var uncompressedBytes = BigArrayPool.Shared.Rent(uncompressedSize); - try - { - _ = reader.Read(compressedBytes, 0, compressedSize); - var compressedSpan = new ReadOnlySpan(compressedBytes, 0, compressedSize); - var uncompressedSpan = new Span(uncompressedBytes, 0, uncompressedSize); + case CompressionType.None: + reader.BaseStream.CopyTo(blocksStream, blockInfo.compressedSize); + numWrite = blockInfo.compressedSize; + break; + case CompressionType.Lzma: + Logger.Info("Decompressing LZMA stream..."); + numWrite = BundleDecompressionHelper.DecompressLzmaStream(reader.BaseStream, blocksStream, blockInfo.compressedSize, blockInfo.uncompressedSize, ref errorMsg); + break; + case CompressionType.Lz4: + case CompressionType.Lz4HC: + case CompressionType.Zstd: + case CompressionType.Oodle: + var compressedSize = (int)blockInfo.compressedSize; + var uncompressedSize = (int)blockInfo.uncompressedSize; + + sharedCompressedBuff.AsSpan().Clear(); + sharedUncompressedBuff.AsSpan().Clear(); + + _ = reader.Read(sharedCompressedBuff, 0, compressedSize); + var compressedSpan = new ReadOnlySpan(sharedCompressedBuff, 0, compressedSize); + var uncompressedSpan = new Span(sharedUncompressedBuff, 0, uncompressedSize); numWrite = BundleDecompressionHelper.DecompressBlock(compressionType, compressedSpan, uncompressedSpan, ref errorMsg); if (numWrite == uncompressedSize) - blocksStream.Write(uncompressedBytes, 0, uncompressedSize); - } - finally - { - BigArrayPool.Shared.Return(compressedBytes, clearArray: true); - BigArrayPool.Shared.Return(uncompressedBytes, clearArray: true); - } - break; - } - case CompressionType.Lzham: - throw new IOException($"Unsupported block compression type: {compressionType}.\n"); - default: - throw new IOException($"Unknown block compression type: {compressionType}.\nYou may try to specify the compression type manually.\n"); - } + blocksStream.Write(sharedUncompressedBuff, 0, uncompressedSize); - if (numWrite != blockInfo.uncompressedSize) - { - var msg = $"{compressionType} block decompression error. {errorMsg}\nWrite {numWrite} bytes but expected {blockInfo.uncompressedSize} bytes."; - var exMsg = compressionType > CompressionType.Lz4HC - ? "Wrong compression type or block data might be encrypted." - : "Block data might be encrypted."; - throw new IOException($"{msg}\n{exMsg}\n"); + break; + case CompressionType.Lzham: + throw new IOException($"Unsupported block compression type: {compressionType}.\n"); + default: + throw new IOException($"Unknown block compression type: {compressionType}.\nYou may try to specify the compression type manually.\n"); + } + + if (numWrite != blockInfo.uncompressedSize) + { + var msg = $"{compressionType} block decompression error. {errorMsg}\nWrite {numWrite} bytes but expected {blockInfo.uncompressedSize} bytes."; + var exMsg = compressionType > CompressionType.Lz4HC || customBlockCompression != CompressionType.Auto + ? "Wrong compression type or block data might be encrypted." + : "Block data might be encrypted."; + throw new IOException($"{msg}\n{exMsg}\n"); + } } } - blocksStream.Position = 0; + finally + { + if (sharedCompressedBuff != null) + BigArrayPool.Shared.Return(sharedCompressedBuff, clearArray: true); + + if (sharedUncompressedBuff != null) + BigArrayPool.Shared.Return(sharedUncompressedBuff, clearArray: true); + } + + return blocksStream; } private void UnityCnCheck(FileReader reader, CompressionType customBlockInfoCompression, UnityVersion unityVer) @@ -525,5 +537,7 @@ namespace AssetStudio } throw new NotSupportedException("Unsupported bundle file. UnityCN encryption was detected."); } + + private bool IsUncompressedBundle => m_BlocksInfo.All(x => (CompressionType)(x.flags & StorageBlockFlags.CompressionTypeMask) == CompressionType.None); } } diff --git a/AssetStudio/OffsetStream.cs b/AssetStudio/OffsetStream.cs index a0f768c..3a89d94 100644 --- a/AssetStudio/OffsetStream.cs +++ b/AssetStudio/OffsetStream.cs @@ -5,12 +5,15 @@ namespace AssetStudio public class OffsetStream : Stream { private readonly Stream _baseStream; + private readonly long _length; private long _offset; public override bool CanRead => _baseStream.CanRead; public override bool CanSeek => _baseStream.CanSeek; public override bool CanWrite => false; - public override long Length => _baseStream.Length - _offset; + public override long Length => _length > 0 + ? _length + : _baseStream.Length - _offset; public override long Position { @@ -40,6 +43,13 @@ namespace AssetStudio Offset = reader.Position; } + public OffsetStream(Stream stream, long offset, long length) + { + _baseStream = stream; + _length = length; + Offset = offset; + } + public override void Flush() { } public override int Read(byte[] buffer, int offset, int count) diff --git a/AssetStudio/WebFile.cs b/AssetStudio/WebFile.cs index ca61f85..2f901a5 100644 --- a/AssetStudio/WebFile.cs +++ b/AssetStudio/WebFile.cs @@ -6,7 +6,7 @@ namespace AssetStudio { public class WebFile { - public StreamFile[] fileList; + public List fileList; private class WebData { @@ -30,16 +30,15 @@ namespace AssetStudio data.path = Encoding.UTF8.GetString(reader.ReadBytes(pathLength)); dataList.Add(data); } - fileList = new StreamFile[dataList.Count]; - for (int i = 0; i < dataList.Count; i++) + fileList = new List(dataList.Count); + foreach (var data in dataList) { - var data = dataList[i]; var file = new StreamFile(); file.path = data.path; file.fileName = Path.GetFileName(data.path); reader.BaseStream.Position = data.dataOffset; file.stream = new MemoryStream(reader.ReadBytes(data.dataLength)); - fileList[i] = file; + fileList.Add(file); } } } diff --git a/AssetStudioCLI/Studio.cs b/AssetStudioCLI/Studio.cs index 2ca9c27..8c9013f 100644 --- a/AssetStudioCLI/Studio.cs +++ b/AssetStudioCLI/Studio.cs @@ -96,11 +96,11 @@ namespace AssetStudioCLI var bundleReader = new FileReader(reader.FullPath, bundleStream); var bundleFile = new BundleFile(bundleReader, assetsManager.CustomBlockInfoCompression, assetsManager.CustomBlockCompression, assetsManager.SpecifyUnityVersion); var extractPath = Path.Combine(savePath, reader.FileName + "_unpacked"); - if (bundleFile.fileList.Length > 0) + if (bundleFile.fileList.Count > 0) { count += ExtractStreamFile(extractPath, bundleFile.fileList); } - while (bundleFile.IsMultiBundle) + while (bundleFile.IsDataAfterBundle) { bundleStream.Offset = reader.Position; bundleReader = new FileReader($"{reader.FullPath}_0x{bundleStream.Offset:X}", bundleStream); @@ -113,8 +113,8 @@ namespace AssetStudioCLI bundleReader.FileName = $"{reader.FileName}_0x{bundleStream.Offset:X}"; } Logger.Info($"[MultiBundle] Decompressing \"{reader.FileName}\" from offset: 0x{bundleStream.Offset:X}.."); - bundleFile = new BundleFile(bundleReader, assetsManager.CustomBlockInfoCompression, assetsManager.CustomBlockCompression, assetsManager.SpecifyUnityVersion); - if (bundleFile.fileList.Length > 0) + bundleFile = new BundleFile(bundleReader, assetsManager.CustomBlockInfoCompression, assetsManager.CustomBlockCompression, assetsManager.SpecifyUnityVersion, isMultiBundle: true); + if (bundleFile.fileList.Count > 0) { count += ExtractStreamFile(extractPath, bundleFile.fileList); } @@ -128,19 +128,21 @@ namespace AssetStudioCLI Logger.Info($"Decompressing {reader.FileName} ..."); var webFile = new WebFile(reader); reader.Dispose(); - if (webFile.fileList.Length > 0) + if (webFile.fileList.Count > 0) { var extractPath = Path.Combine(savePath, reader.FileName + "_unpacked"); - return ExtractStreamFile(extractPath, webFile.fileList); + return ExtractStreamFile(extractPath, webFile.fileList, isOffsetStream: false); } return 0; } - private static int ExtractStreamFile(string extractPath, StreamFile[] fileList) + private static int ExtractStreamFile(string extractPath, List fileList, bool isOffsetStream = true) { var extractedCount = 0; foreach (var file in fileList) { + if (file.stream == null) + continue; var filePath = Path.Combine(extractPath, file.path); var fileDirectory = Path.GetDirectoryName(filePath); if (!Directory.Exists(fileDirectory)) @@ -151,11 +153,16 @@ namespace AssetStudioCLI { using (var fileStream = File.Create(filePath)) { - file.stream.CopyTo(fileStream); + file.stream.Position = 0; + file.stream.CopyTo(fileStream, file.stream.Length); } - extractedCount += 1; + extractedCount++; } - file.stream.Dispose(); + if (!isOffsetStream) file.stream.Dispose(); + } + if (isOffsetStream && fileList.Count > 0) + { + fileList[0].stream?.Dispose(); } return extractedCount; } diff --git a/AssetStudioGUI/Studio.cs b/AssetStudioGUI/Studio.cs index 8d834de..abbf44d 100644 --- a/AssetStudioGUI/Studio.cs +++ b/AssetStudioGUI/Studio.cs @@ -143,11 +143,11 @@ namespace AssetStudioGUI var bundleReader = new FileReader(reader.FullPath, bundleStream); var bundleFile = new BundleFile(bundleReader, assetsManager.CustomBlockInfoCompression, assetsManager.CustomBlockCompression, assetsManager.SpecifyUnityVersion); var extractPath = Path.Combine(savePath, reader.FileName + "_unpacked"); - if (bundleFile.fileList.Length > 0) + if (bundleFile.fileList.Count > 0) { count += ExtractStreamFile(extractPath, bundleFile.fileList); } - while (bundleFile.IsMultiBundle) + while (bundleFile.IsDataAfterBundle) { bundleStream.Offset = reader.Position; bundleReader = new FileReader($"{reader.FullPath}_0x{bundleStream.Offset:X}", bundleStream); @@ -160,8 +160,8 @@ namespace AssetStudioGUI bundleReader.FileName = $"{reader.FileName}_0x{bundleStream.Offset:X}"; } Logger.Info($"[MultiBundle] Decompressing \"{reader.FileName}\" from offset: 0x{bundleStream.Offset:X}.."); - bundleFile = new BundleFile(bundleReader, assetsManager.CustomBlockInfoCompression, assetsManager.CustomBlockCompression, assetsManager.SpecifyUnityVersion); - if (bundleFile.fileList.Length > 0) + bundleFile = new BundleFile(bundleReader, assetsManager.CustomBlockInfoCompression, assetsManager.CustomBlockCompression, assetsManager.SpecifyUnityVersion, isMultiBundle: true); + if (bundleFile.fileList.Count > 0) { count += ExtractStreamFile(extractPath, bundleFile.fileList); } @@ -175,19 +175,21 @@ namespace AssetStudioGUI Logger.Info($"Decompressing {reader.FileName} ..."); var webFile = new WebFile(reader); reader.Dispose(); - if (webFile.fileList.Length > 0) + if (webFile.fileList.Count > 0) { var extractPath = Path.Combine(savePath, reader.FileName + "_unpacked"); - return ExtractStreamFile(extractPath, webFile.fileList); + return ExtractStreamFile(extractPath, webFile.fileList, isOffsetStream: false); } return 0; } - private static int ExtractStreamFile(string extractPath, StreamFile[] fileList) + private static int ExtractStreamFile(string extractPath, List fileList, bool isOffsetStream = true) { - int extractedCount = 0; + var extractedCount = 0; foreach (var file in fileList) { + if (file.stream == null) + continue; var filePath = Path.Combine(extractPath, file.path); var fileDirectory = Path.GetDirectoryName(filePath); if (!Directory.Exists(fileDirectory)) @@ -198,11 +200,16 @@ namespace AssetStudioGUI { using (var fileStream = File.Create(filePath)) { - file.stream.CopyTo(fileStream); + file.stream.Position = 0; + file.stream.CopyTo(fileStream, file.stream.Length); } - extractedCount += 1; + extractedCount++; } - file.stream.Dispose(); + if (!isOffsetStream) file.stream.Dispose(); + } + if (isOffsetStream && fileList.Count > 0) + { + fileList[0].stream?.Dispose(); } return extractedCount; }