private byte[] ExtractUncompressedSectoredFile ( [NotNull] BlockTableEntry fileBlockEntry, uint fileKey ) { // This file uses sectoring, but is not compressed. It may be encrypted. var finalSectorSize = fileBlockEntry.GetFileSize() % GetMaxSectorSize(); // All the even sectors you can fit into the file size var sectorCount = (fileBlockEntry.GetFileSize() - finalSectorSize) / GetMaxSectorSize(); var rawSectors = new List <byte[]>(); for (var i = 0; i < sectorCount; ++i) { // Read a normal sector (usually 4096 bytes) rawSectors.Add(_archiveReader.ReadBytes((int)GetMaxSectorSize())); } // And finally, if there's an uneven sector at the end, read that one too if (finalSectorSize > 0) { rawSectors.Add(_archiveReader.ReadBytes((int)finalSectorSize)); } uint sectorIndex = 0; var finalSectors = new List <byte[]>(); foreach (var rawSector in rawSectors) { var pendingSector = rawSector; if (fileBlockEntry.IsEncrypted()) { // Decrypt the block pendingSector = MPQCrypt.DecryptData(rawSector, fileKey + sectorIndex); } finalSectors.Add(pendingSector); ++sectorIndex; } return(StitchSectors(finalSectors)); }
/// <summary> /// Extracts a file which is stored as a single unit in the archive. /// </summary> /// <param name="fileBlockEntry">The block entry of the file.</param> /// <param name="fileKey">The encryption key of the file.</param> /// <returns>The complete file data.</returns> private byte[] ExtractSingleUnitFile(BlockTableEntry fileBlockEntry, uint fileKey) { // This file does not use sectoring, but may still be encrypted or compressed. byte[] fileData = this.ArchiveReader.ReadBytes((int)fileBlockEntry.GetBlockSize()); if (fileBlockEntry.IsEncrypted()) { // Decrypt the block fileData = MPQCrypt.DecryptData(fileData, fileKey); } // Decompress the sector if neccesary if (fileBlockEntry.IsCompressed()) { fileData = Compression.DecompressSector(fileData, fileBlockEntry.Flags); } return(fileData); }
/// <summary> /// Initializes a new instance of the <see cref="Warcraft.MPQ.MPQ"/> class. /// </summary> /// <param name="mpqStream">An open stream to data containing an MPQ archive.</param> public MPQ([NotNull] Stream mpqStream) { _archiveReader = new BinaryReader(mpqStream); Header = new MPQHeader(_archiveReader.ReadBytes((int)PeekHeaderSize())); // Seek to the hash table and load it _archiveReader.BaseStream.Position = (long)Header.GetHashTableOffset(); var encryptedHashTable = _archiveReader.ReadBytes((int)Header.GetHashTableSize()); var hashTableData = MPQCrypt.DecryptData(encryptedHashTable, HashTable.TableKey); ArchiveHashTable = new HashTable(hashTableData); // Seek to the block table and load it _archiveReader.BaseStream.Position = (long)Header.GetBlockTableOffset(); var encryptedBlockTable = _archiveReader.ReadBytes((int)Header.GetBlockTableSize()); var blockTableData = MPQCrypt.DecryptData(encryptedBlockTable, BlockTable.TableKey); ArchiveBlockTable = new BlockTable(blockTableData); if (Header.GetFormat() != MPQFormat.ExtendedV1) { return; } // Seek to the extended block table and load it, if necessary if (Header.GetExtendedBlockTableOffset() <= 0) { return; } _archiveReader.BaseStream.Position = (long)Header.GetExtendedBlockTableOffset(); var extendedTable = new List <ushort>(); for (var i = 0; i < Header.GetBlockTableEntryCount(); ++i) { extendedTable.Add(_archiveReader.ReadUInt16()); } ExtendedBlockTable = extendedTable; }
/// <summary> /// Extracts a file which is divided into a set of compressed sectors. /// </summary> /// <param name="fileBlockEntry">The block entry of the file.</param> /// <param name="fileKey">The encryption key of the file.</param> /// <param name="adjustedBlockOffset">The offset to where the file sectors begin.</param> /// <returns>The complete file data.</returns> /// <exception cref="InvalidFileSectorTableException">Thrown if the sector table is found to be inconsistent in any way.</exception> private byte[] ExtractCompressedSectoredFile(BlockTableEntry fileBlockEntry, uint fileKey, long adjustedBlockOffset) { // This file uses sectoring, and is compressed. It may be encrypted. //Retrieve the offsets for each sector - these are relative to the beginning of the data. List <uint> sectorOffsets = ReadFileSectorOffsetTable(fileBlockEntry, fileKey); // Read all of the raw file sectors. List <byte[]> compressedSectors = new List <byte[]>(); for (int i = 0; i < sectorOffsets.Count - 1; ++i) { long sectorStartPosition = adjustedBlockOffset + sectorOffsets[i]; this.ArchiveReader.BaseStream.Position = sectorStartPosition; uint sectorLength = sectorOffsets[i + 1] - sectorOffsets[i]; compressedSectors.Add(this.ArchiveReader.ReadBytes((int)sectorLength)); } // Begin decompressing and decrypting the sectors // TODO: If Checksums are present (check the flags), treat the last sector as a checksum sector // TODO: Check "backup.MPQ/realmlist.wtf" for a weird file with checksums that is not working correctly. // It has a single sector with a single checksum after it, and none of the hashing functions seem to // produce a valid hash. CRC32, Adler32, CRC32B, nothing. // Some flags (listfiles mostly) are flagged as having checksums but don't have a checksum sector. // Perhaps related to attributes file? List <byte[]> decompressedSectors = new List <byte[]>(); /* List<uint> SectorChecksums = new List<uint>(); * if (fileBlockEntry.Flags.HasFlag(BlockFlags.BLF_HasChecksums)) * { * byte[] compressedChecksums = compressedSectors.Last(); * byte[] decompressedChecksums = Compression.DecompressSector(compressedChecksums, fileBlockEntry.Flags); * * // Lift out the last sector and treat it as a checksum sector * using (MemoryStream ms = new MemoryStream(decompressedChecksums)) * { * using (BinaryReader br = new BinaryReader(ms)) * { * // Drop the checksum sector from the file sectors * compressedSectors.RemoveAt(compressedSectors.Count - 1); * * for (int i = 0; i < compressedSectors.Count; ++i) * { * SectorChecksums.Add(br.ReadUInt32()); * } * } * } * }*/ uint sectorIndex = 0; foreach (byte[] compressedSector in compressedSectors) { byte[] pendingSector = compressedSector; if (fileBlockEntry.IsEncrypted()) { // Decrypt the block pendingSector = MPQCrypt.DecryptData(compressedSector, fileKey + sectorIndex); } /*if (fileBlockEntry.Flags.HasFlag(BlockFlags.HasCRCChecksums)) * { * // Verify the sector * bool isSectorIntact = MPQCrypt.VerifySectorChecksum(pendingSector, SectorChecksums[(int)sectorIndex]); * if (!isSectorIntact) * { * using (MemoryStream ms = new MemoryStream(pendingSector)) * { * //DEBUG * * uint sectorChecksum = (uint)Adler32.ComputeChecksum(ms); * * string exceptionMessage = String.Format("The decrypted sector failed its integrity checking. \n" + * "The sector had a checksum of \"{0}\", and the expected one was \"{1}\".", * sectorChecksum, SectorChecksums[(int)sectorIndex]); * * throw new InvalidDataException(exceptionMessage); * } * } * }*/ // Decompress the sector if neccesary if (pendingSector.Length < GetMaxSectorSize()) { int currentFileSize = CountBytesInSectors(decompressedSectors); bool canSectorCompleteFile = currentFileSize + pendingSector.Length == fileBlockEntry.GetFileSize(); if (!canSectorCompleteFile && currentFileSize != fileBlockEntry.GetFileSize()) { pendingSector = Compression.DecompressSector(pendingSector, fileBlockEntry.Flags); } } decompressedSectors.Add(pendingSector); ++sectorIndex; } return(StitchSectors(decompressedSectors)); }
/// <summary> /// Initializes a new instance of the <see cref="Warcraft.MPQ.MPQ"/> class. /// </summary> /// <param name="mpqStream">An open stream to data containing an MPQ archive.</param> public MPQ(Stream mpqStream) { this.ArchiveReader = new BinaryReader(mpqStream); this.Header = new MPQHeader(this.ArchiveReader.ReadBytes((int)PeekHeaderSize())); // Seek to the hash table and load it this.ArchiveReader.BaseStream.Position = (long)this.Header.GetHashTableOffset(); byte[] hashTableData; if (this.Header.IsHashTableCompressed()) { byte[] encryptedData = this.ArchiveReader.ReadBytes((int)this.Header.GetCompressedHashTableSize()); byte[] decryptedData = MPQCrypt.DecryptData(encryptedData, HashTable.TableKey); BlockFlags tableFlags = BlockFlags.IsCompressedMultiple; hashTableData = Compression.DecompressSector(decryptedData, tableFlags); } else { byte[] encryptedData = this.ArchiveReader.ReadBytes((int)this.Header.GetHashTableSize()); hashTableData = MPQCrypt.DecryptData(encryptedData, HashTable.TableKey); } this.ArchiveHashTable = new HashTable(hashTableData); // Seek to the block table and load it this.ArchiveReader.BaseStream.Position = (long)this.Header.GetBlockTableOffset(); byte[] blockTableData; if (this.Header.IsBlockTableCompressed()) { byte[] encryptedData = this.ArchiveReader.ReadBytes((int)this.Header.GetCompressedBlockTableSize()); byte[] decryptedData = MPQCrypt.DecryptData(encryptedData, BlockTable.TableKey); BlockFlags tableFlags = BlockFlags.IsCompressedMultiple; blockTableData = Compression.DecompressSector(decryptedData, tableFlags); } else { byte[] encryptedData = this.ArchiveReader.ReadBytes((int)this.Header.GetBlockTableSize()); blockTableData = MPQCrypt.DecryptData(encryptedData, BlockTable.TableKey); } this.ArchiveBlockTable = new BlockTable(blockTableData); // TODO: Seek to the extended hash table and load it // TODO: Seek to the extended block table and load it if (this.Header.GetFormat() >= MPQFormat.ExtendedV1) { // Seek to the extended block table and load it, if neccesary if (this.Header.GetExtendedBlockTableOffset() > 0) { this.ArchiveReader.BaseStream.Position = (long)this.Header.GetExtendedBlockTableOffset(); for (int i = 0; i < this.Header.GetBlockTableEntryCount(); ++i) { this.ExtendedBlockTable.Add(this.ArchiveReader.ReadUInt16()); } } } }