public MpqBlock(BinaryReader br, uint HeaderOffset) { FilePos = br.ReadUInt32() + HeaderOffset; CompressedSize = br.ReadUInt32(); FileSize = br.ReadUInt32(); Flags = (MpqFileFlags)br.ReadUInt32(); }
internal MpqEntry(string filename, uint compressedSize, uint fileSize, MpqFileFlags flags) { CompressedSize = compressedSize; FileSize = fileSize; Flags = flags; _filename = filename; }
internal MpqEntry(uint filePos, uint compressedSize, uint fileSize, MpqFileFlags flags) { FilePos = filePos; CompressedSize = compressedSize; FileSize = fileSize; Flags = flags; IsAdded = true; }
internal MpqFile(ulong?hashedName, MpqStream mpqStream, MpqFileFlags flags, MpqLocale locale, bool leaveOpen) { _name = hashedName; _mpqStream = mpqStream ?? throw new ArgumentNullException(nameof(mpqStream)); _isStreamOwner = !leaveOpen; _flags = flags; _locale = locale; _compressionType = MpqCompressionType.ZLib; }
public void AddFile(MpqFile file, MpqFileFlags targetFlags) { if (file is null) { throw new ArgumentNullException(nameof(file)); } file.TargetFlags = targetFlags; _modifiedFiles.Add(file); }
internal MpqEntry(uint filePos, uint headerOffset, uint compressedSize, uint fileSize, MpqFileFlags flags) { _fileOffset = filePos - headerOffset; FilePos = filePos; CompressedSize = compressedSize; FileSize = fileSize; Flags = flags; EncryptionSeed = 0; IsAdded = true; }
internal MpqBlockEntry(long offset, uint compressedSize, uint uncompressedSize, uint flags, ref uint fileIndex) { this.Offset = offset; this.CompressedSize = compressedSize; this.UncompressedSize = uncompressedSize; this.Flags = unchecked((MpqFileFlags)flags); this.Name = ""; this.FileIndex = (this.Flags & MpqFileFlags.Exists) != 0 ? fileIndex++ : 0; this.Seed = 0; this.Listed = false; }
internal MpqBlockEntry(long offset, uint compressedSize, uint uncompressedSize, uint flags, ref uint fileIndex) { this.Offset = offset; this.CompressedSize = compressedSize; this.UncompressedSize = uncompressedSize; this.Flags = unchecked ((MpqFileFlags)flags); this.Name = ""; this.FileIndex = (this.Flags & MpqFileFlags.Exists) != 0 ? fileIndex++ : 0; this.Seed = 0; this.Listed = false; }
private string CompressionTypeString(MpqFileFlags Flags) { if ((Flags & MpqFileFlags.CompressedMulti) != ((MpqFileFlags)0)) { return("Multi"); } if ((Flags & MpqFileFlags.CompressedPK) != ((MpqFileFlags)0)) { return("PKZip"); } return(""); }
private uint _hashIndex; // position in hashtable /// <summary> /// Initializes a new instance of the <see cref="MpqFile"/> class. /// </summary> /// <param name="sourceStream"></param> /// <param name="fileName"></param> /// <param name="flags"></param> /// <param name="blockSize"></param> /// <exception cref="ArgumentNullException">Thrown when the <paramref name="sourceStream"/> argument is null.</exception> public MpqFile(Stream sourceStream, string fileName, MpqFileFlags flags, ushort blockSize) { _baseStream = sourceStream ?? throw new ArgumentNullException(nameof(sourceStream)); _fileName = fileName; _blockSize = 0x200 << blockSize; var fileSize = (uint)_baseStream.Length; var compressedSize = ((flags & MpqFileFlags.Compressed) != 0) ? Compress() : fileSize; _entry = new MpqEntry(fileName, compressedSize, fileSize, flags); }
/// <summary> /// Initializes a new instance of the <see cref="MpqEntry"/> class. /// </summary> /// <param name="filename"></param> /// <param name="headerOffset">The containing <see cref="MpqArchive"/>'s header offset.</param> /// <param name="fileOffset">The file's position in the archive, relative to the header offset.</param> /// <param name="compressedSize">The compressed size of the file.</param> /// <param name="fileSize">The uncompressed size of the file.</param> /// <param name="flags">The file's <see cref="MpqFileFlags"/>.</param> internal MpqEntry(string?filename, uint headerOffset, uint fileOffset, uint compressedSize, uint fileSize, MpqFileFlags flags) { _headerOffset = headerOffset; _fileOffset = fileOffset; _filename = filename; _compressedSize = compressedSize; _fileSize = fileSize; _flags = flags; if (filename != null) { UpdateEncryptionSeed(); } }
public void TestStoreThenRetrieveEmptyFileWithFlags(MpqFileFlags flags) { const string FileName = "someRandomFile.empty"; using var mpqFile = MpqFile.New(null, FileName); mpqFile.TargetFlags = flags; using var archive = MpqArchive.Create(new MemoryStream(), new List <MpqFile>() { mpqFile }, new MpqArchiveCreateOptions { BlockSize = BlockSize }); using var openedArchive = MpqArchive.Open(archive.BaseStream); var openedStream = openedArchive.OpenFile(FileName); Assert.IsTrue(openedStream.Length == 0); }
public void TestStoreThenRetrieveFileWithFlags(string fileName, MpqFileFlags flags) { using var fileStream = File.OpenRead(fileName); var mpqFile = MpqFile.New(fileStream, fileName, true); mpqFile.TargetFlags = flags; using var archive = MpqArchive.Create(new MemoryStream(), new List <MpqFile>() { mpqFile }, new MpqArchiveCreateOptions { BlockSize = BlockSize }); using var openedArchive = MpqArchive.Open(archive.BaseStream); var openedStream = openedArchive.OpenFile(fileName); StreamAssert.AreEqual(fileStream, openedStream, true); }
/// <summary> /// Initializes a new instance of the <see cref="MpqUnknownFile"/> class. /// </summary> internal MpqUnknownFile(MpqStream mpqStream, MpqFileFlags flags, MpqHash mpqHash, uint hashIndex, uint hashCollisions, uint?encryptionSeed = null) : base(mpqHash.Name, mpqStream, flags, mpqHash.Locale, false) { if (mpqHash.Mask == 0) { throw new ArgumentException("Expected the Mask value of mpqHash argument to be set to a non-zero value.", nameof(mpqHash)); } if (flags.HasFlag(MpqFileFlags.Encrypted) && encryptionSeed is null) { throw new ArgumentException($"Cannot encrypt an {nameof(MpqUnknownFile)} without an encryption seed.", nameof(flags)); } _hashMask = mpqHash.Mask; _hashIndex = hashIndex; _hashCollisions = hashCollisions; _encryptionSeed = encryptionSeed; }
public void TestStoreThenRetrieveEmptyFileWithFlags(MpqFileFlags flags) { const string FileName = "someRandomFile.empty"; // var mpqFile = new MpqKnownFile(FileName, null, flags, MpqLocale.Neutral); var mpqFile = MpqFile.New(null, FileName); mpqFile.TargetFlags = flags; var archive = MpqArchive.Create(new MemoryStream(), new List <MpqFile>() { mpqFile }, blockSize: BlockSize); var openedArchive = MpqArchive.Open(archive.BaseStream); var openedStream = openedArchive.OpenFile(FileName); Assert.IsTrue(openedStream.Length == 0); }
public void TestStoreThenRetrieveFileWithFlags(string filename, MpqFileFlags flags) { var fileStream = File.OpenRead(filename); // var mpqFile = new MpqKnownFile(filename, fileStream, flags, MpqLocale.Neutral, true); var mpqFile = MpqFile.New(fileStream, filename); mpqFile.TargetFlags = flags; var archive = MpqArchive.Create(new MemoryStream(), new List <MpqFile>() { mpqFile }, blockSize: BlockSize); var openedArchive = MpqArchive.Open(archive.BaseStream); var openedStream = openedArchive.OpenFile(filename); fileStream.Position = 0; StreamAssert.AreEqual(fileStream, openedStream); }
/// <summary> /// Initializes a new instance of the <see cref="MpqFile"/> class, for which the filename is unknown. /// </summary> /// <param name="sourceStream"></param> /// <param name="mpqHash"></param> /// <param name="hashIndex"></param> /// <param name="hashCollisions"></param> /// <param name="flags"></param> /// <param name="blockSize"></param> /// <exception cref="ArgumentException"></exception> /// <exception cref="ArgumentNullException">Thrown when the <paramref name="sourceStream"/> argument is null.</exception> public MpqFile(Stream sourceStream, MpqHash mpqHash, uint hashIndex, uint hashCollisions, MpqFileFlags flags, ushort blockSize) : this(sourceStream, null, flags, blockSize) { if (mpqHash.Mask == 0) { throw new ArgumentException("Expected the Mask value of mpqHash argument to be set to a non-zero value.", nameof(mpqHash)); } _hash = mpqHash; _hashIndex = hashIndex; _hashCollisions = hashCollisions; }
internal Stream Transform(MpqFileFlags targetFlags, MpqCompressionType compressionType, uint targetFilePosition, int targetBlockSize) { using var memoryStream = new MemoryStream(); CopyTo(memoryStream); memoryStream.Position = 0; var fileSize = memoryStream.Length; using var compressedStream = GetCompressedStream(memoryStream, targetFlags, compressionType, targetBlockSize); var compressedSize = (uint)compressedStream.Length; var resultStream = new MemoryStream(); var blockPosCount = (uint)(((int)fileSize + targetBlockSize - 1) / targetBlockSize) + 1; if (targetFlags.HasFlag(MpqFileFlags.Encrypted) && blockPosCount > 1) { var blockPositions = new int[blockPosCount]; var singleUnit = targetFlags.HasFlag(MpqFileFlags.SingleUnit); var hasBlockPositions = !singleUnit && ((targetFlags & MpqFileFlags.Compressed) != 0); if (hasBlockPositions) { for (var blockIndex = 0; blockIndex < blockPosCount; blockIndex++) { using (var br = new BinaryReader(compressedStream, new UTF8Encoding(), true)) { for (var i = 0; i < blockPosCount; i++) { blockPositions[i] = (int)br.ReadUInt32(); } } compressedStream.Seek(0, SeekOrigin.Begin); } } else { if (singleUnit) { blockPosCount = 2; } blockPositions[0] = 0; for (var blockIndex = 2; blockIndex < blockPosCount; blockIndex++) { blockPositions[blockIndex - 1] = targetBlockSize * (blockIndex - 1); } blockPositions[blockPosCount - 1] = (int)compressedSize; } var encryptionSeed = _baseEncryptionSeed; if (targetFlags.HasFlag(MpqFileFlags.BlockOffsetAdjustedKey)) { encryptionSeed = MpqEntry.AdjustEncryptionSeed(encryptionSeed, targetFilePosition, (uint)fileSize); } var currentOffset = 0; using (var writer = new BinaryWriter(resultStream, new UTF8Encoding(false, true), true)) { for (var blockIndex = hasBlockPositions ? 0 : 1; blockIndex < blockPosCount; blockIndex++) { var toWrite = blockPositions[blockIndex] - currentOffset; var data = StormBuffer.EncryptStream(compressedStream, (uint)(encryptionSeed + blockIndex - 1), currentOffset, toWrite); writer.Write(data); currentOffset += toWrite; } } } else { compressedStream.CopyTo(resultStream); } resultStream.Position = 0; return(resultStream); }
/// <summary> /// Initializes a new instance of the <see cref="MpqKnownFile"/> class. /// </summary> internal MpqKnownFile(string fileName, MpqStream mpqStream, MpqFileFlags flags, MpqLocale locale, bool leaveOpen = false) : base(MpqHash.GetHashedFileName(fileName), mpqStream, flags, locale, leaveOpen) { _fileName = fileName; }
private Stream GetCompressedStream(Stream baseStream, MpqFileFlags targetFlags, MpqCompressionType compressionType, int targetBlockSize) { var resultStream = new MemoryStream(); var singleUnit = targetFlags.HasFlag(MpqFileFlags.SingleUnit); void TryCompress(uint bytes) { var offset = baseStream.Position; var compressedStream = compressionType switch { MpqCompressionType.ZLib => ZLibCompression.Compress(baseStream, (int)bytes, true), _ => throw new NotSupportedException(), }; // Add one because CompressionType byte not written yet. var length = compressedStream.Length + 1; if (!singleUnit && length >= bytes) { baseStream.CopyTo(resultStream, offset, (int)bytes, StreamExtensions.DefaultBufferSize); } else { resultStream.WriteByte((byte)compressionType); compressedStream.Position = 0; compressedStream.CopyTo(resultStream); } compressedStream.Dispose(); if (singleUnit) { baseStream.Dispose(); } } var length = (uint)baseStream.Length; if ((targetFlags & MpqFileFlags.Compressed) == 0) { baseStream.CopyTo(resultStream); } else if (singleUnit) { TryCompress(length); } else { var blockCount = (uint)((length + targetBlockSize - 1) / targetBlockSize) + 1; var blockOffsets = new uint[blockCount]; blockOffsets[0] = 4 * blockCount; resultStream.Position = blockOffsets[0]; for (var blockIndex = 1; blockIndex < blockCount; blockIndex++) { var bytesToCompress = blockIndex + 1 == blockCount ? (uint)(baseStream.Length - baseStream.Position) : (uint)targetBlockSize; TryCompress(bytesToCompress); blockOffsets[blockIndex] = (uint)resultStream.Position; } resultStream.Position = 0; using (var writer = new BinaryWriter(resultStream, new System.Text.UTF8Encoding(false, true), true)) { for (var blockIndex = 0; blockIndex < blockCount; blockIndex++) { writer.Write(blockOffsets[blockIndex]); } } } resultStream.Position = 0; return(resultStream); }
public MpqOrphanedFile(MpqStream mpqStream, MpqFileFlags flags) : base(null, mpqStream, flags, MpqLocale.Neutral, false) { }
/// <summary> /// Initializes a new instance of the <see cref="MpqStream"/> class. /// </summary> /// <param name="entry">The file's entry in the <see cref="BlockTable"/>.</param> /// <param name="baseStream">The <see cref="MpqArchive"/>'s stream.</param> /// <param name="blockSize">The <see cref="MpqArchive.BlockSize"/>.</param> internal MpqStream(MpqEntry entry, Stream baseStream, int blockSize) { _mode = MpqStreamMode.Read; _isStreamOwner = false; _filePosition = entry.FilePosition; _fileSize = entry.FileSize; _compressedSize = entry.CompressedSize; _flags = entry.Flags; _isCompressed = (_flags & MpqFileFlags.Compressed) != 0; _isEncrypted = _flags.HasFlag(MpqFileFlags.Encrypted); _isSingleUnit = _flags.HasFlag(MpqFileFlags.SingleUnit); _encryptionSeed = entry.EncryptionSeed; _baseEncryptionSeed = entry.BaseEncryptionSeed; _stream = baseStream; _blockSize = blockSize; if (_isSingleUnit) { // Read the entire file into memory var filedata = new byte[_compressedSize]; lock (_stream) { _stream.Seek(_filePosition, SeekOrigin.Begin); var read = _stream.Read(filedata, 0, filedata.Length); if (read != filedata.Length) { throw new MpqParserException("Insufficient data or invalid data length"); } } if (_isEncrypted && _fileSize > 3) { if (_encryptionSeed == 0) { throw new MpqParserException("Unable to determine encryption key"); } StormBuffer.DecryptBlock(filedata, _encryptionSeed); } _currentData = _flags.HasFlag(MpqFileFlags.CompressedMulti) && _compressedSize > 0 ? DecompressMulti(filedata, _fileSize) : filedata; } else { _currentBlockIndex = -1; // Compressed files start with an array of offsets to make seeking possible if (_isCompressed) { var blockposcount = (int)((_fileSize + _blockSize - 1) / _blockSize) + 1; // Files with metadata have an extra block containing block checksums if ((_flags & MpqFileFlags.FileHasMetadata) != 0) { blockposcount++; } _blockPositions = new uint[blockposcount]; lock (_stream) { _stream.Seek(_filePosition, SeekOrigin.Begin); using (var br = new BinaryReader(_stream, new UTF8Encoding(), true)) { for (var i = 0; i < blockposcount; i++) { _blockPositions[i] = br.ReadUInt32(); } } } var blockpossize = (uint)blockposcount * 4; /* * if (_blockPositions[0] != blockpossize) * { * // _entry.Flags |= MpqFileFlags.Encrypted; * throw new MpqParserException(); * } */ if (_isEncrypted && blockposcount > 1) { var maxOffset1 = (uint)_blockSize + blockpossize; if (_encryptionSeed == 0) { // This should only happen when the file name is not known. if (!entry.TryUpdateEncryptionSeed(_blockPositions[0], _blockPositions[1], blockpossize, maxOffset1)) { throw new MpqParserException("Unable to determine encyption seed"); } } _encryptionSeed = entry.EncryptionSeed; _baseEncryptionSeed = entry.BaseEncryptionSeed; StormBuffer.DecryptBlock(_blockPositions, _encryptionSeed - 1); if (_blockPositions[0] != blockpossize) { throw new MpqParserException($"Decryption failed{(string.IsNullOrEmpty(entry.FileName) ? string.Empty : $" for '{entry.FileName}'")} (block position 0)."); } if (_blockPositions[1] > maxOffset1) { throw new MpqParserException($"Decryption failed{(string.IsNullOrEmpty(entry.FileName) ? string.Empty : $" for '{entry.FileName}'")} (block position 1)."); } } }
/// <summary> /// Builds a compressions type string /// </summary> /// <param name="Flags">The flags.</param> /// <returns></returns> string CompressionTypeString(MpqFileFlags Flags) { if((Flags & MpqFileFlags.CompressedMulti) != 0) return "Multi"; if((Flags & MpqFileFlags.CompressedPK) != 0) return "PKZip"; return ""; }