Example #1
0
        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));
        }
Example #2
0
        /// <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);
        }
Example #3
0
        /// <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;
        }
Example #4
0
        /// <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));
        }
Example #5
0
        /// <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());
                    }
                }
            }
        }