/// <summary> /// Reads the entry from the VPK package. /// </summary> /// <param name="entry">Package entry.</param> /// <param name="progressReporter">Progress report callback.</param> /// <param name="validateCrc">If true, CRC32 will be calculated and verified for read data.</param> /// <returns>Output buffer.</returns> public async Task <Memory <byte> > ReadEntryAsync(PackageEntry entry, IProgress <int> progressReporter, bool validateCrc = true) { DecompressState decompressState = null; var parameters = new DecompressParameters(); parameters.DictSizeLog2 = 20; var blockReadBuffer = new Memory <byte>(new byte[MAX_ENTRY_CHUNK_SIZE]); var outputBuffer = new Memory <byte>(new byte[entry.SmallData.Length + entry.TotalLength]); var outputOffset = 0; if (entry.SmallData.Length > 0) { entry.SmallData.CopyTo(outputBuffer); outputOffset += entry.SmallData.Length; } if (entry.TotalCompressedLength < entry.TotalLength) { decompressState = Lzham.DecompressInit(parameters); } if (entry.TotalLength > 0) { Stream fs = null; ushort currentArchiveIndex = 0x7FFF; try { foreach (var chunk in entry.Chunks) { var streamOffset = chunk.Offset; if (currentArchiveIndex != chunk.ArchiveIndex) { currentArchiveIndex = chunk.ArchiveIndex; fs?.Close(); } if (chunk.ArchiveIndex != 0x7FFF) { if (currentArchiveIndex != chunk.ArchiveIndex) { currentArchiveIndex = chunk.ArchiveIndex; fs?.Close(); } if (!IsDirVPK) { throw new InvalidOperationException("Given VPK is not a _dir, but entry is referencing an external archive."); } var vpkDirectory = Path.GetDirectoryName(FileName); var vpkName = Path.GetFileName(FileName); foreach (var prefix in VPK_PREFIXES) { if (vpkName.StartsWith(prefix)) { vpkName = vpkName.Substring(prefix.Length); break; } } var fileName = Path.Combine(vpkDirectory, $"{vpkName}_{chunk.ArchiveIndex:D3}.vpk"); fs = new FileStream(fileName, FileMode.Open, FileAccess.Read); } else { fs = Reader.BaseStream; streamOffset += HeaderSize + TreeSize; } fs.Seek(streamOffset, SeekOrigin.Begin); var readBuffer = blockReadBuffer.Slice(0, (int)chunk.CompressedLength); var bytesRead = await fs.ReadAsync(readBuffer); if (bytesRead != chunk.CompressedLength) { throw new InvalidOperationException($"Attempted to read {chunk.CompressedLength} bytes, got {bytesRead.ToString()}."); } if (chunk.CompressedLength < chunk.Length) { var decompressedSpan = outputBuffer.Slice(outputOffset); var decompressResult = Lzham.DecompressMemory(parameters, readBuffer, ref decompressedSpan); if (decompressResult != DecompressStatus.Success) { throw new InvalidOperationException($"Error attempting to decompress: {decompressResult.ToString()}"); } outputOffset += (int)chunk.Length; } else { readBuffer.CopyTo(outputBuffer.Slice(outputOffset)); outputOffset += bytesRead; } if (progressReporter != null) { progressReporter.Report(outputOffset); } } } finally { if (currentArchiveIndex != 0x7FFF) { fs?.Close(); } } } if (validateCrc && entry.CRC32 != Crc32.Compute(outputBuffer)) { throw new InvalidDataException("CRC32 mismatch for read data."); } if (decompressState != null) { var deinitResult = Lzham.DecompressDeinit(decompressState); if (deinitResult >= DecompressStatus.FirstFailureCode) { throw new InvalidOperationException($"Error attempting to deinitialize compressor: {deinitResult.ToString()}"); } } return(outputBuffer); }
private void ReadEntries() { var typeEntries = new Dictionary <string, List <PackageEntry> >(); // Types while (true) { var typeName = Reader.ReadNullTermString(Encoding.UTF8); if (string.IsNullOrEmpty(typeName)) { break; } var entries = new List <PackageEntry>(); // Directories while (true) { var directoryName = Reader.ReadNullTermString(Encoding.UTF8); if (directoryName?.Length == 0) { break; } // Files while (true) { var fileName = Reader.ReadNullTermString(Encoding.UTF8); if (fileName?.Length == 0) { break; } var entry = new PackageEntry { FileName = fileName, DirectoryName = directoryName, TypeName = typeName, CRC32 = Reader.ReadUInt32(), SmallData = new byte[Reader.ReadUInt16()], }; var entryChunks = new List <PackageEntryChunk>(); PackageEntryChunk chunk = null; var archiveIndex = Reader.ReadUInt16(); while (archiveIndex != 0xFFFF) { chunk = new PackageEntryChunk() { ArchiveIndex = archiveIndex, Unknown1 = Reader.ReadBytes(6), Offset = Reader.ReadUInt32(), Unknown2 = Reader.ReadBytes(4), CompressedLength = Reader.ReadUInt32(), Unknown3 = Reader.ReadBytes(4), Length = Reader.ReadUInt32(), Unknown4 = Reader.ReadBytes(4), }; entryChunks.Add(chunk); archiveIndex = Reader.ReadUInt16(); } entry.Chunks = entryChunks.ToArray(); if (entry.SmallData.Length > 0) { Reader.Read(entry.SmallData, 0, entry.SmallData.Length); } entries.Add(entry); } } typeEntries.Add(typeName, entries); } Entries = typeEntries; }
/// <summary> /// Reads the entry from the VPK package. /// </summary> /// <param name="entry">Package entry.</param> /// <param name="validateCrc">If true, CRC32 will be calculated and verified for read data.</param> /// <returns>Output buffer.</returns> public Task <Memory <byte> > ReadEntryAsync(PackageEntry entry, bool validateCrc = true) { return(ReadEntryAsync(entry, null, validateCrc)); }