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.
This commit is contained in:
VaDiM
2025-07-24 17:24:41 +03:00
parent efca2a7557
commit c20c07b5f2
7 changed files with 191 additions and 143 deletions

View File

@ -321,16 +321,18 @@ namespace AssetStudio
Logger.Debug($"Bundle offset: {reader.Position}"); Logger.Debug($"Bundle offset: {reader.Position}");
var bundleStream = new OffsetStream(reader); var bundleStream = new OffsetStream(reader);
var bundleReader = new FileReader(reader.FullPath, bundleStream); var bundleReader = new FileReader(reader.FullPath, bundleStream);
var isLoaded = false;
try try
{ {
var bundleFile = new BundleFile(bundleReader, CustomBlockInfoCompression, CustomBlockCompression, specifiedUnityVersion); var bundleFile = new BundleFile(bundleReader, CustomBlockInfoCompression, CustomBlockCompression, specifiedUnityVersion);
var isLoaded = LoadBundleFiles(bundleReader, bundleFile, originalPath); isLoaded = LoadBundleFiles(bundleReader, bundleFile, originalPath);
if (!isLoaded) if (!isLoaded)
return false; return false;
while (bundleFile.IsMultiBundle && isLoaded) while (bundleFile.IsDataAfterBundle && isLoaded)
{ {
isLoaded = false;
bundleStream.Offset = reader.Position; bundleStream.Offset = reader.Position;
bundleReader = new FileReader($"{reader.FullPath}_0x{bundleStream.Offset:X}", bundleStream); bundleReader = new FileReader($"{reader.FullPath}_0x{bundleStream.Offset:X}", bundleStream);
if (bundleReader.FileType != FileType.BundleFile) if (bundleReader.FileType != FileType.BundleFile)
@ -345,7 +347,7 @@ namespace AssetStudio
bundleReader.FileName = $"{reader.FileName}_0x{bundleStream.Offset:X}"; bundleReader.FileName = $"{reader.FileName}_0x{bundleStream.Offset:X}";
} }
Logger.Info($"[MultiBundle] Loading \"{reader.FileName}\" from offset: 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); isLoaded = LoadBundleFiles(bundleReader, bundleFile, originalPath ?? reader.FullPath);
} }
return isLoaded; return isLoaded;
@ -367,7 +369,8 @@ namespace AssetStudio
} }
finally finally
{ {
bundleReader.Dispose(); if (!isLoaded)
bundleReader.Dispose();
} }
} }
@ -375,6 +378,9 @@ namespace AssetStudio
{ {
foreach (var file in bundleFile.fileList) 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 dummyPath = Path.Combine(Path.GetDirectoryName(reader.FullPath), file.fileName);
var subReader = new FileReader(dummyPath, file.stream); var subReader = new FileReader(dummyPath, file.stream);
if (subReader.FileType == FileType.AssetsFile) if (subReader.FileType == FileType.AssetsFile)

View File

@ -1,6 +1,7 @@
// LzmaDecoder.cs // LzmaDecoder.cs
using System; using System;
using AssetStudio;
namespace SevenZip.Compression.LZMA namespace SevenZip.Compression.LZMA
{ {
@ -247,6 +248,8 @@ namespace SevenZip.Compression.LZMA
m_OutWindow.PutByte(b); m_OutWindow.PutByte(b);
nowPos64++; nowPos64++;
} }
Progress.Reset();
while (nowPos64 < outSize64) while (nowPos64 < outSize64)
{ {
// UInt64 next = Math.Min(nowPos64 + (1 << 18), outSize64); // UInt64 next = Math.Min(nowPos64 + (1 << 18), outSize64);
@ -338,6 +341,8 @@ namespace SevenZip.Compression.LZMA
} }
m_OutWindow.CopyBlock(rep0, len); m_OutWindow.CopyBlock(rep0, len);
nowPos64 += len; nowPos64 += len;
Progress.Report((int)(nowPos64 * 100f / outSize64), 100);
} }
} }
} }

