Exemple #1
0
        private bool TryGetHashEntry(string filename, out MpqHash hash)
        {
            var index = StormBuffer.HashString(filename, 0);

            index &= _mpqHeader.HashTableSize - 1;
            var name = MpqHash.GetHashedFileName(filename);

            for (var i = index; i < _hashTable.Size; ++i)
            {
                hash = _hashTable[i];
                if (hash.Name == name)
                {
                    return(true);
                }
            }

            for (uint i = 0; i < index; ++i)
            {
                hash = _hashTable[i];
                if (hash.Name == name)
                {
                    return(true);
                }
            }

            hash = default;
            return(false);
        }
Exemple #2
0
        /*public void WriteToStream( Stream stream )
         * {
         *  WriteToStream( new BinaryWriter( stream ) );
         *  //WriteToStream( new StreamWriter( stream ) );
         * }*/

        public void SerializeTo(BinaryWriter writer, bool dispose = true)
        {
            var stream = _entry.IsCompressed ? _compressedStream : _baseStream;

            if (_entry.IsEncrypted)
            {
                var blockPosCount  = (uint)(((int)_baseStream.Length + _blockSize - 1) / _blockSize) + 1;
                var blockPositions = new int[blockPosCount];
                if (_entry.IsCompressed)
                {
                    for (var blockIndex = 0; blockIndex < blockPosCount; blockIndex++)
                    {
                        using (var br = new BinaryReader(stream, new System.Text.UTF8Encoding(), true))
                        {
                            for (var i = 0; i < blockPosCount; i++)
                            {
                                blockPositions[i] = (int)br.ReadUInt32();
                            }
                        }

                        stream.Seek(0, SeekOrigin.Begin);
                    }
                }
                else
                {
                    // untested: encryption for uncompressed files
                    for (var blockIndex = 1; blockIndex < blockPosCount; blockIndex++)
                    {
                        blockPositions[blockIndex - 1] = _blockSize * blockIndex;
                    }

                    blockPositions[blockPosCount - 1] = (int)_baseStream.Length;
                }

                var currentOffset = 0;
                for (var blockIndex = _entry.IsCompressed ? 0 : 1; blockIndex < blockPosCount; blockIndex++)
                {
                    var toWrite = (int)blockPositions[blockIndex] - currentOffset;

                    var data = StormBuffer.EncryptStream(stream, (uint)(_entry.EncryptionSeed + blockIndex - 1), currentOffset, toWrite);
                    for (var b = 0; b < data.Length; b++)
                    {
                        writer.Write(data[b]);
                    }

                    currentOffset += toWrite;
                }
            }
            else
            {
                WriteStreamToWriter(stream, writer);
            }

            if (dispose)
            {
                Dispose();
            }
        }
Exemple #3
0
        public static ulong GetHashedFileName(string fileName)
        {
            if (fileName.Any(c => c >= 0x200))
            {
                throw new ArgumentException($"One or more of the characters in the input string have a numerical value of 0x200 or larger.", nameof(fileName));
            }

            return(CombineNames(StormBuffer.HashString(fileName, 0x100), StormBuffer.HashString(fileName, 0x200)));
        }
Exemple #4
0
        public static uint GetIndex(string path)
        {
            if (path.Any(c => c >= 0x200))
            {
                throw new ArgumentException($"One or more of the characters in the input string have a numerical value of 0x200 or larger.", nameof(path));
            }

            return(StormBuffer.HashString(path, 0));
        }
