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); }
/*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(); } }
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))); }
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)); }
// 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"); } } }
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); }
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; } } }
/// <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)); }
/// <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)); }
/// <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)."); } } }
/// <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) { }
/// <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); }
public static ulong GetHashedFileName(string fileName) { return(CombineNames(StormBuffer.HashString(fileName, 0x100), StormBuffer.HashString(fileName, 0x200))); }
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); }