using LZ4; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using UnityEngine; namespace UnityFS { public class BundleFileWriter { private readonly BundleFileInfo _bundle; private readonly List _files = new List(); private readonly List _blocks = new List(); private readonly EndianBinaryWriter _blockDirectoryMetadataStream = new EndianBinaryWriter(new MemoryStream()); private byte[] _blockBytes; public BundleFileWriter(BundleFileInfo bundle) { _bundle = bundle; } public void Write(EndianBinaryWriter output) { InitBlockAndDirectories(); output.WriteNullEndString(_bundle.signature); output.Write(_bundle.version); output.WriteNullEndString(_bundle.unityVersion); output.WriteNullEndString(_bundle.unityRevision); BuildBlockDirectoryMetadata(); long sizePos = output.Position; output.Write(0L); output.Write((uint)_blockDirectoryMetadataStream.Length); output.Write((uint)_blockDirectoryMetadataStream.Length); ArchiveFlags flags = ArchiveFlags.BlocksAndDirectoryInfoCombined | (uint)CompressionType.None; output.Write((uint)flags); if (_bundle.version >= 7) { output.AlignStream(16); } byte[] metadataBytes = _blockDirectoryMetadataStream.BaseStream.ReadAllBytes(); output.Write(metadataBytes, 0, metadataBytes.Length); byte[] dataBytes = _blockBytes; output.Write(dataBytes, 0, dataBytes.Length); output.Position = sizePos; output.Write(output.Length); } private void InitBlockAndDirectories() { var dataStream = new MemoryStream(); foreach(var file in _bundle.files) { byte[] data = file.data; _files.Add(new Node { path = file.file, flags = 0, offset = dataStream.Length, size = data.LongLength }); dataStream.Write(data, 0, data.Length); } byte[] dataBytes = dataStream.ToArray(); var compressedBlockStream = new MemoryStream(dataBytes.Length / 2); int blockByteSize = 128 * 1024; long dataSize = dataBytes.Length; byte[] tempCompressBlock = new byte[blockByteSize * 2]; for(long i = 0, blockNum = (dataSize + blockByteSize - 1) / blockByteSize; i < blockNum; i++) { long curBlockSize = Math.Min(dataSize, blockByteSize); dataSize -= curBlockSize; int compressedSize = LZ4Codec.Encode(dataBytes, (int)(i * blockByteSize), (int)curBlockSize, tempCompressBlock, 0, tempCompressBlock.Length); compressedBlockStream.Write(tempCompressBlock, 0, compressedSize); _blocks.Add(new StorageBlock { flags = (StorageBlockFlags)(int)CompressionType.Lz4, compressedSize = (uint)compressedSize, uncompressedSize = (uint)curBlockSize }); //Debug.Log($"== block[{i}] uncompressedSize:{curBlockSize} compressedSize:{compressedSize} totalblocksize:{compressedBlockStream.Length}"); } _blockBytes = compressedBlockStream.ToArray(); } private void BuildBlockDirectoryMetadata() { var hash = new byte[16]; _blockDirectoryMetadataStream.Write(hash, 0, 16); _blockDirectoryMetadataStream.Write((uint)_blocks.Count); foreach(var b in _blocks) { _blockDirectoryMetadataStream.Write(b.uncompressedSize); _blockDirectoryMetadataStream.Write(b.compressedSize); _blockDirectoryMetadataStream.Write((ushort)b.flags); } _blockDirectoryMetadataStream.Write((uint)_files.Count); foreach(var f in _files) { _blockDirectoryMetadataStream.Write(f.offset); _blockDirectoryMetadataStream.Write(f.size); _blockDirectoryMetadataStream.Write(f.flags); _blockDirectoryMetadataStream.WriteNullEndString(f.path); } //Debug.Log($"block and directory metadata size:{_blockDirectoryMetadataStream.Length}"); } } }