mirror of
https://github.com/aelurum/AssetStudio.git
synced 2025-11-12 23:32:42 -05:00
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:
@ -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)
|
||||||
|
|||||||
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user