Start fixing PKZIP deserializing

This does a forward-reading approach on the archive instead of the "proper" way of reading from the end. This change introduces potential issues, as both "deleted" entries and normal ones will be included. It also makes it more difficult to determine if the archive is ZIP64 or not. But it is far more stable than the read-from-the-end approach of the old code.
This commit is contained in:
Matt Nadareski
2025-08-27 12:54:36 -04:00
parent e163302522
commit a93a46d6c0
2 changed files with 362 additions and 530 deletions

View File

@@ -1,12 +1,11 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using SabreTools.IO.Extensions;
using SabreTools.Matching;
using SabreTools.Models.PKZIP;
using static SabreTools.Models.PKZIP.Constants;
// TODO: Finish replacing ReadType
namespace SabreTools.Serialization.Deserializers
{
public class PKZIP : BaseBinaryDeserializer<Archive>
@@ -28,258 +27,199 @@ namespace SabreTools.Serialization.Deserializers
var archive = new Archive();
#region End of Central Directory Record
// Find the end of central directory record
long eocdrOffset = SearchForEndOfCentralDirectoryRecord(data);
if (eocdrOffset < initialOffset || eocdrOffset >= data.Length)
return null;
// Seek to the end of central directory record
data.Seek(eocdrOffset, SeekOrigin.Begin);
// Read the end of central directory record
var eocdr = ParseEndOfCentralDirectoryRecord(data);
if (eocdr == null)
return null;
// Assign the end of central directory record
archive.EndOfCentralDirectoryRecord = eocdr;
#endregion
#region ZIP64 End of Central Directory Locator and Record
// Set a flag for ZIP64 not found by default
bool zip64 = false;
// Process ZIP64 if any fields are set to max value
if (eocdr.DiskNumber == 0xFFFF
|| eocdr.StartDiskNumber == 0xFFFF
|| eocdr.TotalEntriesOnDisk == 0xFFFF
|| eocdr.TotalEntries == 0xFFFF
|| eocdr.CentralDirectorySize == 0xFFFFFFFF
|| eocdr.CentralDirectoryOffset == 0xFFFFFFFF)
{
// Set the ZIP64 flag
zip64 = true;
// Find the ZIP64 end of central directory locator
long eocdlOffset = SearchForZIP64EndOfCentralDirectoryLocator(data);
if (eocdlOffset < initialOffset || eocdlOffset >= data.Length)
return null;
// Seek to the ZIP64 end of central directory locator
data.Seek(eocdlOffset, SeekOrigin.Begin);
// Read the ZIP64 end of central directory locator
var eocdl64 = data.ReadType<EndOfCentralDirectoryLocator64>();
if (eocdl64 == null)
return null;
// Assign the ZIP64 end of central directory record
archive.ZIP64EndOfCentralDirectoryLocator = eocdl64;
// Try to get the ZIP64 end of central directory record offset
if ((long)eocdl64.CentralDirectoryOffset < 0 || initialOffset + (long)eocdl64.CentralDirectoryOffset >= data.Length)
return null;
// Seek to the ZIP64 end of central directory record
data.Seek(initialOffset + (long)eocdl64.CentralDirectoryOffset, SeekOrigin.Begin);
// Read the ZIP64 end of central directory record
var eocdr64 = ParseEndOfCentralDirectoryRecord64(data);
if (eocdr64 == null)
return null;
// Assign the ZIP64 end of central directory record
archive.ZIP64EndOfCentralDirectoryRecord = eocdr64;
}
#endregion
#region Central Directory Records
// Try to get the central directory record offset
long cdrOffset, cdrSize;
if (zip64 && archive.ZIP64EndOfCentralDirectoryRecord != null)
{
cdrOffset = initialOffset + (long)archive.ZIP64EndOfCentralDirectoryRecord.CentralDirectoryOffset;
cdrSize = (long)archive.ZIP64EndOfCentralDirectoryRecord.CentralDirectorySize;
}
else if (archive.EndOfCentralDirectoryRecord != null)
{
cdrOffset = initialOffset + archive.EndOfCentralDirectoryRecord.CentralDirectoryOffset;
cdrSize = archive.EndOfCentralDirectoryRecord.CentralDirectorySize;
}
else
{
return null;
}
// Try to get the central directory record offset
if (cdrOffset < initialOffset || cdrOffset >= data.Length)
return null;
// Seek to the first central directory record
data.Seek(cdrOffset, SeekOrigin.Begin);
// Cache the current offset
long currentOffset = data.Position;
// Read the central directory records
var cdrs = new List<CentralDirectoryFileHeader>();
while (data.Position < currentOffset + cdrSize)
{
// Read the central directory record
var cdr = ParseCentralDirectoryFileHeader(data);
if (cdr == null)
return null;
// Add the central directory record
cdrs.Add(cdr);
}
// Assign the central directory records
archive.CentralDirectoryHeaders = [.. cdrs];
#endregion
// TODO: Handle digital signature -- immediately following central directory records
#region Archive Extra Data Record
// Find the archive extra data record
long aedrOffset = SearchForArchiveExtraDataRecord(data, cdrOffset);
if (aedrOffset >= 0 && aedrOffset < data.Length)
{
// Seek to the archive extra data record
data.Seek(aedrOffset, SeekOrigin.Begin);
// Read the archive extra data record
var aedr = ParseArchiveExtraDataRecord(data);
if (aedr == null)
return null;
// Assign the archive extra data record
archive.ArchiveExtraDataRecord = aedr;
}
#endregion
#region Local File
// Setup all of the collections
var localFileHeaders = new List<LocalFileHeader>();
var encryptionHeaders = new List<byte[]>();
var fileData = new List<byte[]>(); // TODO: Should this data be read here?
var dataDescriptors = new List<DataDescriptor>();
var zip64DataDescriptors = new List<DataDescriptor64>();
var cdrs = new List<CentralDirectoryFileHeader>();
// Read the local file headers
for (int i = 0; i < archive.CentralDirectoryHeaders.Length; i++)
// Flag if we have a ZIP64 archive
bool? zip64 = null;
// Read all blocks
do
{
var header = archive.CentralDirectoryHeaders[i];
// Read the signature
long beforeSignature = data.Position;
uint signature = data.ReadUInt32LittleEndian();
data.Seek(beforeSignature, SeekOrigin.Begin);
// Get the local file header offset
long headerOffset = header.RelativeOffsetOfLocalHeader;
if (headerOffset == 0xFFFFFFFF && header.ExtraField != null)
// Switch based on the signature found
bool validBlock = false;
switch (signature)
{
// TODO: Parse into a proper structure instead of this
byte[] extraData = header.ExtraField;
if (BitConverter.ToUInt16(extraData, 0) == 0x0001)
headerOffset = BitConverter.ToInt64(extraData, 4);
}
if (headerOffset < 0 || initialOffset + headerOffset >= data.Length)
return null;
// Seek to the local file header
data.Seek(initialOffset + headerOffset, SeekOrigin.Begin);
// Try to parse the local header
var localFileHeader = ParseLocalFileHeader(data);
if (localFileHeader == null)
{
// Add a placeholder null item
localFileHeaders.Add(new LocalFileHeader());
encryptionHeaders.Add([]);
fileData.Add([]);
dataDescriptors.Add(new DataDescriptor());
zip64DataDescriptors.Add(new DataDescriptor64());
continue;
}
// Add the local file header
localFileHeaders.Add(localFileHeader);
// Only read the encryption header if necessary
#if NET20 || NET35
if ((header.Flags & GeneralPurposeBitFlags.FileEncrypted) != 0)
#else
if (header.Flags.HasFlag(GeneralPurposeBitFlags.FileEncrypted))
#endif
{
// Try to read the encryption header data -- TODO: Verify amount to read
byte[] encryptionHeader = data.ReadBytes(12);
if (encryptionHeader.Length != 12)
return null;
// Add the encryption header
encryptionHeaders.Add(encryptionHeader);
}
else
{
// Add the empty encryption header
encryptionHeaders.Add([]);
}
// Try to read the file data
byte[] fileDatum = data.ReadBytes((int)header.CompressedSize);
if (fileDatum.Length < header.CompressedSize)
return null;
// Add the file data
fileData.Add(fileDatum);
// Only read the data descriptor if necessary
#if NET20 || NET35
if ((header.Flags & GeneralPurposeBitFlags.NoCRC) != 0)
#else
if (header.Flags.HasFlag(GeneralPurposeBitFlags.NoCRC))
#endif
{
// Select the data descriptor that is being used
if (zip64)
{
// Try to parse the data descriptor
var dataDescriptor64 = ParseDataDescriptor64(data);
if (dataDescriptor64 == null)
// Central Directory File Header
case CentralDirectoryFileHeaderSignature:
var cdr = ParseCentralDirectoryFileHeader(data);
if (cdr == null)
return null;
// Add the data descriptor
dataDescriptors.Add(new DataDescriptor());
zip64DataDescriptors.Add(dataDescriptor64);
}
else
{
// Try to parse the data descriptor
var dataDescriptor = ParseDataDescriptor(data);
if (dataDescriptor == null)
// Add the central directory record
validBlock = true;
cdrs.Add(cdr);
break;
// Local File
case LocalFileHeaderSignature:
#region Local File Header
var localFileHeader = ParseLocalFileHeader(data);
if (localFileHeader == null)
break;
// Add the local file header
localFileHeaders.Add(localFileHeader);
#endregion
#region Encryption Header
// Only read the encryption header if necessary
#if NET20 || NET35
if ((localFileHeader.Flags & GeneralPurposeBitFlags.FileEncrypted) != 0)
#else
if (localFileHeader.Flags.HasFlag(GeneralPurposeBitFlags.FileEncrypted))
#endif
{
// Try to read the encryption header data -- TODO: Verify amount to read
byte[] encryptionHeader = data.ReadBytes(12);
if (encryptionHeader.Length != 12)
break;
// Add the encryption header
encryptionHeaders.Add(encryptionHeader);
}
else
{
// Add the empty encryption header
encryptionHeaders.Add([]);
}
#endregion
#region File Data
// Try to read the file data
byte[] fileDatum = data.ReadBytes((int)localFileHeader.CompressedSize);
if (fileDatum.Length < localFileHeader.CompressedSize)
break;
// Add the file data
validBlock = true;
fileData.Add(fileDatum);
#endregion
break;
// TODO: Implement
case DigitalSignatureSignature:
break;
// End of Central Directory Record
case EndOfCentralDirectoryRecordSignature:
var eocdr = ParseEndOfCentralDirectoryRecord(data);
if (eocdr == null)
return null;
// Add the data descriptor
dataDescriptors.Add(dataDescriptor);
zip64DataDescriptors.Add(new DataDescriptor64());
}
// Assign the end of central directory record
validBlock = true;
archive.EndOfCentralDirectoryRecord = eocdr;
break;
// ZIP64 End of Central Directory
case EndOfCentralDirectoryRecord64Signature:
var eocdr64 = ParseEndOfCentralDirectoryRecord64(data);
if (eocdr64 == null)
return null;
// Assign the ZIP64 end of central directory record
zip64 = true;
validBlock = true;
archive.ZIP64EndOfCentralDirectoryRecord = eocdr64;
break;
// ZIP64 End of Central Directory Locator
case EndOfCentralDirectoryLocator64Signature:
var eocdl64 = ParseEndOfCentralDirectoryLocator64(data);
if (eocdl64 == null)
return null;
// Assign the ZIP64 end of central directory record
zip64 = true;
validBlock = true;
archive.ZIP64EndOfCentralDirectoryLocator = eocdl64;
break;
// Archive Extra Data Record
case ArchiveExtraDataRecordSignature:
var aedr = ParseArchiveExtraDataRecord(data);
if (aedr == null)
return null;
// Assign the archive extra data record
validBlock = true;
archive.ArchiveExtraDataRecord = aedr;
break;
// Data Descriptor -- Usually follows a local file header
case DataDescriptorSignature:
if (zip64 == null)
{
if (data.Position + 16 == data.Length)
{
zip64 = false;
}
else if (data.Position + 24 == data.Length)
{
zip64 = true;
}
else
{
long beforeCheck = data.Position;
data.Seek(16, SeekOrigin.Current);
byte[] nextBlock = data.ReadBytes(2);
data.Seek(beforeCheck, SeekOrigin.Begin);
zip64 = !nextBlock.EqualsExactly([0x50, 0x4B]);
}
}
if (zip64 == true)
{
// Try to parse the data descriptor
var dataDescriptor64 = ParseDataDescriptor64(data);
if (dataDescriptor64 == null)
break;
// Add the data descriptor
validBlock = true;
dataDescriptors.Add(new DataDescriptor());
zip64DataDescriptors.Add(dataDescriptor64);
}
else
{
// Try to parse the data descriptor
var dataDescriptor = ParseDataDescriptor(data);
if (dataDescriptor == null)
break;
// Add the data descriptor
validBlock = true;
dataDescriptors.Add(dataDescriptor);
zip64DataDescriptors.Add(new DataDescriptor64());
}
break;
}
else
{
// Add the null data descriptor
dataDescriptors.Add(new DataDescriptor());
zip64DataDescriptors.Add(new DataDescriptor64());
}
}
// If there was an invalid block
if (!validBlock)
break;
} while (data.Position < data.Length);
// Assign the local file headers
archive.LocalFileHeaders = [.. localFileHeaders];
@@ -294,9 +234,8 @@ namespace SabreTools.Serialization.Deserializers
archive.DataDescriptors = [.. dataDescriptors];
archive.ZIP64DataDescriptors = [.. zip64DataDescriptors];
#endregion
// TODO: Handle archive decryption header
// Assign the central directory records
archive.CentralDirectoryHeaders = [.. cdrs];
return archive;
}
@@ -308,161 +247,29 @@ namespace SabreTools.Serialization.Deserializers
}
/// <summary>
/// Search for the end of central directory record
/// Parse a Stream into an archive extra data record
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Position of the end of central directory record, -1 on error</returns>
public static long SearchForEndOfCentralDirectoryRecord(Stream data)
/// <returns>Filled archive extra data record on success, null on error</returns>
public static ArchiveExtraDataRecord? ParseArchiveExtraDataRecord(Stream data)
{
// Cache the current offset
long current = data.Position;
var obj = new ArchiveExtraDataRecord();
// Seek to the minimum size of the record from the end
data.Seek(-22, SeekOrigin.End);
// Attempt to find the end of central directory signature
while (data.Position > 0)
{
// Read the potential signature
uint possibleSignature = data.ReadUInt32LittleEndian();
if (possibleSignature == EndOfCentralDirectoryRecordSignature)
{
long signaturePosition = data.Position - 4;
data.Seek(current, SeekOrigin.Begin);
return signaturePosition;
}
// If we find any other signature
switch (possibleSignature)
{
case ArchiveExtraDataRecordSignature:
case CentralDirectoryFileHeaderSignature:
case DataDescriptorSignature:
case DigitalSignatureSignature:
case EndOfCentralDirectoryLocator64Signature:
case EndOfCentralDirectoryRecord64Signature:
case LocalFileHeaderSignature:
data.Seek(current, SeekOrigin.Begin);
return -1;
}
// Seek backward 5 bytes, if possible
data.Seek(-5, SeekOrigin.Current);
}
// No signature was found
data.Seek(current, SeekOrigin.Begin);
return -1;
}
/// <summary>
/// Parse a Stream into an end of central directory record
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled end of central directory record on success, null on error</returns>
public static EndOfCentralDirectoryRecord? ParseEndOfCentralDirectoryRecord(Stream data)
{
var record = new EndOfCentralDirectoryRecord();
record.Signature = data.ReadUInt32LittleEndian();
if (record.Signature != EndOfCentralDirectoryRecordSignature)
obj.Signature = data.ReadUInt32LittleEndian();
if (obj.Signature != ArchiveExtraDataRecordSignature)
return null;
record.DiskNumber = data.ReadUInt16LittleEndian();
record.StartDiskNumber = data.ReadUInt16LittleEndian();
record.TotalEntriesOnDisk = data.ReadUInt16LittleEndian();
record.TotalEntries = data.ReadUInt16LittleEndian();
record.CentralDirectorySize = data.ReadUInt32LittleEndian();
record.CentralDirectoryOffset = data.ReadUInt32LittleEndian();
record.FileCommentLength = data.ReadUInt16LittleEndian();
if (record.FileCommentLength > 0 && data.Position + record.FileCommentLength <= data.Length)
obj.ExtraFieldLength = data.ReadUInt32LittleEndian();
if (obj.ExtraFieldLength > 0 && data.Position + obj.ExtraFieldLength <= data.Length)
{
byte[] commentBytes = data.ReadBytes(record.FileCommentLength);
if (commentBytes.Length != record.FileCommentLength)
byte[] extraBytes = data.ReadBytes((int)obj.ExtraFieldLength);
if (extraBytes.Length != obj.ExtraFieldLength)
return null;
record.FileComment = Encoding.ASCII.GetString(commentBytes);
obj.ExtraFieldData = extraBytes;
}
return record;
}
/// <summary>
/// Search for the ZIP64 end of central directory locator
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Position of the ZIP64 end of central directory locator, -1 on error</returns>
public static long SearchForZIP64EndOfCentralDirectoryLocator(Stream data)
{
// Cache the current offset
long current = data.Position;
// Seek to the minimum size of the record from the minimum start
// of theend of central directory record
data.Seek(-22 + -20, SeekOrigin.Current);
// Attempt to find the ZIP64 end of central directory locator signature
while (data.Position > 0)
{
// Read the potential signature
uint possibleSignature = data.ReadUInt32LittleEndian();
if (possibleSignature == EndOfCentralDirectoryLocator64Signature)
{
long signaturePosition = data.Position - 4;
data.Seek(current, SeekOrigin.Begin);
return signaturePosition;
}
// If we find any other signature
switch (possibleSignature)
{
case ArchiveExtraDataRecordSignature:
case CentralDirectoryFileHeaderSignature:
case DataDescriptorSignature:
case DigitalSignatureSignature:
case EndOfCentralDirectoryRecordSignature:
case EndOfCentralDirectoryRecord64Signature:
case LocalFileHeaderSignature:
data.Seek(current, SeekOrigin.Begin);
return -1;
}
// Seek backward 5 bytes, if possible
data.Seek(-5, SeekOrigin.Current);
}
// No signature was found
data.Seek(current, SeekOrigin.Begin);
return -1;
}
/// <summary>
/// Parse a Stream into a ZIP64 end of central directory record
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled ZIP64 end of central directory record on success, null on error</returns>
public static EndOfCentralDirectoryRecord64? ParseEndOfCentralDirectoryRecord64(Stream data)
{
var record = new EndOfCentralDirectoryRecord64();
record.Signature = data.ReadUInt32LittleEndian();
if (record.Signature != EndOfCentralDirectoryRecord64Signature)
return null;
record.DirectoryRecordSize = data.ReadUInt64LittleEndian();
record.HostSystem = (HostSystem)data.ReadByteValue();
record.VersionMadeBy = data.ReadByteValue();
record.VersionNeededToExtract = data.ReadUInt16LittleEndian();
record.DiskNumber = data.ReadUInt32LittleEndian();
record.StartDiskNumber = data.ReadUInt32LittleEndian();
record.TotalEntriesOnDisk = data.ReadUInt64LittleEndian();
record.TotalEntries = data.ReadUInt64LittleEndian();
record.CentralDirectorySize = data.ReadUInt64LittleEndian();
record.CentralDirectoryOffset = data.ReadUInt64LittleEndian();
// TODO: Handle the ExtensibleDataSector -- How to detect if exists?
return record;
return obj;
}
/// <summary>
@@ -525,80 +332,129 @@ namespace SabreTools.Serialization.Deserializers
}
/// <summary>
/// Search for the archive extra data record
/// Parse a Stream into a data descriptor
/// </summary>
/// <param name="data">Stream to parse</param>
/// <param name="centralDirectoryoffset">Offset to the first central directory record</param>
/// <returns>Position of the archive extra data record, -1 on error</returns>
public static long SearchForArchiveExtraDataRecord(Stream data, long centralDirectoryoffset)
/// <returns>Filled data descriptor on success, null on error</returns>
public static DataDescriptor? ParseDataDescriptor(Stream data)
{
// Cache the current offset
long current = data.Position;
var obj = new DataDescriptor();
// Seek to the minimum size of the record from the central directory
data.Seek(centralDirectoryoffset - 8, SeekOrigin.Begin);
// Signatures are expected but not required
obj.Signature = data.ReadUInt32LittleEndian();
if (obj.Signature != DataDescriptorSignature)
data.Seek(-4, SeekOrigin.Current);
// Attempt to find the end of central directory signature
while (data.Position > 0)
{
// Read the potential signature
uint possibleSignature = data.ReadUInt32LittleEndian();
if (possibleSignature == ArchiveExtraDataRecordSignature)
{
long signaturePosition = data.Position - 4;
data.Seek(current, SeekOrigin.Begin);
return signaturePosition;
}
obj.CRC32 = data.ReadUInt32LittleEndian();
obj.CompressedSize = data.ReadUInt32LittleEndian();
obj.UncompressedSize = data.ReadUInt32LittleEndian();
// If we find any other signature
switch (possibleSignature)
{
case CentralDirectoryFileHeaderSignature:
case DataDescriptorSignature:
case DigitalSignatureSignature:
case EndOfCentralDirectoryLocator64Signature:
case EndOfCentralDirectoryRecordSignature:
case EndOfCentralDirectoryRecord64Signature:
case LocalFileHeaderSignature:
data.Seek(current, SeekOrigin.Begin);
return -1;
}
// Seek backward 5 bytes, if possible
data.Seek(-5, SeekOrigin.Current);
}
// No signature was found
data.Seek(current, SeekOrigin.Begin);
return -1;
return obj;
}
/// <summary>
/// Parse a Stream into an archive extra data record
/// Parse a Stream into a ZIP64 data descriptor
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled archive extra data record on success, null on error</returns>
public static ArchiveExtraDataRecord? ParseArchiveExtraDataRecord(Stream data)
/// <returns>Filled ZIP64 data descriptor on success, null on error</returns>
public static DataDescriptor64? ParseDataDescriptor64(Stream data)
{
var record = new ArchiveExtraDataRecord();
var obj = new DataDescriptor64();
// Signatures are expected but not required
obj.Signature = data.ReadUInt32LittleEndian();
if (obj.Signature != DataDescriptorSignature)
data.Seek(-4, SeekOrigin.Current);
obj.CRC32 = data.ReadUInt32LittleEndian();
obj.CompressedSize = data.ReadUInt64LittleEndian();
obj.UncompressedSize = data.ReadUInt64LittleEndian();
return obj;
}
/// <summary>
/// Parse a Stream into a ZIP64 end of central directory locator
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled ZIP64 end of central directory locator on success, null on error</returns>
public static EndOfCentralDirectoryLocator64? ParseEndOfCentralDirectoryLocator64(Stream data)
{
var obj = new EndOfCentralDirectoryLocator64();
// Signatures are expected but not required
obj.Signature = data.ReadUInt32LittleEndian();
if (obj.Signature != EndOfCentralDirectoryLocator64Signature)
data.Seek(-4, SeekOrigin.Current);
obj.StartDiskNumber = data.ReadUInt32LittleEndian();
obj.CentralDirectoryOffset = data.ReadUInt64LittleEndian();
obj.TotalDisks = data.ReadUInt32LittleEndian();
return obj;
}
/// <summary>
/// Parse a Stream into an end of central directory record
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled end of central directory record on success, null on error</returns>
public static EndOfCentralDirectoryRecord? ParseEndOfCentralDirectoryRecord(Stream data)
{
var record = new EndOfCentralDirectoryRecord();
record.Signature = data.ReadUInt32LittleEndian();
if (record.Signature != ArchiveExtraDataRecordSignature)
if (record.Signature != EndOfCentralDirectoryRecordSignature)
return null;
record.ExtraFieldLength = data.ReadUInt32LittleEndian();
if (record.ExtraFieldLength > 0 && data.Position + record.ExtraFieldLength <= data.Length)
record.DiskNumber = data.ReadUInt16LittleEndian();
record.StartDiskNumber = data.ReadUInt16LittleEndian();
record.TotalEntriesOnDisk = data.ReadUInt16LittleEndian();
record.TotalEntries = data.ReadUInt16LittleEndian();
record.CentralDirectorySize = data.ReadUInt32LittleEndian();
record.CentralDirectoryOffset = data.ReadUInt32LittleEndian();
record.FileCommentLength = data.ReadUInt16LittleEndian();
if (record.FileCommentLength > 0 && data.Position + record.FileCommentLength <= data.Length)
{
byte[] extraBytes = data.ReadBytes((int)record.ExtraFieldLength);
if (extraBytes.Length != record.ExtraFieldLength)
byte[] commentBytes = data.ReadBytes(record.FileCommentLength);
if (commentBytes.Length != record.FileCommentLength)
return null;
record.ExtraFieldData = extraBytes;
record.FileComment = Encoding.ASCII.GetString(commentBytes);
}
return record;
}
/// <summary>
/// Parse a Stream into a ZIP64 end of central directory record
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled ZIP64 end of central directory record on success, null on error</returns>
public static EndOfCentralDirectoryRecord64? ParseEndOfCentralDirectoryRecord64(Stream data)
{
var record = new EndOfCentralDirectoryRecord64();
record.Signature = data.ReadUInt32LittleEndian();
if (record.Signature != EndOfCentralDirectoryRecord64Signature)
return null;
record.DirectoryRecordSize = data.ReadUInt64LittleEndian();
record.HostSystem = (HostSystem)data.ReadByteValue();
record.VersionMadeBy = data.ReadByteValue();
record.VersionNeededToExtract = data.ReadUInt16LittleEndian();
record.DiskNumber = data.ReadUInt32LittleEndian();
record.StartDiskNumber = data.ReadUInt32LittleEndian();
record.TotalEntriesOnDisk = data.ReadUInt64LittleEndian();
record.TotalEntries = data.ReadUInt64LittleEndian();
record.CentralDirectorySize = data.ReadUInt64LittleEndian();
record.CentralDirectoryOffset = data.ReadUInt64LittleEndian();
// TODO: Handle the ExtensibleDataSector -- How to detect if exists?
return record;
}
/// <summary>
/// Parse a Stream into a local file header
/// </summary>
@@ -606,83 +462,41 @@ namespace SabreTools.Serialization.Deserializers
/// <returns>Filled local file header on success, null on error</returns>
public static LocalFileHeader? ParseLocalFileHeader(Stream data)
{
var header = new LocalFileHeader();
var obj = new LocalFileHeader();
header.Signature = data.ReadUInt32LittleEndian();
if (header.Signature != LocalFileHeaderSignature)
obj.Signature = data.ReadUInt32LittleEndian();
if (obj.Signature != LocalFileHeaderSignature)
return null;
header.Version = data.ReadUInt16LittleEndian();
header.Flags = (GeneralPurposeBitFlags)data.ReadUInt16LittleEndian();
header.CompressionMethod = (CompressionMethod)data.ReadUInt16LittleEndian();
header.LastModifedFileTime = data.ReadUInt16LittleEndian();
header.LastModifiedFileDate = data.ReadUInt16LittleEndian();
header.CRC32 = data.ReadUInt32LittleEndian();
header.CompressedSize = data.ReadUInt32LittleEndian();
header.UncompressedSize = data.ReadUInt32LittleEndian();
header.FileNameLength = data.ReadUInt16LittleEndian();
header.ExtraFieldLength = data.ReadUInt16LittleEndian();
obj.Version = data.ReadUInt16LittleEndian();
obj.Flags = (GeneralPurposeBitFlags)data.ReadUInt16LittleEndian();
obj.CompressionMethod = (CompressionMethod)data.ReadUInt16LittleEndian();
obj.LastModifedFileTime = data.ReadUInt16LittleEndian();
obj.LastModifiedFileDate = data.ReadUInt16LittleEndian();
obj.CRC32 = data.ReadUInt32LittleEndian();
obj.CompressedSize = data.ReadUInt32LittleEndian();
obj.UncompressedSize = data.ReadUInt32LittleEndian();
obj.FileNameLength = data.ReadUInt16LittleEndian();
obj.ExtraFieldLength = data.ReadUInt16LittleEndian();
if (header.FileNameLength > 0 && data.Position + header.FileNameLength <= data.Length)
if (obj.FileNameLength > 0 && data.Position + obj.FileNameLength <= data.Length)
{
byte[] filenameBytes = data.ReadBytes(header.FileNameLength);
if (filenameBytes.Length != header.FileNameLength)
byte[] filenameBytes = data.ReadBytes(obj.FileNameLength);
if (filenameBytes.Length != obj.FileNameLength)
return null;
header.FileName = Encoding.ASCII.GetString(filenameBytes);
obj.FileName = Encoding.ASCII.GetString(filenameBytes);
}
if (header.ExtraFieldLength > 0 && data.Position + header.ExtraFieldLength <= data.Length)
if (obj.ExtraFieldLength > 0 && data.Position + obj.ExtraFieldLength <= data.Length)
{
byte[] extraBytes = data.ReadBytes(header.ExtraFieldLength);
if (extraBytes.Length != header.ExtraFieldLength)
byte[] extraBytes = data.ReadBytes(obj.ExtraFieldLength);
if (extraBytes.Length != obj.ExtraFieldLength)
return null;
header.ExtraField = extraBytes;
obj.ExtraField = extraBytes;
}
return header;
}
/// <summary>
/// Parse a Stream into a data descriptor
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled data descriptor on success, null on error</returns>
public static DataDescriptor? ParseDataDescriptor(Stream data)
{
var dataDescriptor = new DataDescriptor();
// Signatures are expected but not required
dataDescriptor.Signature = data.ReadUInt32LittleEndian();
if (dataDescriptor.Signature != DataDescriptorSignature)
data.Seek(-4, SeekOrigin.Current);
dataDescriptor.CRC32 = data.ReadUInt32LittleEndian();
dataDescriptor.CompressedSize = data.ReadUInt32LittleEndian();
dataDescriptor.UncompressedSize = data.ReadUInt32LittleEndian();
return dataDescriptor;
}
/// <summary>
/// Parse a Stream into a ZIP64 data descriptor
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled ZIP64 data descriptor on success, null on error</returns>
public static DataDescriptor64? ParseDataDescriptor64(Stream data)
{
var zip64DataDescriptor = new DataDescriptor64();
// Signatures are expected but not required
zip64DataDescriptor.Signature = data.ReadUInt32LittleEndian();
if (zip64DataDescriptor.Signature != DataDescriptorSignature)
data.Seek(-4, SeekOrigin.Current);
zip64DataDescriptor.CRC32 = data.ReadUInt32LittleEndian();
zip64DataDescriptor.CompressedSize = data.ReadUInt64LittleEndian();
zip64DataDescriptor.UncompressedSize = data.ReadUInt64LittleEndian();
return zip64DataDescriptor;
return obj;
}
}
}

