Exemplo n.º 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));
        }
Exemplo n.º 2
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));
        }
Exemplo n.º 3
0
        // TODO: Filter files based on language and platform
        /// <summary>
        /// Extract the file at <paramref name="filePath"/> from the archive.
        /// </summary>
        /// <returns>The file as a byte array, or null if the file could not be found.</returns>
        /// <param name="filePath">Path to the file in the archive.</param>
        public byte[] ExtractFile(string filePath)
        {
            if (this.IsDisposed)
            {
                throw new ObjectDisposedException(ToString(), "Cannot use a disposed archive.");
            }

            // Reset all positions to be safe
            this.ArchiveReader.BaseStream.Position = 0;

            HashTableEntry fileHashEntry = this.ArchiveHashTable.FindEntry(filePath);

            if (fileHashEntry == null)
            {
                return(null);
            }

            BlockTableEntry fileBlockEntry = this.ArchiveBlockTable.GetEntry((int)fileHashEntry.GetBlockEntryIndex());

            // Drop out if the file is not actually a file
            if (!fileBlockEntry.HasData())
            {
                return(null);
            }

            // Seek to the beginning of the file's sectors
            long adjustedBlockOffset;

            if (this.Header.GetFormat() >= MPQFormat.ExtendedV1 && RequiresExtendedFormat())
            {
                ushort upperOffsetBits = this.ExtendedBlockTable[(int)fileHashEntry.GetBlockEntryIndex()];
                adjustedBlockOffset = (long)fileBlockEntry.GetExtendedBlockOffset(upperOffsetBits);
            }
            else
            {
                adjustedBlockOffset = fileBlockEntry.GetBlockOffset();
            }
            this.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));
        }
Exemplo n.º 4
0
        /// <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));
        }