213 lines
8.6 KiB
C#
213 lines
8.6 KiB
C#
|
using LZ4;
|
|||
|
using System;
|
|||
|
using System.IO;
|
|||
|
using System.Linq;
|
|||
|
using UnityEngine;
|
|||
|
|
|||
|
namespace UnityFS
|
|||
|
{
|
|||
|
|
|||
|
public class BundleFileReader
|
|||
|
{
|
|||
|
|
|||
|
private Header m_Header;
|
|||
|
private StorageBlock[] m_BlocksInfo;
|
|||
|
private Node[] m_DirectoryInfo;
|
|||
|
|
|||
|
private StreamFile[] fileList;
|
|||
|
|
|||
|
public BundleFileReader()
|
|||
|
{
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
public void Load(EndianBinaryReader reader)
|
|||
|
{
|
|||
|
Debug.Log($"reader. pos:{reader.Position} length:{reader.BaseStream.Length}");
|
|||
|
m_Header = new Header();
|
|||
|
m_Header.signature = reader.ReadStringToNull();
|
|||
|
m_Header.version = reader.ReadUInt32();
|
|||
|
m_Header.unityVersion = reader.ReadStringToNull();
|
|||
|
m_Header.unityRevision = reader.ReadStringToNull();
|
|||
|
System.Diagnostics.Debug.Assert(m_Header.signature == "UnityFS");
|
|||
|
|
|||
|
|
|||
|
m_Header.size = reader.ReadInt64();
|
|||
|
Debug.Log($"header size:{m_Header.size}");
|
|||
|
m_Header.compressedBlocksInfoSize = reader.ReadUInt32();
|
|||
|
m_Header.uncompressedBlocksInfoSize = reader.ReadUInt32();
|
|||
|
m_Header.flags = (ArchiveFlags)reader.ReadUInt32();
|
|||
|
if (m_Header.signature != "UnityFS")
|
|||
|
{
|
|||
|
reader.ReadByte();
|
|||
|
}
|
|||
|
|
|||
|
ReadMetadata(reader);
|
|||
|
using (var blocksStream = CreateBlocksStream())
|
|||
|
{
|
|||
|
ReadBlocks(reader, blocksStream);
|
|||
|
ReadFiles(blocksStream);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
public BundleFileInfo CreateBundleFileInfo()
|
|||
|
{
|
|||
|
return new BundleFileInfo
|
|||
|
{
|
|||
|
signature = m_Header.signature,
|
|||
|
version = m_Header.version,
|
|||
|
unityVersion = m_Header.unityVersion,
|
|||
|
unityRevision = m_Header.unityRevision,
|
|||
|
files = fileList.Select(f => new BundleSubFile { file = f.path, data = f.stream.ReadAllBytes() }).ToList(),
|
|||
|
};
|
|||
|
}
|
|||
|
|
|||
|
private byte[] ReadBlocksInfoAndDirectoryMetadataUnCompressedBytes(EndianBinaryReader reader)
|
|||
|
{
|
|||
|
byte[] metadataUncompressBytes;
|
|||
|
if (m_Header.version >= 7)
|
|||
|
{
|
|||
|
reader.AlignStream(16);
|
|||
|
}
|
|||
|
if ((m_Header.flags & ArchiveFlags.BlocksInfoAtTheEnd) != 0)
|
|||
|
{
|
|||
|
var position = reader.Position;
|
|||
|
reader.Position = reader.BaseStream.Length - m_Header.compressedBlocksInfoSize;
|
|||
|
metadataUncompressBytes = reader.ReadBytes((int)m_Header.compressedBlocksInfoSize);
|
|||
|
reader.Position = position;
|
|||
|
}
|
|||
|
else //0x40 BlocksAndDirectoryInfoCombined
|
|||
|
{
|
|||
|
metadataUncompressBytes = reader.ReadBytes((int)m_Header.compressedBlocksInfoSize);
|
|||
|
}
|
|||
|
return metadataUncompressBytes;
|
|||
|
}
|
|||
|
|
|||
|
private byte[] DecompressBytes(CompressionType compressionType, byte[] compressedBytes, uint uncompressedSize)
|
|||
|
{
|
|||
|
switch (compressionType)
|
|||
|
{
|
|||
|
case CompressionType.None:
|
|||
|
{
|
|||
|
return compressedBytes;
|
|||
|
}
|
|||
|
case CompressionType.Lzma:
|
|||
|
{
|
|||
|
var uncompressedStream = new MemoryStream((int)(uncompressedSize));
|
|||
|
using (var compressedStream = new MemoryStream(compressedBytes))
|
|||
|
{
|
|||
|
ComparessHelper.Decompress7Zip(compressedStream, uncompressedStream, m_Header.compressedBlocksInfoSize, m_Header.uncompressedBlocksInfoSize);
|
|||
|
}
|
|||
|
return uncompressedStream.ReadAllBytes();
|
|||
|
}
|
|||
|
case CompressionType.Lz4:
|
|||
|
case CompressionType.Lz4HC:
|
|||
|
{
|
|||
|
var uncompressedBytes = new byte[uncompressedSize];
|
|||
|
var numWrite = LZ4Codec.Decode(compressedBytes, 0, compressedBytes.Length, uncompressedBytes, 0, uncompressedBytes.Length, true);
|
|||
|
if (numWrite != uncompressedSize)
|
|||
|
{
|
|||
|
throw new IOException($"Lz4 decompression error, write {numWrite} bytes but expected {uncompressedSize} bytes");
|
|||
|
}
|
|||
|
return uncompressedBytes;
|
|||
|
}
|
|||
|
default:
|
|||
|
throw new IOException($"Unsupported compression type {compressionType}");
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
private void ReadMetadata(EndianBinaryReader reader)
|
|||
|
{
|
|||
|
byte[] compressMetadataBytes = ReadBlocksInfoAndDirectoryMetadataUnCompressedBytes(reader);
|
|||
|
MemoryStream metadataStream = new MemoryStream(DecompressBytes((CompressionType)(m_Header.flags & ArchiveFlags.CompressionTypeMask), compressMetadataBytes, m_Header.uncompressedBlocksInfoSize));
|
|||
|
using (var blocksInfoReader = new EndianBinaryReader(metadataStream))
|
|||
|
{
|
|||
|
var uncompressedDataHash = blocksInfoReader.ReadBytes(16);
|
|||
|
var blocksInfoCount = blocksInfoReader.ReadInt32();
|
|||
|
m_BlocksInfo = new StorageBlock[blocksInfoCount];
|
|||
|
for (int i = 0; i < blocksInfoCount; i++)
|
|||
|
{
|
|||
|
m_BlocksInfo[i] = new StorageBlock
|
|||
|
{
|
|||
|
uncompressedSize = blocksInfoReader.ReadUInt32(),
|
|||
|
compressedSize = blocksInfoReader.ReadUInt32(),
|
|||
|
flags = (StorageBlockFlags)blocksInfoReader.ReadUInt16()
|
|||
|
};
|
|||
|
}
|
|||
|
|
|||
|
var nodesCount = blocksInfoReader.ReadInt32();
|
|||
|
m_DirectoryInfo = new Node[nodesCount];
|
|||
|
for (int i = 0; i < nodesCount; i++)
|
|||
|
{
|
|||
|
m_DirectoryInfo[i] = new Node
|
|||
|
{
|
|||
|
offset = blocksInfoReader.ReadInt64(),
|
|||
|
size = blocksInfoReader.ReadInt64(),
|
|||
|
flags = blocksInfoReader.ReadUInt32(),
|
|||
|
path = blocksInfoReader.ReadStringToNull(),
|
|||
|
};
|
|||
|
}
|
|||
|
}
|
|||
|
if (m_Header.flags.HasFlag(ArchiveFlags.BlockInfoNeedPaddingAtStart))
|
|||
|
{
|
|||
|
reader.AlignStream(16);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
private Stream CreateBlocksStream()
|
|||
|
{
|
|||
|
Stream blocksStream;
|
|||
|
var uncompressedSizeSum = m_BlocksInfo.Sum(x => x.uncompressedSize);
|
|||
|
if (uncompressedSizeSum >= int.MaxValue)
|
|||
|
{
|
|||
|
throw new Exception($"too fig file");
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
blocksStream = new MemoryStream((int)uncompressedSizeSum);
|
|||
|
}
|
|||
|
return blocksStream;
|
|||
|
}
|
|||
|
|
|||
|
public void ReadFiles(Stream blocksStream)
|
|||
|
{
|
|||
|
fileList = new StreamFile[m_DirectoryInfo.Length];
|
|||
|
for (int i = 0; i < m_DirectoryInfo.Length; i++)
|
|||
|
{
|
|||
|
var node = m_DirectoryInfo[i];
|
|||
|
var file = new StreamFile();
|
|||
|
fileList[i] = file;
|
|||
|
file.path = node.path;
|
|||
|
file.fileName = Path.GetFileName(node.path);
|
|||
|
if (node.size >= int.MaxValue)
|
|||
|
{
|
|||
|
throw new Exception($"exceed max file size");
|
|||
|
/*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 MemoryStream((int)node.size);
|
|||
|
blocksStream.Position = node.offset;
|
|||
|
blocksStream.CopyTo(file.stream, node.size);
|
|||
|
file.stream.Position = 0;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
private void ReadBlocks(EndianBinaryReader reader, Stream blocksStream)
|
|||
|
{
|
|||
|
foreach (var blockInfo in m_BlocksInfo)
|
|||
|
{
|
|||
|
var compressedSize = (int)blockInfo.compressedSize;
|
|||
|
byte[] compressedBlockBytes = reader.ReadBytes(compressedSize);
|
|||
|
var compressionType = (CompressionType)(blockInfo.flags & StorageBlockFlags.CompressionTypeMask);
|
|||
|
byte[] uncompressedBlockBytes = DecompressBytes(compressionType, compressedBlockBytes, blockInfo.uncompressedSize);
|
|||
|
blocksStream.Write(uncompressedBlockBytes, 0, uncompressedBlockBytes.Length);
|
|||
|
}
|
|||
|
blocksStream.Position = 0;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|