View File

@@ -24,9 +24,9 @@ namespace SabreTools.Serialization.Printers
Print(builder,
archive.LocalFileHeaders,
archive.EncryptionHeaders,
archive.FileData,
archive.DataDescriptors,
archive.ZIP64DataDescriptors);
archive.FileData);
Print(builder, archive.DataDescriptors);
Print(builder, archive.ZIP64DataDescriptors);
}
private static void Print(StringBuilder builder, EndOfCentralDirectoryRecord? record)
@@ -158,9 +158,7 @@ namespace SabreTools.Serialization.Printers
private static void Print(StringBuilder builder,
LocalFileHeader[]? localFileHeaders,
byte[][]? encryptionHeaders,
byte[][]? fileData,
DataDescriptor[]? dataDescriptors,
DataDescriptor64[]? zip64DataDescriptors)
byte[][]? fileData)
{
builder.AppendLine(" Local File Information:");
builder.AppendLine(" -------------------------");
@@ -172,9 +170,7 @@ namespace SabreTools.Serialization.Printers
}
if (encryptionHeaders == null || localFileHeaders.Length > encryptionHeaders.Length
|| fileData == null || localFileHeaders.Length > fileData.Length
|| dataDescriptors == null || localFileHeaders.Length > dataDescriptors.Length
|| zip64DataDescriptors == null || localFileHeaders.Length > zip64DataDescriptors.Length)
|| fileData == null || localFileHeaders.Length > fileData.Length)
{
builder.AppendLine(" Mismatch in local file array values");
builder.AppendLine();
@@ -185,10 +181,8 @@ namespace SabreTools.Serialization.Printers
var localFileHeader = localFileHeaders[i];
var encryptionHeader = encryptionHeaders != null && i < encryptionHeaders.Length ? encryptionHeaders[i] : null;
var fileDatum = fileData != null && i < fileData.Length ? fileData[i] : null;
var dataDescriptor = dataDescriptors != null && i < dataDescriptors.Length ? dataDescriptors[i] : null;
var zip64DataDescriptor = zip64DataDescriptors != null && i < zip64DataDescriptors.Length ? zip64DataDescriptors[i] : null;
Print(builder, localFileHeader, encryptionHeader, fileDatum, dataDescriptor, zip64DataDescriptor, i);
Print(builder, localFileHeader, encryptionHeader, fileDatum, i);
}
builder.AppendLine();
@@ -198,8 +192,6 @@ namespace SabreTools.Serialization.Printers
LocalFileHeader localFileHeader,
byte[]? encryptionHeader,
byte[]? fileData,
DataDescriptor? dataDescriptor,
DataDescriptor64? zip64DataDescriptor,
int index)
{
builder.AppendLine($" Local File Entry {index}");
@@ -236,30 +228,56 @@ namespace SabreTools.Serialization.Printers
builder.AppendLine(fileData.Length, " [File Data] Length");
//builder.AppendLine(fileData, " [File Data] Data");
}
}
if (dataDescriptor == null)
private static void Print(StringBuilder builder, DataDescriptor[]? entries)
{
builder.AppendLine(" Data Descriptors Information:");
builder.AppendLine(" -------------------------");
if (entries == null || entries.Length == 0)
{
builder.AppendLine(" [Data Descriptor]: [NULL]");
}
else
{
builder.AppendLine(dataDescriptor.Signature, " [Data Descriptor] Signature");
builder.AppendLine(dataDescriptor.CRC32, " [Data Descriptor] CRC-32");
builder.AppendLine(dataDescriptor.CompressedSize, " [Data Descriptor] Compressed size");
builder.AppendLine(dataDescriptor.UncompressedSize, " [Data Descriptor] Uncompressed size");
builder.AppendLine(" No data descriptors");
builder.AppendLine();
return;
}
if (zip64DataDescriptor == null)
for (int i = 0; i < entries.Length; i++)
{
builder.AppendLine(" [ZIP64 Data Descriptor]: [NULL]");
var entry = entries[i];
builder.AppendLine($" Data Descriptors Entry {i}");
builder.AppendLine(entry.Signature, " Signature");
builder.AppendLine(entry.CRC32, $" CRC-32");
builder.AppendLine(entry.CompressedSize, $" Compressed size");
builder.AppendLine(entry.UncompressedSize, $" Uncompressed size");
}
else
builder.AppendLine();
}
private static void Print(StringBuilder builder, DataDescriptor64[]? entries)
{
builder.AppendLine(" ZIP64 Data Descriptors Information:");
builder.AppendLine(" -------------------------");
if (entries == null || entries.Length == 0)
{
builder.AppendLine(zip64DataDescriptor.Signature, " [ZIP64 Data Descriptor] Signature");
builder.AppendLine(zip64DataDescriptor.CRC32, " [ZIP64 Data Descriptor] CRC-32");
builder.AppendLine(zip64DataDescriptor.CompressedSize, " [ZIP64 Data Descriptor] Compressed size");
builder.AppendLine(zip64DataDescriptor.UncompressedSize, " [ZIP64 Data Descriptor] Uncompressed size");
builder.AppendLine(" No ZIP64 data descriptors");
builder.AppendLine();
return;
}
for (int i = 0; i < entries.Length; i++)
{
var entry = entries[i];
builder.AppendLine($" ZIP64 Data Descriptors Entry {i}");
builder.AppendLine(entry.Signature, " Signature");
builder.AppendLine(entry.CRC32, $" CRC-32");
builder.AppendLine(entry.CompressedSize, $" Compressed size");
builder.AppendLine(entry.UncompressedSize, $" Uncompressed size");
}
builder.AppendLine();
}
}
}