예제 #1
0
        /// <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;
        }
예제 #2
0
        /// <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).");
                        }
                    }
                }
예제 #3
0
        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);
        }
예제 #4
0
        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);
        }