diff --git a/SabreTools.Serialization/Deserializers/PKZIP.cs b/SabreTools.Serialization/Deserializers/PKZIP.cs index 57fa4c88..52934082 100644 --- a/SabreTools.Serialization/Deserializers/PKZIP.cs +++ b/SabreTools.Serialization/Deserializers/PKZIP.cs @@ -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 @@ -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(); - 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(); - 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(); var encryptionHeaders = new List(); var fileData = new List(); // TODO: Should this data be read here? var dataDescriptors = new List(); var zip64DataDescriptors = new List(); + var cdrs = new List(); - // 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 } /// - /// Search for the end of central directory record + /// Parse a Stream into an archive extra data record /// /// Stream to parse - /// Position of the end of central directory record, -1 on error - public static long SearchForEndOfCentralDirectoryRecord(Stream data) + /// Filled archive extra data record on success, null on error + 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; - } - - /// - /// Parse a Stream into an end of central directory record - /// - /// Stream to parse - /// Filled end of central directory record on success, null on error - 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; - } - - /// - /// Search for the ZIP64 end of central directory locator - /// - /// Stream to parse - /// Position of the ZIP64 end of central directory locator, -1 on error - 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; - } - - /// - /// Parse a Stream into a ZIP64 end of central directory record - /// - /// Stream to parse - /// Filled ZIP64 end of central directory record on success, null on error - 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; } /// @@ -525,80 +332,129 @@ namespace SabreTools.Serialization.Deserializers } /// - /// Search for the archive extra data record + /// Parse a Stream into a data descriptor /// /// Stream to parse - /// Offset to the first central directory record - /// Position of the archive extra data record, -1 on error - public static long SearchForArchiveExtraDataRecord(Stream data, long centralDirectoryoffset) + /// Filled data descriptor on success, null on error + 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; } /// - /// Parse a Stream into an archive extra data record + /// Parse a Stream into a ZIP64 data descriptor /// /// Stream to parse - /// Filled archive extra data record on success, null on error - public static ArchiveExtraDataRecord? ParseArchiveExtraDataRecord(Stream data) + /// Filled ZIP64 data descriptor on success, null on error + 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; + } + + /// + /// Parse a Stream into a ZIP64 end of central directory locator + /// + /// Stream to parse + /// Filled ZIP64 end of central directory locator on success, null on error + 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; + } + + /// + /// Parse a Stream into an end of central directory record + /// + /// Stream to parse + /// Filled end of central directory record on success, null on error + 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; } + /// + /// Parse a Stream into a ZIP64 end of central directory record + /// + /// Stream to parse + /// Filled ZIP64 end of central directory record on success, null on error + 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; + } + /// /// Parse a Stream into a local file header /// @@ -606,83 +462,41 @@ namespace SabreTools.Serialization.Deserializers /// Filled local file header on success, null on error 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; - } - - /// - /// Parse a Stream into a data descriptor - /// - /// Stream to parse - /// Filled data descriptor on success, null on error - 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; - } - - /// - /// Parse a Stream into a ZIP64 data descriptor - /// - /// Stream to parse - /// Filled ZIP64 data descriptor on success, null on error - 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; } } } diff --git a/SabreTools.Serialization/Printers/PKZIP.cs b/SabreTools.Serialization/Printers/PKZIP.cs index 9c92cd18..b3291475 100644 --- a/SabreTools.Serialization/Printers/PKZIP.cs +++ b/SabreTools.Serialization/Printers/PKZIP.cs @@ -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(); } } }