internal MpqFile(ulong?hashedName, MpqStream mpqStream, MpqFileFlags flags, MpqLocale locale, bool leaveOpen) { _name = hashedName; _mpqStream = mpqStream ?? throw new ArgumentNullException(nameof(mpqStream)); _isStreamOwner = !leaveOpen; _flags = flags; _locale = locale; _compressionType = MpqCompressionType.ZLib; }
public bool TryOpenFile(string fileName, MpqLocale?locale, bool orderByBlockIndex, [NotNullWhen(true)] out MpqStream?stream) { var entry = GetMpqEntries(fileName, locale, orderByBlockIndex).FirstOrDefault(); if (entry is not null) { stream = new MpqStream(this, entry); return(true); } stream = null; return(false); }
/// <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; }
/// <summary> /// Repairs corrupted values in an <see cref="MpqArchive"/>. /// </summary> /// <param name="sourceStream">The stream containing the archive that needs to be repaired.</param> /// <param name="leaveOpen">If false, the given <paramref name="sourceStream"/> will be disposed at the end of this method.</param> /// <returns>A stream containing the repaired archive.</returns> public static MemoryStream Restore(Stream sourceStream, bool leaveOpen = false) { if (sourceStream is null) { throw new ArgumentNullException(nameof(sourceStream)); } if (!TryLocateMpqHeader(sourceStream, out var mpqHeader, out var headerOffset)) { throw new MpqParserException($"Unable to locate MPQ header."); } if (mpqHeader.MpqVersion != 0) { throw new MpqParserException($"MPQ format version {mpqHeader.MpqVersion} is not supported"); } var memoryStream = new MemoryStream(); using (var writer = new BinaryWriter(memoryStream, new UTF8Encoding(false, true), true)) { // Skip the MPQ header, since its contents will be calculated afterwards. writer.Seek((int)MpqHeader.Size, SeekOrigin.Current); var archiveSize = 0U; var hashTableEntries = mpqHeader.HashTableSize; var blockTableEntries = mpqHeader.BlockTableSize > MpqTable.MaxSize ? mpqHeader.IsArchiveAfterHeader() ? mpqHeader.BlockTablePosition < mpqHeader.HeaderOffset ? (mpqHeader.HeaderOffset - mpqHeader.BlockTablePosition) / MpqEntry.Size : (uint)(sourceStream.Length - sourceStream.Position) / MpqEntry.Size : throw new MpqParserException($"Unable to determine true BlockTable size.") : mpqHeader.BlockTableSize; var hashTable = (HashTable?)null; var blockTable = (BlockTable?)null; using (var reader = new BinaryReader(sourceStream, new UTF8Encoding(), true)) { // Load hash table sourceStream.Seek(mpqHeader.HashTablePosition, SeekOrigin.Begin); hashTable = new HashTable(reader, hashTableEntries); // Load entry table sourceStream.Seek(mpqHeader.BlockTablePosition, SeekOrigin.Begin); blockTable = new BlockTable(reader, blockTableEntries, (uint)headerOffset); // Load archive files for (var i = 0; i < blockTable.Size; i++) { var entry = blockTable[i]; if ((entry.Flags & MpqFileFlags.Garbage) == 0) { var size = entry.CompressedSize; var flags = entry.Flags; if (entry.IsEncrypted && entry.Flags.HasFlag(MpqFileFlags.BlockOffsetAdjustedKey)) { // To prevent encryption seed becoming incorrect, save file uncompressed and unencrypted. var pos = sourceStream.Position; using (var mpqStream = new MpqStream(entry, sourceStream, BlockSizeModifier << mpqHeader.BlockSize)) { mpqStream.CopyTo(memoryStream); } sourceStream.Position = pos + size; size = entry.FileSize; flags = entry.Flags & ~(MpqFileFlags.Compressed | MpqFileFlags.Encrypted | MpqFileFlags.BlockOffsetAdjustedKey); } else { sourceStream.Position = entry.FilePosition; writer.Write(reader.ReadBytes((int)size)); } blockTable[i] = new MpqEntry(null, 0, MpqHeader.Size + archiveSize, size, entry.FileSize, flags); archiveSize += size; } else { blockTable[i] = new MpqEntry(null, 0, MpqHeader.Size + archiveSize, 0, 0, 0); } } } // Fix invalid block indices and locales. for (var i = 0; i < hashTable.Size; i++) { var hash = hashTable[i]; if (!hash.IsEmpty && !hash.IsDeleted && hash.BlockIndex > BlockTable.MaxSize) { // TODO: don't force neutral locale if another MpqHash exists with the same Name1 and Name2, and that has the neutral locale hashTable[i] = new MpqHash(hash.Name, MpqLocale.Neutral /*hash.Locale & (MpqLocale)0x00000FFF*/, hash.BlockIndex & (BlockTable.MaxSize - 1), hash.Mask); } } hashTable.SerializeTo(memoryStream); blockTable.SerializeTo(memoryStream); writer.Seek(0, SeekOrigin.Begin); new MpqHeader(archiveSize, hashTableEntries, blockTableEntries, mpqHeader.BlockSize).WriteTo(writer); } if (!leaveOpen) { sourceStream.Dispose(); } memoryStream.Position = 0; return(memoryStream); }
public MpqOrphanedFile(MpqStream mpqStream, MpqFileFlags flags) : base(null, mpqStream, flags, MpqLocale.Neutral, false) { }
public bool TryOpenFile(string fileName, bool orderByBlockIndex, [NotNullWhen(true)] out MpqStream stream) { return(TryOpenFile(fileName, null, orderByBlockIndex, out stream)); }
public bool TryOpenFile(string fileName, MpqLocale?locale, [NotNullWhen(true)] out MpqStream stream) { return(TryOpenFile(fileName, locale, true, out stream)); }
/// <summary> /// Initializes a new instance of the <see cref="MpqKnownFile"/> class. /// </summary> internal MpqKnownFile(string fileName, MpqStream mpqStream, MpqFileFlags flags, MpqLocale locale, bool leaveOpen = false) : base(fileName.GetStringHash(), mpqStream, flags, locale, leaveOpen) { _fileName = fileName; }