private int ReadMultiUnitFile(byte[] buffer, BlockTableEntry blockEntry) { var sectorCount = blockEntry.FileSize / SectorSize + 1; //if (blockEntry.FileSize%SectorSize > 0) //sectorCount++; var hasCRC = (blockEntry.Flags & BlockFlags.HasChecksums) != 0; if (hasCRC) { sectorCount++; } var sectorTable = new int[sectorCount + 1]; for (var i = 0; i < sectorTable.Length; i++) { sectorTable[i] = _reader.ReadInt32(); } var resultPosition = 0; long left = blockEntry.FileSize; for (var i = 0; i < sectorCount - (hasCRC ? 1 : 0); i++) { var position = sectorTable[i]; var length = sectorTable[i + 1] - position; SeekToArchiveOffset(position + blockEntry.BlockOffset); var tempBuffer = ArrayPool <byte> .Shared.Rent(length); var decompressedBuffer = tempBuffer; int decompressedLength = length; var sectorData = _reader.Read(tempBuffer, 0, length); Debug.Assert(sectorData == length); if (blockEntry.IsCompressed && left > length) { var compressionFlags = (CompressionFlags)tempBuffer[0]; if (compressionFlags == CompressionFlags.Bzip2) { decompressedBuffer = Compression.BZip2Decompress(tempBuffer, 1, length - 1); decompressedLength = decompressedBuffer.Length; Array.Copy(decompressedBuffer, 0, buffer, resultPosition, decompressedLength); } else if (compressionFlags == CompressionFlags.Deflated) { decompressedLength = Compression.DeflateTo(tempBuffer, 1, length - 1, buffer.AsSpan(resultPosition)); } else { throw new NotSupportedException( "Currenlty only Bzip2 and Deflate compression is supported by Nmpq. Compression flags: " + compressionFlags); } } else { Array.Copy(decompressedBuffer, 0, buffer, resultPosition, decompressedLength); } ArrayPool <byte> .Shared.Return(tempBuffer); resultPosition += decompressedLength; left -= decompressedLength; } return((int)blockEntry.FileSize); }
// todos: support more decompression algorithms? public int?ReadFile(byte[] buffer, string path) { if (path == null) { throw new ArgumentNullException("path"); } var blockEntry = FindBlockTableEntry(path); if (blockEntry == null) { return(null); } if (!blockEntry.Value.IsFile) { throw new NotSupportedException("Non-file blocks are not currently supported by Nmpq."); } if (blockEntry.Value.IsEncrypted) { throw new NotSupportedException("Encrypted files are not currently supported by Nmpq."); } if (blockEntry.Value.IsImploded) { throw new NotSupportedException("Imploded files are not currently supported by Nmpq."); } SeekToArchiveOffset(blockEntry.Value.BlockOffset); if (!blockEntry.Value.IsFileSingleUnit) { return(ReadMultiUnitFile(buffer, blockEntry.Value)); } // file is only compressed if the block size is smaller than the file size. // per docs at (http://wiki.devklog.net/index.php?title=MPQ_format_specification) var compressed = blockEntry.Value.IsCompressed && blockEntry.Value.BlockSize < blockEntry.Value.FileSize; if (!compressed) { return(_reader.Read(buffer, 0, (int)blockEntry.Value.BlockSize)); } // first byte of each compressed block is a set of flags indicating which // compression algorithm(s) to use var compressionFlags = (CompressionFlags)_reader.ReadByte(); // compression flags don't count toward the data size, but does toward the block size var dataSize = (int)(blockEntry.Value.BlockSize - 1); var compressedData = ArrayPool <byte> .Shared.Rent((int)dataSize); _reader.Read(compressedData, 0, (int)dataSize); int decompressedLength; if (compressionFlags == CompressionFlags.Bzip2) { decompressedLength = Compression.BZip2Decompress(compressedData, 0, dataSize, buffer); } else if (compressionFlags == CompressionFlags.Deflated) { decompressedLength = Compression.Deflate(compressedData, 0, dataSize, buffer); } else { throw new NotSupportedException("Currenlty only Bzip2 and Deflate compression is supported by Nmpq. Compression flags: " + compressionFlags); } ArrayPool <byte> .Shared.Return(compressedData); return(decompressedLength); }