View File

@ -1,6 +1,7 @@
using System; using System;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Collections.Generic;
namespace AssetStudio namespace AssetStudio
{ {
@ -42,7 +43,7 @@ namespace AssetStudio
public class BundleFile public class BundleFile
{ {
public readonly bool IsMultiBundle; public readonly bool IsDataAfterBundle;
public class Header public class Header
{ {
@ -75,10 +76,11 @@ namespace AssetStudio
private StorageBlock[] m_BlocksInfo; private StorageBlock[] m_BlocksInfo;
private Node[] m_DirectoryInfo; private Node[] m_DirectoryInfo;
public StreamFile[] fileList; public List<StreamFile> 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 = new Header();
m_Header.signature = reader.ReadStringToNull(); m_Header.signature = reader.ReadStringToNull();
m_Header.version = reader.ReadUInt32(); m_Header.version = reader.ReadUInt32();
@ -98,11 +100,11 @@ namespace AssetStudio
goto case "UnityFS"; goto case "UnityFS";
} }
ReadHeaderAndBlocksInfo(reader); ReadHeaderAndBlocksInfo(reader);
using (var blocksStream = CreateBlocksStream(reader.FullPath)) using (reader)
{ {
ReadBlocksAndDirectory(reader, blocksStream); blocksStream = ReadBlocksAndDirectory(reader);
ReadFiles(blocksStream, reader.FullPath);
} }
ReadFiles(blocksStream);
break; break;
case "UnityFS": case "UnityFS":
ReadHeader(reader); ReadHeader(reader);
@ -115,7 +117,7 @@ namespace AssetStudio
} }
else if (streamSize - bundleSize > 200) else if (streamSize - bundleSize > 200)
{ {
IsMultiBundle = true; IsDataAfterBundle = true;
} }
var unityVer = m_Header.unityRevision; var unityVer = m_Header.unityRevision;
@ -133,11 +135,19 @@ namespace AssetStudio
UnityCnCheck(reader, customBlockInfoCompression, unityVer); UnityCnCheck(reader, customBlockInfoCompression, unityVer);
ReadBlocksInfoAndDirectory(reader, customBlockInfoCompression, unityVer); ReadBlocksInfoAndDirectory(reader, customBlockInfoCompression, unityVer);
using (var blocksStream = CreateBlocksStream(reader.FullPath))
if (!isMultiBundle && IsUncompressedBundle)
{ {
ReadBlocks(reader, customBlockCompression, blocksStream); ReadFiles(reader.BaseStream, reader.Position);
ReadFiles(blocksStream, reader.FullPath); break;
} }
blocksStream = ReadBlocks(reader, customBlockCompression);
ReadFiles(blocksStream);
if (!IsDataAfterBundle)
reader.Close();
break; break;
} }
} }
@ -156,7 +166,7 @@ namespace AssetStudio
m_BlocksInfo = new StorageBlock[1]; m_BlocksInfo = new StorageBlock[1];
for (int i = 0; i < levelCount; i++) for (int i = 0; i < levelCount; i++)
{ {
var storageBlock = new StorageBlock() var storageBlock = new StorageBlock
{ {
compressedSize = reader.ReadUInt32(), compressedSize = reader.ReadUInt32(),
uncompressedSize = reader.ReadUInt32(), uncompressedSize = reader.ReadUInt32(),
@ -179,23 +189,15 @@ namespace AssetStudio
private Stream CreateBlocksStream(string path) private Stream CreateBlocksStream(string path)
{ {
Stream blocksStream;
var uncompressedSizeSum = m_BlocksInfo.Sum(x => x.uncompressedSize); var uncompressedSizeSum = m_BlocksInfo.Sum(x => x.uncompressedSize);
if (uncompressedSizeSum >= int.MaxValue) return uncompressedSizeSum >= int.MaxValue
{ ? new FileStream(path + ".temp", FileMode.Create, FileAccess.ReadWrite, FileShare.None, 4096, FileOptions.DeleteOnClose)
/*var memoryMappedFile = MemoryMappedFile.CreateNew(null, uncompressedSizeSum); : (Stream) new MemoryStream((int)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;
} }
private void ReadBlocksAndDirectory(FileReader reader, Stream blocksStream) private Stream ReadBlocksAndDirectory(FileReader reader)
{ {
var blocksStream = CreateBlocksStream(reader.FullPath);
var isCompressed = m_Header.signature == "UnityWeb"; var isCompressed = m_Header.signature == "UnityWeb";
foreach (var blockInfo in m_BlocksInfo) foreach (var blockInfo in m_BlocksInfo)
{ {
@ -213,10 +215,11 @@ namespace AssetStudio
blocksStream.Write(uncompressedBytes, 0, uncompressedBytes.Length); blocksStream.Write(uncompressedBytes, 0, uncompressedBytes.Length);
} }
blocksStream.Position = 0; blocksStream.Position = 0;
var blocksReader = new EndianBinaryReader(blocksStream); var blocksReader = new EndianBinaryReader(blocksStream);
var nodesCount = blocksReader.ReadInt32(); var nodesCount = blocksReader.ReadInt32();
m_DirectoryInfo = new Node[nodesCount]; m_DirectoryInfo = new Node[nodesCount];
for (int i = 0; i < nodesCount; i++) for (var i = 0; i < nodesCount; i++)
{ {
m_DirectoryInfo[i] = new Node m_DirectoryInfo[i] = new Node
{ {
@ -225,33 +228,27 @@ namespace AssetStudio
size = blocksReader.ReadUInt32() 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]; fileList = new List<StreamFile>(m_DirectoryInfo.Length);
for (int i = 0; i < m_DirectoryInfo.Length; i++) foreach (var node in m_DirectoryInfo)
{ {
var node = m_DirectoryInfo[i];
var file = new StreamFile(); var file = new StreamFile();
fileList[i] = file; fileList.Add(file);
file.path = node.path; file.path = node.path;
file.fileName = Path.GetFileName(node.path); file.fileName = Path.GetFileName(node.path);
if (node.size >= int.MaxValue) try
{ {
/*var memoryMappedFile = MemoryMappedFile.CreateNew(null, entryinfo_size); file.stream = new OffsetStream(inputStream, node.offset + blocksOffset, node.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);
} }
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) if (numWrite != uncompressedSize)
{ {
var msg = $"{compressionType} blockInfo decompression error. {errorMsg}\nWrite {numWrite} bytes but expected {uncompressedSize} bytes."; 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." ? "Wrong compression type or blockInfo data might be encrypted."
: "BlockInfo data might be encrypted."; : "BlockInfo data might be encrypted.";
throw new IOException($"{msg}\n{exMsg}\n"); throw new IOException($"{msg}\n{exMsg}\n");
@ -376,7 +373,7 @@ namespace AssetStudio
var uncompressedDataHash = blocksInfoReader.ReadBytes(16); var uncompressedDataHash = blocksInfoReader.ReadBytes(16);
var blocksInfoCount = blocksInfoReader.ReadInt32(); var blocksInfoCount = blocksInfoReader.ReadInt32();
m_BlocksInfo = new StorageBlock[blocksInfoCount]; m_BlocksInfo = new StorageBlock[blocksInfoCount];
for (int i = 0; i < blocksInfoCount; i++) for (var i = 0; i < blocksInfoCount; i++)
{ {
m_BlocksInfo[i] = new StorageBlock m_BlocksInfo[i] = new StorageBlock
{ {
@ -388,7 +385,7 @@ namespace AssetStudio
var nodesCount = blocksInfoReader.ReadInt32(); var nodesCount = blocksInfoReader.ReadInt32();
m_DirectoryInfo = new Node[nodesCount]; m_DirectoryInfo = new Node[nodesCount];
for (int i = 0; i < nodesCount; i++) for (var i = 0; i < nodesCount; i++)
{ {
m_DirectoryInfo[i] = new Node 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; if (customBlockCompression == CompressionType.Auto)
foreach (var blockInfo in m_BlocksInfo)
{ {
var compressionType = (CompressionType)(blockInfo.flags & StorageBlockFlags.CompressionTypeMask); if (blocksCompression > CompressionType.Lzham && Enum.IsDefined(typeof(CompressionType), blocksCompression))
if (customBlockCompression == CompressionType.Auto)
{ {
if (showCustomTypeWarning && compressionType > CompressionType.Lzham && Enum.IsDefined(typeof(CompressionType), compressionType)) Logger.Warning($"Non-standard block compression type: {(int)blocksCompression}. Trying to decompress as {blocksCompression} archive..");
{
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;
}
} }
}
else
{
Logger.Info($"Custom block compression type: {customBlockCompression}");
blocksCompression = customBlockCompression;
}
long numWrite; byte[] sharedCompressedBuff = null;
var errorMsg = string.Empty; byte[] sharedUncompressedBuff = null;
switch (compressionType) if (blocksCompression != CompressionType.Lzma && blocksCompression != CompressionType.Lzham)
{
var blockSize = (int)m_BlocksInfo.Max(x => x.uncompressedSize);
Logger.Debug($"BlockSize: {blockSize}");
sharedCompressedBuff = BigArrayPool<byte>.Shared.Rent(blockSize);
sharedUncompressedBuff = BigArrayPool<byte>.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); compressionType = customBlockCompression;
numWrite = blockInfo.compressedSize;
break;
} }
case CompressionType.Lzma:
long numWrite;
var errorMsg = string.Empty;
switch (compressionType)
{ {
numWrite = BundleDecompressionHelper.DecompressLzmaStream(reader.BaseStream, blocksStream, blockInfo.compressedSize, blockInfo.uncompressedSize, ref errorMsg); case CompressionType.None:
break; reader.BaseStream.CopyTo(blocksStream, blockInfo.compressedSize);
} numWrite = blockInfo.compressedSize;
case CompressionType.Lz4: break;
case CompressionType.Lz4HC: case CompressionType.Lzma:
case CompressionType.Zstd: Logger.Info("Decompressing LZMA stream...");
case CompressionType.Oodle: numWrite = BundleDecompressionHelper.DecompressLzmaStream(reader.BaseStream, blocksStream, blockInfo.compressedSize, blockInfo.uncompressedSize, ref errorMsg);
{ break;
var compressedSize = (int)blockInfo.compressedSize; case CompressionType.Lz4:
var uncompressedSize = (int)blockInfo.uncompressedSize; case CompressionType.Lz4HC:
case CompressionType.Zstd:
var compressedBytes = BigArrayPool<byte>.Shared.Rent(compressedSize); case CompressionType.Oodle:
var uncompressedBytes = BigArrayPool<byte>.Shared.Rent(uncompressedSize); var compressedSize = (int)blockInfo.compressedSize;
try var uncompressedSize = (int)blockInfo.uncompressedSize;
{
_ = reader.Read(compressedBytes, 0, compressedSize); sharedCompressedBuff.AsSpan().Clear();
var compressedSpan = new ReadOnlySpan<byte>(compressedBytes, 0, compressedSize); sharedUncompressedBuff.AsSpan().Clear();
var uncompressedSpan = new Span<byte>(uncompressedBytes, 0, uncompressedSize);
_ = reader.Read(sharedCompressedBuff, 0, compressedSize);
var compressedSpan = new ReadOnlySpan<byte>(sharedCompressedBuff, 0, compressedSize);
var uncompressedSpan = new Span<byte>(sharedUncompressedBuff, 0, uncompressedSize);
numWrite = BundleDecompressionHelper.DecompressBlock(compressionType, compressedSpan, uncompressedSpan, ref errorMsg); numWrite = BundleDecompressionHelper.DecompressBlock(compressionType, compressedSpan, uncompressedSpan, ref errorMsg);
if (numWrite == uncompressedSize) if (numWrite == uncompressedSize)
blocksStream.Write(uncompressedBytes, 0, uncompressedSize); blocksStream.Write(sharedUncompressedBuff, 0, uncompressedSize);
}
finally
{
BigArrayPool<byte>.Shared.Return(compressedBytes, clearArray: true);
BigArrayPool<byte>.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");
}
if (numWrite != blockInfo.uncompressedSize) break;
{ case CompressionType.Lzham:
var msg = $"{compressionType} block decompression error. {errorMsg}\nWrite {numWrite} bytes but expected {blockInfo.uncompressedSize} bytes."; throw new IOException($"Unsupported block compression type: {compressionType}.\n");
var exMsg = compressionType > CompressionType.Lz4HC default:
? "Wrong compression type or block data might be encrypted." throw new IOException($"Unknown block compression type: {compressionType}.\nYou may try to specify the compression type manually.\n");
: "Block data might be encrypted."; }
throw new IOException($"{msg}\n{exMsg}\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<byte>.Shared.Return(sharedCompressedBuff, clearArray: true);
if (sharedUncompressedBuff != null)
BigArrayPool<byte>.Shared.Return(sharedUncompressedBuff, clearArray: true);
}
return blocksStream;
} }
private void UnityCnCheck(FileReader reader, CompressionType customBlockInfoCompression, UnityVersion unityVer) 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."); throw new NotSupportedException("Unsupported bundle file. UnityCN encryption was detected.");
} }
private bool IsUncompressedBundle => m_BlocksInfo.All(x => (CompressionType)(x.flags & StorageBlockFlags.CompressionTypeMask) == CompressionType.None);
} }
} }

View File

@ -5,12 +5,15 @@ namespace AssetStudio
public class OffsetStream : Stream public class OffsetStream : Stream
{ {
private readonly Stream _baseStream; private readonly Stream _baseStream;
private readonly long _length;
private long _offset; private long _offset;
public override bool CanRead => _baseStream.CanRead; public override bool CanRead => _baseStream.CanRead;
public override bool CanSeek => _baseStream.CanSeek; public override bool CanSeek => _baseStream.CanSeek;
public override bool CanWrite => false; 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 public override long Position
{ {
@ -40,6 +43,13 @@ namespace AssetStudio
Offset = reader.Position; Offset = reader.Position;
} }
public OffsetStream(Stream stream, long offset, long length)
{
_baseStream = stream;
_length = length;
Offset = offset;
}
public override void Flush() { } public override void Flush() { }
public override int Read(byte[] buffer, int offset, int count) public override int Read(byte[] buffer, int offset, int count)

View File

@ -6,7 +6,7 @@ namespace AssetStudio
{ {
public class WebFile public class WebFile
{ {
public StreamFile[] fileList; public List<StreamFile> fileList;
private class WebData private class WebData
{ {
@ -30,16 +30,15 @@ namespace AssetStudio
data.path = Encoding.UTF8.GetString(reader.ReadBytes(pathLength)); data.path = Encoding.UTF8.GetString(reader.ReadBytes(pathLength));
dataList.Add(data); dataList.Add(data);
} }
fileList = new StreamFile[dataList.Count]; fileList = new List<StreamFile>(dataList.Count);
for (int i = 0; i < dataList.Count; i++) foreach (var data in dataList)
{ {
var data = dataList[i];
var file = new StreamFile(); var file = new StreamFile();
file.path = data.path; file.path = data.path;
file.fileName = Path.GetFileName(data.path); file.fileName = Path.GetFileName(data.path);
reader.BaseStream.Position = data.dataOffset; reader.BaseStream.Position = data.dataOffset;
file.stream = new MemoryStream(reader.ReadBytes(data.dataLength)); file.stream = new MemoryStream(reader.ReadBytes(data.dataLength));
fileList[i] = file; fileList.Add(file);
} }
} }
} }

View File

@ -96,11 +96,11 @@ namespace AssetStudioCLI
var bundleReader = new FileReader(reader.FullPath, bundleStream); var bundleReader = new FileReader(reader.FullPath, bundleStream);
var bundleFile = new BundleFile(bundleReader, assetsManager.CustomBlockInfoCompression, assetsManager.CustomBlockCompression, assetsManager.SpecifyUnityVersion); var bundleFile = new BundleFile(bundleReader, assetsManager.CustomBlockInfoCompression, assetsManager.CustomBlockCompression, assetsManager.SpecifyUnityVersion);
var extractPath = Path.Combine(savePath, reader.FileName + "_unpacked"); var extractPath = Path.Combine(savePath, reader.FileName + "_unpacked");
if (bundleFile.fileList.Length > 0) if (bundleFile.fileList.Count > 0)
{ {
count += ExtractStreamFile(extractPath, bundleFile.fileList); count += ExtractStreamFile(extractPath, bundleFile.fileList);
} }
while (bundleFile.IsMultiBundle) while (bundleFile.IsDataAfterBundle)
{ {
bundleStream.Offset = reader.Position; bundleStream.Offset = reader.Position;
bundleReader = new FileReader($"{reader.FullPath}_0x{bundleStream.Offset:X}", bundleStream); bundleReader = new FileReader($"{reader.FullPath}_0x{bundleStream.Offset:X}", bundleStream);
@ -113,8 +113,8 @@ namespace AssetStudioCLI
bundleReader.FileName = $"{reader.FileName}_0x{bundleStream.Offset:X}"; bundleReader.FileName = $"{reader.FileName}_0x{bundleStream.Offset:X}";
} }
Logger.Info($"[MultiBundle] Decompressing \"{reader.FileName}\" from offset: 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); bundleFile = new BundleFile(bundleReader, assetsManager.CustomBlockInfoCompression, assetsManager.CustomBlockCompression, assetsManager.SpecifyUnityVersion, isMultiBundle: true);
if (bundleFile.fileList.Length > 0) if (bundleFile.fileList.Count > 0)
{ {
count += ExtractStreamFile(extractPath, bundleFile.fileList); count += ExtractStreamFile(extractPath, bundleFile.fileList);
} }
@ -128,19 +128,21 @@ namespace AssetStudioCLI
Logger.Info($"Decompressing {reader.FileName} ..."); Logger.Info($"Decompressing {reader.FileName} ...");
var webFile = new WebFile(reader); var webFile = new WebFile(reader);
reader.Dispose(); reader.Dispose();
if (webFile.fileList.Length > 0) if (webFile.fileList.Count > 0)
{ {
var extractPath = Path.Combine(savePath, reader.FileName + "_unpacked"); var extractPath = Path.Combine(savePath, reader.FileName + "_unpacked");
return ExtractStreamFile(extractPath, webFile.fileList); return ExtractStreamFile(extractPath, webFile.fileList, isOffsetStream: false);
} }
return 0; return 0;
} }
private static int ExtractStreamFile(string extractPath, StreamFile[] fileList) private static int ExtractStreamFile(string extractPath, List<StreamFile> fileList, bool isOffsetStream = true)
{ {
var extractedCount = 0; var extractedCount = 0;
foreach (var file in fileList) foreach (var file in fileList)
{ {
if (file.stream == null)
continue;
var filePath = Path.Combine(extractPath, file.path); var filePath = Path.Combine(extractPath, file.path);
var fileDirectory = Path.GetDirectoryName(filePath); var fileDirectory = Path.GetDirectoryName(filePath);
if (!Directory.Exists(fileDirectory)) if (!Directory.Exists(fileDirectory))
@ -151,11 +153,16 @@ namespace AssetStudioCLI
{ {
using (var fileStream = File.Create(filePath)) 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; return extractedCount;
} }

View File

@ -143,11 +143,11 @@ namespace AssetStudioGUI
var bundleReader = new FileReader(reader.FullPath, bundleStream); var bundleReader = new FileReader(reader.FullPath, bundleStream);
var bundleFile = new BundleFile(bundleReader, assetsManager.CustomBlockInfoCompression, assetsManager.CustomBlockCompression, assetsManager.SpecifyUnityVersion); var bundleFile = new BundleFile(bundleReader, assetsManager.CustomBlockInfoCompression, assetsManager.CustomBlockCompression, assetsManager.SpecifyUnityVersion);
var extractPath = Path.Combine(savePath, reader.FileName + "_unpacked"); var extractPath = Path.Combine(savePath, reader.FileName + "_unpacked");
if (bundleFile.fileList.Length > 0) if (bundleFile.fileList.Count > 0)
{ {
count += ExtractStreamFile(extractPath, bundleFile.fileList); count += ExtractStreamFile(extractPath, bundleFile.fileList);
} }
while (bundleFile.IsMultiBundle) while (bundleFile.IsDataAfterBundle)
{ {
bundleStream.Offset = reader.Position; bundleStream.Offset = reader.Position;
bundleReader = new FileReader($"{reader.FullPath}_0x{bundleStream.Offset:X}", bundleStream); bundleReader = new FileReader($"{reader.FullPath}_0x{bundleStream.Offset:X}", bundleStream);
@ -160,8 +160,8 @@ namespace AssetStudioGUI
bundleReader.FileName = $"{reader.FileName}_0x{bundleStream.Offset:X}"; bundleReader.FileName = $"{reader.FileName}_0x{bundleStream.Offset:X}";
} }
Logger.Info($"[MultiBundle] Decompressing \"{reader.FileName}\" from offset: 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); bundleFile = new BundleFile(bundleReader, assetsManager.CustomBlockInfoCompression, assetsManager.CustomBlockCompression, assetsManager.SpecifyUnityVersion, isMultiBundle: true);
if (bundleFile.fileList.Length > 0) if (bundleFile.fileList.Count > 0)
{ {
count += ExtractStreamFile(extractPath, bundleFile.fileList); count += ExtractStreamFile(extractPath, bundleFile.fileList);
} }
@ -175,19 +175,21 @@ namespace AssetStudioGUI
Logger.Info($"Decompressing {reader.FileName} ..."); Logger.Info($"Decompressing {reader.FileName} ...");
var webFile = new WebFile(reader); var webFile = new WebFile(reader);
reader.Dispose(); reader.Dispose();
if (webFile.fileList.Length > 0) if (webFile.fileList.Count > 0)
{ {
var extractPath = Path.Combine(savePath, reader.FileName + "_unpacked"); var extractPath = Path.Combine(savePath, reader.FileName + "_unpacked");
return ExtractStreamFile(extractPath, webFile.fileList); return ExtractStreamFile(extractPath, webFile.fileList, isOffsetStream: false);
} }
return 0; return 0;
} }
private static int ExtractStreamFile(string extractPath, StreamFile[] fileList) private static int ExtractStreamFile(string extractPath, List<StreamFile> fileList, bool isOffsetStream = true)
{ {
int extractedCount = 0; var extractedCount = 0;
foreach (var file in fileList) foreach (var file in fileList)
{ {
if (file.stream == null)
continue;
var filePath = Path.Combine(extractPath, file.path); var filePath = Path.Combine(extractPath, file.path);
var fileDirectory = Path.GetDirectoryName(filePath); var fileDirectory = Path.GetDirectoryName(filePath);
if (!Directory.Exists(fileDirectory)) if (!Directory.Exists(fileDirectory))
@ -198,11 +200,16 @@ namespace AssetStudioGUI
{ {
using (var fileStream = File.Create(filePath)) 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; return extractedCount;
} }