diff --git a/AssetTools.NET/Standard/AssetsBundleFileFormat/AssetsBundleFile.cs b/AssetTools.NET/Standard/AssetsBundleFileFormat/AssetsBundleFile.cs index 68accd6..f311b74 100644 --- a/AssetTools.NET/Standard/AssetsBundleFileFormat/AssetsBundleFile.cs +++ b/AssetTools.NET/Standard/AssetsBundleFileFormat/AssetsBundleFile.cs @@ -380,7 +380,7 @@ public bool Unpack(AssetsFileReader reader, AssetsFileWriter writer) } return false; } - public bool Pack(AssetsFileReader reader, AssetsFileWriter writer, AssetBundleCompressionType compType) + public bool Pack(AssetsFileReader reader, AssetsFileWriter writer, AssetBundleCompressionType compType, bool blockDirAtEnd = true) { reader.Position = 0; writer.Position = 0; @@ -395,7 +395,7 @@ public bool Pack(AssetsFileReader reader, AssetsFileWriter writer, AssetBundleCo totalFileSize = 0, compressedSize = 0, decompressedSize = 0, - flags = 0x43 + flags = (uint)(0x43 | (blockDirAtEnd ? 0x80 : 0x00)) }; AssetBundleBlockAndDirectoryList06 newBlockAndDirList = new AssetBundleBlockAndDirectoryList06() @@ -408,128 +408,164 @@ public bool Pack(AssetsFileReader reader, AssetsFileWriter writer, AssetBundleCo dirInf = bundleInf6.dirInf }; + //write header now and overwrite it later + long startPos = writer.Position; + + newHeader.Write(writer); + if (newHeader.fileVersion >= 7) + writer.Align16(); + + int headerSize = (int)(writer.Position - startPos); + + long totalCompressedSize = 0; List newBlocks = new List(); + List newStreams = new List(); //used if blockDirAtEnd == false - reader.Position = bundleHeader6.GetFileDataOffset(); - int fileDataLength = (int)(bundleHeader6.totalFileSize - reader.Position); - byte[] fileData = reader.ReadBytes(fileDataLength); + long fileDataOffset = bundleHeader6.GetFileDataOffset(); + int fileDataLength = (int)(bundleHeader6.totalFileSize - fileDataOffset); + + SegmentStream bundleDataStream = new SegmentStream(reader.BaseStream, fileDataOffset, fileDataLength); - //todo, we just write everything to memory and then write to file - //we could calculate the blocks we need ahead of time and correctly - //size the block listing before this so we can write directly to file - byte[] compressedFileData; switch (compType) { case AssetBundleCompressionType.LZMA: { - compressedFileData = SevenZipHelper.Compress(fileData); - newBlocks.Add(new AssetBundleBlockInfo06() + Stream writeStream; + if (blockDirAtEnd) + writeStream = writer.BaseStream; + else + writeStream = GetTempFileStream(); + + long writeStreamStart = writeStream.Position; + SevenZipHelper.Compress(bundleDataStream, writeStream); + uint writeStreamLength = (uint)(writeStream.Position - writeStreamStart); + + AssetBundleBlockInfo06 blockInfo = new AssetBundleBlockInfo06() { - compressedSize = (uint)compressedFileData.Length, - decompressedSize = (uint)fileData.Length, + compressedSize = writeStreamLength, + decompressedSize = (uint)fileDataLength, flags = 0x41 - }); + }; + + totalCompressedSize += blockInfo.compressedSize; + newBlocks.Add(blockInfo); + + if (blockDirAtEnd) + bundleDataStream.CopyToCompat(writer.BaseStream); + else + newStreams.Add(writeStream); + break; } case AssetBundleCompressionType.LZ4: { - using (var memStreamCom = new MemoryStream()) - using (var binaryWriter = new BinaryWriter(memStreamCom)) + //compress into 0x20000 blocks + BinaryReader bundleDataReader = new BinaryReader(bundleDataStream); + byte[] uncompressedBlock = bundleDataReader.ReadBytes(0x20000); + while (uncompressedBlock.Length != 0) { - using (var memStreamUnc = new MemoryStream(fileData)) - using (var binaryReader = new BinaryReader(memStreamUnc)) + Stream writeStream; + if (blockDirAtEnd) + writeStream = writer.BaseStream; + else + writeStream = GetTempFileStream(); + + byte[] compressedBlock = LZ4Codec.Encode32HC(uncompressedBlock, 0, uncompressedBlock.Length); + writeStream.Write(compressedBlock, 0, compressedBlock.Length); + + if (compressedBlock.Length > uncompressedBlock.Length) { - //compress into 0x20000 blocks - byte[] uncompressedBlock = binaryReader.ReadBytes(131072); - while (uncompressedBlock.Length != 0) + AssetBundleBlockInfo06 blockInfo = new AssetBundleBlockInfo06() { - byte[] compressedBlock = LZ4Codec.Encode32HC(uncompressedBlock, 0, uncompressedBlock.Length); - - if (compressedBlock.Length > uncompressedBlock.Length) - { - newBlocks.Add(new AssetBundleBlockInfo06() - { - compressedSize = (uint)uncompressedBlock.Length, - decompressedSize = (uint)uncompressedBlock.Length, - flags = 0x0 - }); - binaryWriter.Write(uncompressedBlock); - } - else - { - newBlocks.Add(new AssetBundleBlockInfo06() - { - compressedSize = (uint)compressedBlock.Length, - decompressedSize = (uint)uncompressedBlock.Length, - flags = 0x3 - }); - binaryWriter.Write(compressedBlock); - } - - uncompressedBlock = binaryReader.ReadBytes(131072); - } + compressedSize = (uint)uncompressedBlock.Length, + decompressedSize = (uint)uncompressedBlock.Length, + flags = 0x0 + }; + + totalCompressedSize += blockInfo.compressedSize; + + newBlocks.Add(blockInfo); } + else + { + AssetBundleBlockInfo06 blockInfo = new AssetBundleBlockInfo06() + { + compressedSize = (uint)compressedBlock.Length, + decompressedSize = (uint)uncompressedBlock.Length, + flags = 0x3 + }; + + totalCompressedSize += blockInfo.compressedSize; + + newBlocks.Add(blockInfo); + } + + if (blockDirAtEnd) + bundleDataStream.CopyToCompat(writer.BaseStream); + else + newStreams.Add(writeStream); - compressedFileData = memStreamCom.ToArray(); + uncompressedBlock = bundleDataReader.ReadBytes(0x20000); } break; } case AssetBundleCompressionType.NONE: { - compressedFileData = fileData; - newBlocks.Add(new AssetBundleBlockInfo06() + AssetBundleBlockInfo06 blockInfo = new AssetBundleBlockInfo06() { - compressedSize = (uint)fileData.Length, - decompressedSize = (uint)fileData.Length, + compressedSize = (uint)fileDataLength, + decompressedSize = (uint)fileDataLength, flags = 0x00 - }); + }; + + totalCompressedSize += blockInfo.compressedSize; + + newBlocks.Add(blockInfo); + + if (blockDirAtEnd) + bundleDataStream.CopyToCompat(writer.BaseStream); + else + newStreams.Add(bundleDataStream); + break; } - default: - { - return false; - } } newBlockAndDirList.blockInf = newBlocks.ToArray(); byte[] bundleInfoBytes; - using (var memStream = new MemoryStream()) + using (MemoryStream memStream = new MemoryStream()) { - var afw = new AssetsFileWriter(memStream); - newBlockAndDirList.Write(afw); + AssetsFileWriter infoWriter = new AssetsFileWriter(memStream); + newBlockAndDirList.Write(infoWriter); bundleInfoBytes = memStream.ToArray(); } - if (bundleInfoBytes == null || bundleInfoBytes.Length == 0) - return false; - //listing is usually lz4 even if the data blocks are lzma byte[] bundleInfoBytesCom = LZ4Codec.Encode32HC(bundleInfoBytes, 0, bundleInfoBytes.Length); - byte[] bundleHeaderBytes = null; - using (var memStream = new MemoryStream()) - { - var afw = new AssetsFileWriter(memStream); - newHeader.Write(afw); - bundleHeaderBytes = memStream.ToArray(); - } - - if (bundleHeaderBytes == null || bundleHeaderBytes.Length == 0) - return false; - - uint totalFileSize = (uint)(bundleHeaderBytes.Length + bundleInfoBytesCom.Length + compressedFileData.Length); + uint totalFileSize = (uint)(headerSize + bundleInfoBytesCom.Length + totalCompressedSize); newHeader.totalFileSize = totalFileSize; newHeader.decompressedSize = (uint)bundleInfoBytes.Length; newHeader.compressedSize = (uint)bundleInfoBytesCom.Length; + writer.Write(bundleInfoBytesCom); + + if (!blockDirAtEnd) + { + foreach (Stream newStream in newStreams) + { + newStream.Position = 0; + newStream.CopyToCompat(writer.BaseStream); + newStream.Close(); + } + } + + writer.Position = 0; newHeader.Write(writer); if (newHeader.fileVersion >= 7) writer.Align16(); - writer.Write(bundleInfoBytesCom); - writer.Write(compressedFileData); - return true; } return false; @@ -614,6 +650,13 @@ internal void GetFileRange(int index, out long offset, out long length) throw new NotSupportedException(); } } + + private FileStream GetTempFileStream() + { + string tempFilePath = Path.GetTempFileName(); + FileStream tempFileStream = new FileStream(tempFilePath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.Read, 4096, FileOptions.DeleteOnClose); + return tempFileStream; + } } public enum AssetBundleCompressionType