Exemple #5
0
        // Compressed files start with an array of offsets to make seeking possible
        private void LoadBlockPositions()
        {
            var blockposcount = (int)((_entry.FileSize + _blockSize - 1) / _blockSize) + 1;

            // Files with metadata have an extra block containing block checksums
            if ((_entry.Flags & MpqFileFlags.FileHasMetadata) != 0)
            {
                blockposcount++;
            }

            _blockPositions = new uint[blockposcount];

            lock (_stream)
            {
                _stream.Seek(_entry.FilePos, 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;
             */

            if (_entry.IsEncrypted)
            {
                if (_entry.EncryptionSeed == 0)
                {
                    // This should only happen when the file name is not known.
                    _entry.EncryptionSeed = StormBuffer.DetectFileSeed(_blockPositions[0], _blockPositions[1], blockpossize) + 1;
                    if (_entry.EncryptionSeed == 1)
                    {
                        throw new MpqParserException("Unable to determine encyption seed");
                    }
                }

                StormBuffer.DecryptBlock(_blockPositions, _entry.EncryptionSeed - 1);

                if (_blockPositions[0] != blockpossize)
                {
                    throw new MpqParserException("Decryption failed");
                }

                if (_blockPositions[1] > _blockSize + blockpossize)
                {
                    throw new MpqParserException("Decryption failed");
                }
            }
        }
Exemple #6
0
        private byte[] LoadBlock(int blockIndex, int expectedLength)
        {
            uint offset;
            int  toread;
            uint encryptionseed;

            if (_entry.IsCompressed)
            {
                offset = _blockPositions[blockIndex];
                toread = (int)(_blockPositions[blockIndex + 1] - offset);
            }
            else
            {
                offset = (uint)(blockIndex * _blockSize);
                toread = expectedLength;
            }

            offset += _entry.FilePos;

            var data = new byte[toread];

            lock (_stream)
            {
                _stream.Seek(offset, SeekOrigin.Begin);
                var read = _stream.Read(data, 0, toread);
                if (read != toread)
                {
                    throw new MpqParserException("Insufficient data or invalid data length");
                }
            }

            if (_entry.IsEncrypted && _entry.FileSize > 3)
            {
                if (_entry.EncryptionSeed == 0)
                {
                    throw new MpqParserException("Unable to determine encryption key");
                }

                encryptionseed = (uint)(blockIndex + _entry.EncryptionSeed);
                StormBuffer.DecryptBlock(data, encryptionseed);
            }

            if (_entry.IsCompressed && (toread != expectedLength))
            {
                data = (_entry.Flags & MpqFileFlags.CompressedMulti) != 0
                    ? DecompressMulti(data, expectedLength)
                    : PKDecompress(new MemoryStream(data), expectedLength);
            }

            return(data);
        }
Exemple #7
0
        private IEnumerable <MpqHash> GetHashEntries(string filename)
        {
            if (!StormBuffer.TryGetHashString(filename, 0, out var index))
            {
                yield break;
            }

            index &= _mpqHeader.HashTableSize - 1;
            var name = MpqHash.GetHashedFileName(filename);

            var foundAnyHash = false;

            for (var i = index; i < _hashTable.Size; ++i)
            {
                var hash = _hashTable[i];
                if (hash.Name == name)
                {
                    yield return(hash);

                    foundAnyHash = true;
                }
                else if (hash.IsEmpty && foundAnyHash)
                {
                    yield break;
                }
            }

            for (uint i = 0; i < index; ++i)
            {
                var hash = _hashTable[i];
                if (hash.Name == name)
                {
                    yield return(hash);

                    foundAnyHash = true;
                }
                else if (hash.IsEmpty && foundAnyHash)
                {
                    yield break;
                }
            }
        }
Exemple #8
0
 /// <summary>
 /// Decrypts the contents of the <see cref="MpqTable"/>.
 /// </summary>
 /// <param name="data">The encrypted entries in the table.</param>
 internal void Decrypt(byte[] data)
 {
     StormBuffer.DecryptBlock(data, StormBuffer.HashString(Key, 0x300));
 }
Exemple #9
0
 /// <summary>
 /// Encrypts the contents of the <see cref="MpqTable"/>.
 /// </summary>
 /// <param name="data">The unencrypted entries in the table.</param>
 public void Encrypt(byte[] data)
 {
     StormBuffer.EncryptBlock(data, StormBuffer.HashString(Key, 0x300));
 }
Exemple #10
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).");
                        }
                    }
                }
Exemple #11
0
 /// <summary>
 /// Initializes a new instance of the <see cref="MpqHash"/> struct.
 /// </summary>
 /// <param name="fileName"></param>
 /// <param name="mask"></param>
 /// <param name="locale"></param>
 /// <param name="blockIndex"></param>
 public MpqHash(string fileName, uint mask, MpqLocale locale, uint blockIndex)
     : this(StormBuffer.HashString(fileName, 0x100), StormBuffer.HashString(fileName, 0x200), locale, blockIndex, mask)
 {
 }
Exemple #12
0
 /// <summary>
 ///
 /// </summary>
 /// <param name="path"></param>
 /// <param name="mask"></param>
 /// <returns></returns>
 public static uint GetIndex(string path, uint mask)
 {
     return(StormBuffer.HashString(path, 0) & mask);
 }
Exemple #13
0
 public static ulong GetHashedFileName(string fileName)
 {
     return(CombineNames(StormBuffer.HashString(fileName, 0x100), StormBuffer.HashString(fileName, 0x200)));
 }
Exemple #14
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);
        }