/// <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; }
/// <inheritdoc /> /// <exception cref="ObjectDisposedException">Thrown if the archive has been disposed.</exception> /// <exception cref="FileNotFoundException">Thrown if the archive does not contain a file at the given path.</exception> /// <exception cref="FileDeletedException">Thrown if the file is deleted in the archive.</exception> public byte[] ExtractFile(string filePath) { ThrowIfDisposed(); // Reset all positions to be safe ArchiveReader.BaseStream.Position = 0; HashTableEntry fileHashEntry; try { fileHashEntry = ArchiveHashTable.FindEntry(filePath); } catch (FileNotFoundException fex) { throw new FileNotFoundException("No file found at the given path.", filePath, fex); } BlockTableEntry fileBlockEntry = ArchiveBlockTable.GetEntry((int)fileHashEntry.GetBlockEntryIndex()); // Drop out if the file has been deleted if (fileBlockEntry.IsDeleted()) { throw new FileDeletedException("The given file is deleted.", filePath); } // Seek to the beginning of the file's sectors long adjustedBlockOffset; if (Header.GetFormat() >= MPQFormat.ExtendedV1 && RequiresExtendedFormat()) { ushort upperOffsetBits = ExtendedBlockTable[(int)fileHashEntry.GetBlockEntryIndex()]; adjustedBlockOffset = (long)fileBlockEntry.GetExtendedBlockOffset(upperOffsetBits); } else { adjustedBlockOffset = fileBlockEntry.GetBlockOffset(); } ArchiveReader.BaseStream.Position = adjustedBlockOffset; // Calculate the decryption key if neccesary uint fileKey = MPQCrypt.CreateFileEncryptionKey ( filePath, fileBlockEntry.ShouldEncryptionKeyBeAdjusted(), adjustedBlockOffset, fileBlockEntry.GetFileSize() ); // Examine the file storage types and extract as neccesary if (fileBlockEntry.IsSingleUnit()) { return(ExtractSingleUnitFile(fileBlockEntry, fileKey)); } if (fileBlockEntry.IsCompressed()) { return(ExtractCompressedSectoredFile(fileBlockEntry, fileKey, adjustedBlockOffset)); } return(ExtractUncompressedSectoredFile(fileBlockEntry, fileKey)); }
/// <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) { 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(); byte[] hashTableData; if (Header.IsHashTableCompressed()) { byte[] encryptedData = ArchiveReader.ReadBytes((int)Header.GetCompressedHashTableSize()); byte[] decryptedData = MPQCrypt.DecryptData(encryptedData, HashTable.TableKey); BlockFlags tableFlags = BlockFlags.IsCompressedMultiple; hashTableData = Compression.DecompressSector(decryptedData, tableFlags); } else { byte[] encryptedData = ArchiveReader.ReadBytes((int)Header.GetHashTableSize()); hashTableData = MPQCrypt.DecryptData(encryptedData, HashTable.TableKey); } ArchiveHashTable = new HashTable(hashTableData); // Seek to the block table and load it ArchiveReader.BaseStream.Position = (long)Header.GetBlockTableOffset(); byte[] blockTableData; if (Header.IsBlockTableCompressed()) { byte[] encryptedData = ArchiveReader.ReadBytes((int)Header.GetCompressedBlockTableSize()); byte[] decryptedData = MPQCrypt.DecryptData(encryptedData, BlockTable.TableKey); BlockFlags tableFlags = BlockFlags.IsCompressedMultiple; blockTableData = Compression.DecompressSector(decryptedData, tableFlags); } else { byte[] encryptedData = ArchiveReader.ReadBytes((int)Header.GetBlockTableSize()); blockTableData = MPQCrypt.DecryptData(encryptedData, BlockTable.TableKey); } 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 (Header.GetFormat() >= MPQFormat.ExtendedV1) { // Seek to the extended block table and load it, if neccesary if (Header.GetExtendedBlockTableOffset() <= 0) { return; } ArchiveReader.BaseStream.Position = (long)Header.GetExtendedBlockTableOffset(); for (int i = 0; i < Header.GetBlockTableEntryCount(); ++i) { ExtendedBlockTable.Add(ArchiveReader.ReadUInt16()); } } }