using System; using System.Collections.Generic; using System.IO; using System.Text; using SabreTools.Data.Models.PKZIP; using SabreTools.IO.Extensions; using SabreTools.Matching; using SabreTools.Numerics.Extensions; using static SabreTools.Data.Models.PKZIP.Constants; #pragma warning disable IDE0017 // Simplify object initialization namespace SabreTools.Serialization.Readers { public class PKZIP : BaseBinaryReader { /// public override Archive? Deserialize(Stream? data) { // If the data is invalid if (data is null || !data.CanRead) return null; try { // Cache the current offset long initialOffset = data.Position; var archive = new Archive(); // Setup all of the collections var localFiles = new List(); var cdrs = new List(); // Read all blocks do { // Read the signature long beforeSignature = data.Position; uint signature = data.ReadUInt32LittleEndian(); data.SeekIfPossible(beforeSignature, SeekOrigin.Begin); // Switch based on the signature found bool validBlock = false; switch (signature) { // Central Directory File Header case CentralDirectoryFileHeaderSignature: var cdr = ParseCentralDirectoryFileHeader(data, Debug); if (cdr is null) return null; // Add the central directory record validBlock = true; cdrs.Add(cdr); break; // Local File case LocalFileHeaderSignature: var lf = ParseLocalFile(data, Debug); if (lf is null) return null; // Add the local file validBlock = true; localFiles.Add(lf); break; // TODO: Implement case DigitalSignatureSignature: break; // End of Central Directory Record case EndOfCentralDirectoryRecordSignature: var eocdr = ParseEndOfCentralDirectoryRecord(data); if (eocdr is null) return null; // 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 is null) return null; // Assign the ZIP64 end of central directory record validBlock = true; archive.ZIP64EndOfCentralDirectoryRecord = eocdr64; break; // ZIP64 End of Central Directory Locator case EndOfCentralDirectoryLocator64Signature: var eocdl64 = ParseEndOfCentralDirectoryLocator64(data); if (eocdl64 is null) return null; // Assign the ZIP64 end of central directory record validBlock = true; archive.ZIP64EndOfCentralDirectoryLocator = eocdl64; break; // Archive Extra Data Record case ArchiveExtraDataRecordSignature: var aedr = ParseArchiveExtraDataRecord(data); if (aedr is null) return null; // Assign the archive extra data record validBlock = true; archive.ArchiveExtraDataRecord = aedr; break; default: // TODO: Log invalid values break; } // If there was an invalid block if (!validBlock) break; } while (data.Position < data.Length); // If no blocks were read if (localFiles.Count == 0 && cdrs.Count == 0) return null; // Assign the local files archive.LocalFiles = [.. localFiles]; // Assign the central directory records archive.CentralDirectoryHeaders = [.. cdrs]; return archive; } catch { // Ignore the actual error return null; } } /// /// Parse a Stream into an archive extra data record /// /// Stream to parse /// Filled archive extra data record on success, null on error public static ArchiveExtraDataRecord? ParseArchiveExtraDataRecord(Stream data) { var obj = new ArchiveExtraDataRecord(); obj.Signature = data.ReadUInt32LittleEndian(); if (obj.Signature != ArchiveExtraDataRecordSignature) return null; obj.ExtraFieldLength = data.ReadUInt32LittleEndian(); if (obj.ExtraFieldLength > 0 && data.Position + obj.ExtraFieldLength <= data.Length) { byte[] extraBytes = data.ReadBytes((int)obj.ExtraFieldLength); if (extraBytes.Length != obj.ExtraFieldLength) return null; obj.ExtraFieldData = extraBytes; } return obj; } /// /// Parse a Stream into a central directory file header /// /// Stream to parse /// True to include debug data, false otherwise /// Filled central directory file header on success, null on error public static CentralDirectoryFileHeader? ParseCentralDirectoryFileHeader(Stream data, bool includeDebug = false) { var obj = new CentralDirectoryFileHeader(); obj.Signature = data.ReadUInt32LittleEndian(); if (obj.Signature != CentralDirectoryFileHeaderSignature) return null; obj.HostSystem = (HostSystem)data.ReadByteValue(); obj.VersionMadeBy = data.ReadByteValue(); obj.VersionNeededToExtract = 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(); obj.FileCommentLength = data.ReadUInt16LittleEndian(); obj.DiskNumberStart = data.ReadUInt16LittleEndian(); obj.InternalFileAttributes = (InternalFileAttributes)data.ReadUInt16LittleEndian(); obj.ExternalFileAttributes = data.ReadUInt32LittleEndian(); obj.RelativeOffsetOfLocalHeader = data.ReadUInt32LittleEndian(); #if NET20 || NET35 bool utf8 = (obj.Flags & GeneralPurposeBitFlags.LanguageEncodingFlag) != 0; #else bool utf8 = obj.Flags.HasFlag(GeneralPurposeBitFlags.LanguageEncodingFlag); #endif if (obj.FileNameLength > 0 && data.Position + obj.FileNameLength <= data.Length) { byte[] filenameBytes = data.ReadBytes(obj.FileNameLength); if (filenameBytes.Length != obj.FileNameLength) return null; if (utf8) obj.FileName = Encoding.UTF8.GetString(filenameBytes); else obj.FileName = Encoding.ASCII.GetString(filenameBytes); } if (obj.ExtraFieldLength > 0 && data.Position + obj.ExtraFieldLength <= data.Length) { byte[] extraBytes = data.ReadBytes(obj.ExtraFieldLength); if (extraBytes.Length != obj.ExtraFieldLength) return null; obj.ExtraFields = ParseExtraFields(obj, extraBytes, includeDebug); } if (obj.FileCommentLength > 0 && data.Position + obj.FileCommentLength <= data.Length) { byte[] commentBytes = data.ReadBytes(obj.FileCommentLength); if (commentBytes.Length != obj.FileCommentLength) return null; if (utf8) obj.FileComment = Encoding.UTF8.GetString(commentBytes); else obj.FileComment = Encoding.ASCII.GetString(commentBytes); } return obj; } /// /// 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 obj = new DataDescriptor(); // Cache the current position long currentPosition = data.Position; // Check if the block is a 12- or 16-byte bool isShort = false; if (data.Position + 12 == data.Length) { isShort = true; } else { data.SeekIfPossible(12, SeekOrigin.Current); byte[] nextBlock = data.ReadBytes(2); data.SeekIfPossible(currentPosition, SeekOrigin.Begin); if (nextBlock.EqualsExactly([0x50, 0x4B])) isShort = true; } // If the 16-byte variant if (!isShort) { obj.Signature = data.ReadUInt32LittleEndian(); if (obj.Signature != DataDescriptorSignature) return null; } obj.CRC32 = data.ReadUInt32LittleEndian(); obj.CompressedSize = data.ReadUInt32LittleEndian(); obj.UncompressedSize = data.ReadUInt32LittleEndian(); return obj; } /// /// 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 obj = new DataDescriptor64(); // Cache the current position long currentPosition = data.Position; // Check if the block is a 20- or 24-byte bool isShort = false; if (data.Position + 20 == data.Length) { isShort = true; } else { data.SeekIfPossible(20, SeekOrigin.Current); byte[] nextBlock = data.ReadBytes(2); data.SeekIfPossible(currentPosition, SeekOrigin.Begin); if (nextBlock.EqualsExactly([0x50, 0x4B])) isShort = true; } // If the 24-byte variant if (!isShort) { obj.Signature = data.ReadUInt32LittleEndian(); if (obj.Signature != DataDescriptorSignature) return null; } 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.SeekIfPossible(-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 obj = new EndOfCentralDirectoryRecord(); obj.Signature = data.ReadUInt32LittleEndian(); if (obj.Signature != EndOfCentralDirectoryRecordSignature) return null; obj.DiskNumber = data.ReadUInt16LittleEndian(); obj.StartDiskNumber = data.ReadUInt16LittleEndian(); obj.TotalEntriesOnDisk = data.ReadUInt16LittleEndian(); obj.TotalEntries = data.ReadUInt16LittleEndian(); obj.CentralDirectorySize = data.ReadUInt32LittleEndian(); obj.CentralDirectoryOffset = data.ReadUInt32LittleEndian(); obj.FileCommentLength = data.ReadUInt16LittleEndian(); if (obj.FileCommentLength > 0 && data.Position + obj.FileCommentLength <= data.Length) { byte[] commentBytes = data.ReadBytes(obj.FileCommentLength); if (commentBytes.Length != obj.FileCommentLength) return null; obj.FileComment = Encoding.ASCII.GetString(commentBytes); } return obj; } /// /// 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 obj = new EndOfCentralDirectoryRecord64(); obj.Signature = data.ReadUInt32LittleEndian(); if (obj.Signature != EndOfCentralDirectoryRecord64Signature) return null; obj.DirectoryRecordSize = data.ReadUInt64LittleEndian(); obj.HostSystem = (HostSystem)data.ReadByteValue(); obj.VersionMadeBy = data.ReadByteValue(); obj.VersionNeededToExtract = data.ReadUInt16LittleEndian(); obj.DiskNumber = data.ReadUInt32LittleEndian(); obj.StartDiskNumber = data.ReadUInt32LittleEndian(); obj.TotalEntriesOnDisk = data.ReadUInt64LittleEndian(); obj.TotalEntries = data.ReadUInt64LittleEndian(); obj.CentralDirectorySize = data.ReadUInt64LittleEndian(); obj.CentralDirectoryOffset = data.ReadUInt64LittleEndian(); // TODO: Handle the ExtensibleDataSector -- How to detect if exists? return obj; } /// /// Parse a Stream into a local file /// /// Stream to parse /// True to include debug data, false otherwise /// Filled local file on success, null on error public static LocalFile? ParseLocalFile(Stream data, bool includeDebug = false) { var obj = new LocalFile(); #region Local File Header // Try to read the header var localFileHeader = ParseLocalFileHeader(data, includeDebug); if (localFileHeader is null) return null; // Assign the header obj.LocalFileHeader = localFileHeader; ulong compressedSize = localFileHeader.CompressedSize; if (localFileHeader.ExtraFields is not null) { foreach (var field in localFileHeader.ExtraFields) { if (field is not Zip64ExtendedInformationExtraField infoField) continue; if (infoField.CompressedSize is null) continue; compressedSize = infoField.CompressedSize.Value; } } #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[] encryptionHeaders = data.ReadBytes(12); if (encryptionHeaders.Length != 12) return null; // Set the encryption headers obj.EncryptionHeaders = encryptionHeaders; } else { // Add the empty encryption header obj.EncryptionHeaders = []; } #endregion #region File Data // Try to read the file data var fileData = data.ReadBytes((int)compressedSize); if (fileData.Length < (long)compressedSize) return null; // Set the file data obj.FileData = fileData; #endregion #region Data Descriptor // Only attempt to read the descriptor if necessary #if NET20 || NET35 if ((localFileHeader.Flags & GeneralPurposeBitFlags.NoCRC) == 0) #else if (!localFileHeader.Flags.HasFlag(GeneralPurposeBitFlags.NoCRC)) #endif { obj.DataDescriptor = new DataDescriptor(); obj.ZIP64DataDescriptor = new DataDescriptor64(); return obj; } // Read the signature long beforeSignature = data.Position; uint signature = data.ReadUInt32LittleEndian(); data.SeekIfPossible(beforeSignature, SeekOrigin.Begin); // Don't fail if descriptor is missing if (signature != DataDescriptorSignature) { obj.DataDescriptor = new DataDescriptor(); obj.ZIP64DataDescriptor = new DataDescriptor64(); return obj; } // Determine if the entry is ZIP64 bool zip64 = IsZip64Descriptor(data); // Try to parse the correct data descriptor if (zip64) { obj.DataDescriptor = new DataDescriptor(); obj.ZIP64DataDescriptor = ParseDataDescriptor64(data); if (obj.ZIP64DataDescriptor is null) return null; } else { obj.DataDescriptor = ParseDataDescriptor(data); obj.ZIP64DataDescriptor = new DataDescriptor64(); if (obj.DataDescriptor is null) return null; } #endregion return obj; } /// /// Parse a Stream into a local file header /// /// Stream to parse /// True to include debug data, false otherwise /// Filled local file header on success, null on error public static LocalFileHeader? ParseLocalFileHeader(Stream data, bool includeDebug = false) { var obj = new LocalFileHeader(); obj.Signature = data.ReadUInt32LittleEndian(); if (obj.Signature != LocalFileHeaderSignature) return null; 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 NET20 || NET35 bool utf8 = (obj.Flags & GeneralPurposeBitFlags.LanguageEncodingFlag) != 0; #else bool utf8 = obj.Flags.HasFlag(GeneralPurposeBitFlags.LanguageEncodingFlag); #endif if (obj.FileNameLength > 0 && data.Position + obj.FileNameLength <= data.Length) { byte[] filenameBytes = data.ReadBytes(obj.FileNameLength); if (filenameBytes.Length != obj.FileNameLength) return null; if (utf8) obj.FileName = Encoding.UTF8.GetString(filenameBytes); else obj.FileName = Encoding.ASCII.GetString(filenameBytes); } if (obj.ExtraFieldLength > 0 && data.Position + obj.ExtraFieldLength <= data.Length) { byte[] extraBytes = data.ReadBytes(obj.ExtraFieldLength); if (extraBytes.Length != obj.ExtraFieldLength) return null; obj.ExtraFields = ParseExtraFields(obj, extraBytes, includeDebug); } return obj; } #region Extras Fields /// /// Process all extensible data fields in a central directory file extras block /// /// Central directory file header /// Byte array to parse /// True to include debug data, false otherwise /// Array of data fields on success, null otherwise public static ExtensibleDataField[]? ParseExtraFields(CentralDirectoryFileHeader header, byte[]? data, bool includeDebug = false) { if (data is null) return null; List fields = []; int offset = 0; while (offset < data.Length) { // Peek at the next header ID HeaderID id = (HeaderID)data.ReadUInt16LittleEndian(ref offset); offset -= 2; // Read based on the ID ExtensibleDataField? field = id switch { HeaderID.Zip64ExtendedInformation => ParseZip64ExtendedInformationExtraField(data, ref offset, header), HeaderID.AVInfo => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement model HeaderID.ExtendedLanguageEncodingData => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement model HeaderID.OS2 => ParseOS2ExtraField(data, ref offset), HeaderID.NTFS => ParseNTFSExtraField(data, ref offset), HeaderID.OpenVMS => ParseOpenVMSExtraField(data, ref offset), HeaderID.UNIX => ParseUnixExtraField(data, ref offset), HeaderID.FileStreamFork => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement HeaderID.PatchDescriptor => ParsePatchDescriptorExtraField(data, ref offset), HeaderID.PKCSStore => ParsePKCS7Store(data, ref offset), HeaderID.X509IndividualFile => ParseX509IndividualFile(data, ref offset), HeaderID.X509CentralDirectory => ParseX509CentralDirectory(data, ref offset), HeaderID.StrongEncryptionHeader => ParseStrongEncryptionHeader(data, ref offset), HeaderID.RecordManagementControls => ParseRecordManagementControls(data, ref offset), HeaderID.PKCSCertificateList => ParsePKCS7EncryptionRecipientCertificateList(data, ref offset), HeaderID.Timestamp => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement HeaderID.PolicyDecryptionKey => ParsePolicyDecryptionKeyRecordExtraField(data, ref offset), HeaderID.SmartcryptKeyProvider => ParseKeyProviderRecordExtraField(data, ref offset), HeaderID.SmartcryptPolicyKeyData => ParsePolicyKeyDataRecordRecordExtraField(data, ref offset), HeaderID.IBMS390AttributesUncompressed => ParseAS400ExtraFieldAttribute(data, ref offset), HeaderID.IBMS390AttributesCompressed => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement HeaderID.POSZIP4690 => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement HeaderID.Macintosh => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement HeaderID.PixarUSD => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement HeaderID.ZipItMacintosh => ParseZipItMacintoshExtraField(data, ref offset), HeaderID.ZipItMacintosh135Plus => ParseZipItMacintoshShortFileExtraField(data, ref offset), HeaderID.ZipItMacintosh135PlusAlt => ParseZipItMacintoshShortDirectoryExtraField(data, ref offset), HeaderID.InfoZIPMacintosh => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement HeaderID.AcornSparkFS => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement HeaderID.WindowsNTSecurityDescriptor => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement HeaderID.VMCMS => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement HeaderID.MVS => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement HeaderID.THEOSold => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement HeaderID.FWKCSMD5 => ParseFWKCSMD5ExtraField(data, ref offset), HeaderID.OS2AccessControlList => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement HeaderID.InfoZIPOpenVMS => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement HeaderID.MacintoshSmartzip => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement HeaderID.XceedOriginalLocation => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement HeaderID.ADSVS => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement HeaderID.ExtendedTimestamp => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement HeaderID.XceedUnicode => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement HeaderID.InfoZIPUNIX => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement HeaderID.InfoZIPUnicodeComment => ParseInfoZIPUnicodeCommentExtraField(data, ref offset), HeaderID.BeOSBeBox => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement HeaderID.THEOS => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement HeaderID.InfoZIPUnicodePath => ParseInfoZIPUnicodePathExtraField(data, ref offset), HeaderID.AtheOSSyllable => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement HeaderID.ASiUNIX => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement HeaderID.InfoZIPUNIXNew => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement HeaderID.InfoZIPUNIXNewer => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement HeaderID.DataStreamAlignment => ParseDataStreamAlignment(data, ref offset), HeaderID.MicrosoftOpenPackagingGrowthHint => ParseMicrosoftOpenPackagingGrowthHint(data, ref offset), HeaderID.JavaJAR => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement HeaderID.AndroidZIPAlignment => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement HeaderID.KoreanZIPCodePage => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement HeaderID.SMSQDOS => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement HeaderID.AExEncryptionStructure => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement HeaderID.Unknown => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement _ => ParseUnknownExtraField(data, ref offset, includeDebug), }; if (field is not null) fields.Add(field); } return [.. fields]; } /// /// Process all extensible data fields in a local file extras block /// /// Local file header /// Byte array to parse /// True to include debug data, false otherwise /// Array of data fields on success, null otherwise public static ExtensibleDataField[]? ParseExtraFields(LocalFileHeader header, byte[]? data, bool includeDebug = false) { if (data is null) return null; List fields = []; int offset = 0; while (offset < data.Length) { // Peek at the next header ID HeaderID id = (HeaderID)data.ReadUInt16LittleEndian(ref offset); offset -= 2; // Read based on the ID ExtensibleDataField? field = id switch { HeaderID.Zip64ExtendedInformation => ParseZip64ExtendedInformationExtraField(data, ref offset, header), HeaderID.AVInfo => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement model HeaderID.ExtendedLanguageEncodingData => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement model HeaderID.OS2 => ParseOS2ExtraField(data, ref offset), HeaderID.NTFS => ParseNTFSExtraField(data, ref offset), HeaderID.OpenVMS => ParseOpenVMSExtraField(data, ref offset), HeaderID.UNIX => ParseUnixExtraField(data, ref offset), HeaderID.FileStreamFork => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement HeaderID.PatchDescriptor => ParsePatchDescriptorExtraField(data, ref offset), HeaderID.PKCSStore => ParsePKCS7Store(data, ref offset), HeaderID.X509IndividualFile => ParseX509IndividualFile(data, ref offset), HeaderID.X509CentralDirectory => ParseX509CentralDirectory(data, ref offset), HeaderID.StrongEncryptionHeader => ParseStrongEncryptionHeader(data, ref offset), HeaderID.RecordManagementControls => ParseRecordManagementControls(data, ref offset), HeaderID.PKCSCertificateList => ParsePKCS7EncryptionRecipientCertificateList(data, ref offset), HeaderID.Timestamp => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement HeaderID.PolicyDecryptionKey => ParsePolicyDecryptionKeyRecordExtraField(data, ref offset), HeaderID.SmartcryptKeyProvider => ParseKeyProviderRecordExtraField(data, ref offset), HeaderID.SmartcryptPolicyKeyData => ParsePolicyKeyDataRecordRecordExtraField(data, ref offset), HeaderID.IBMS390AttributesUncompressed => ParseAS400ExtraFieldAttribute(data, ref offset), HeaderID.IBMS390AttributesCompressed => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement HeaderID.POSZIP4690 => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement HeaderID.Macintosh => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement HeaderID.PixarUSD => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement HeaderID.ZipItMacintosh => ParseZipItMacintoshExtraField(data, ref offset), HeaderID.ZipItMacintosh135Plus => ParseZipItMacintoshShortFileExtraField(data, ref offset), HeaderID.ZipItMacintosh135PlusAlt => ParseZipItMacintoshShortDirectoryExtraField(data, ref offset), HeaderID.InfoZIPMacintosh => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement HeaderID.AcornSparkFS => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement HeaderID.WindowsNTSecurityDescriptor => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement HeaderID.VMCMS => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement HeaderID.MVS => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement HeaderID.THEOSold => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement HeaderID.FWKCSMD5 => ParseFWKCSMD5ExtraField(data, ref offset), HeaderID.OS2AccessControlList => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement HeaderID.InfoZIPOpenVMS => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement HeaderID.MacintoshSmartzip => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement HeaderID.XceedOriginalLocation => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement HeaderID.ADSVS => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement HeaderID.ExtendedTimestamp => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement HeaderID.XceedUnicode => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement HeaderID.InfoZIPUNIX => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement HeaderID.InfoZIPUnicodeComment => ParseInfoZIPUnicodeCommentExtraField(data, ref offset), HeaderID.BeOSBeBox => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement HeaderID.THEOS => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement HeaderID.InfoZIPUnicodePath => ParseInfoZIPUnicodePathExtraField(data, ref offset), HeaderID.AtheOSSyllable => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement HeaderID.ASiUNIX => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement HeaderID.InfoZIPUNIXNew => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement HeaderID.InfoZIPUNIXNewer => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement HeaderID.DataStreamAlignment => ParseDataStreamAlignment(data, ref offset), HeaderID.MicrosoftOpenPackagingGrowthHint => ParseMicrosoftOpenPackagingGrowthHint(data, ref offset), HeaderID.JavaJAR => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement HeaderID.AndroidZIPAlignment => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement HeaderID.KoreanZIPCodePage => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement HeaderID.SMSQDOS => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement HeaderID.AExEncryptionStructure => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement HeaderID.Unknown => ParseUnknownExtraField(data, ref offset, includeDebug), // TODO: Implement _ => ParseUnknownExtraField(data, ref offset, includeDebug), }; if (field is not null) fields.Add(field); } return [.. fields]; } /// /// Parse a Stream into an unknown extras field /// /// Byte array to parse /// Offset into the byte array /// True to include debug data, false otherwise /// Filled unknown extras field on success, null on error private static UnknownExtraField? ParseUnknownExtraField(byte[] data, ref int offset, bool includeDebug) { var obj = new UnknownExtraField(); obj.HeaderID = (HeaderID)data.ReadUInt16LittleEndian(ref offset); obj.DataSize = data.ReadUInt16LittleEndian(ref offset); if (obj.DataSize > data.Length - offset) { ushort remainingSize = (ushort)(data.Length - offset); if (includeDebug) Console.WriteLine($"Extra field of type '{obj.HeaderID}' requested {obj.DataSize} bytes, but only {remainingSize} remain"); obj.DataSize = remainingSize; } if (obj.DataSize > 0) obj.Data = data.ReadBytes(ref offset, obj.DataSize); return obj; } /// /// Parse a Stream into a Zip64ExtendedInformationExtraField /// /// Byte array to parse /// Offset into the byte array /// Filled Zip64ExtendedInformationExtraField on success, null on error private static Zip64ExtendedInformationExtraField? ParseZip64ExtendedInformationExtraField(byte[] data, ref int offset, CentralDirectoryFileHeader header) { var obj = new Zip64ExtendedInformationExtraField(); obj.HeaderID = (HeaderID)data.ReadUInt16LittleEndian(ref offset); if (obj.HeaderID != HeaderID.Zip64ExtendedInformation) return null; obj.DataSize = data.ReadUInt16LittleEndian(ref offset); int bytesRemaining = obj.DataSize; if (header.UncompressedSize == uint.MaxValue && bytesRemaining >= 8) { obj.OriginalSize = data.ReadUInt64LittleEndian(ref offset); bytesRemaining -= 8; } if (header.CompressedSize == uint.MaxValue && bytesRemaining >= 8) { obj.CompressedSize = data.ReadUInt64LittleEndian(ref offset); bytesRemaining -= 8; } if (header.RelativeOffsetOfLocalHeader == uint.MaxValue && bytesRemaining >= 8) { obj.RelativeHeaderOffset = data.ReadUInt64LittleEndian(ref offset); bytesRemaining -= 8; } if (header.DiskNumberStart == ushort.MaxValue && bytesRemaining >= 4) { obj.DiskStartNumber = data.ReadUInt32LittleEndian(ref offset); //bytesRemaining -= 4; } return obj; } /// /// Parse a Stream into a Zip64ExtendedInformationExtraField /// /// Byte array to parse /// Offset into the byte array /// Filled Zip64ExtendedInformationExtraField on success, null on error private static Zip64ExtendedInformationExtraField? ParseZip64ExtendedInformationExtraField(byte[] data, ref int offset, LocalFileHeader header) { var obj = new Zip64ExtendedInformationExtraField(); obj.HeaderID = (HeaderID)data.ReadUInt16LittleEndian(ref offset); if (obj.HeaderID != HeaderID.Zip64ExtendedInformation) return null; obj.DataSize = data.ReadUInt16LittleEndian(ref offset); int bytesRemaining = obj.DataSize; if (header.UncompressedSize == uint.MaxValue) { obj.OriginalSize = data.ReadUInt64LittleEndian(ref offset); bytesRemaining -= 8; } if (header.CompressedSize == uint.MaxValue) { obj.CompressedSize = data.ReadUInt64LittleEndian(ref offset); bytesRemaining -= 8; } // TODO: These rely on values from the central directory if (bytesRemaining >= 8) { obj.RelativeHeaderOffset = data.ReadUInt64LittleEndian(ref offset); bytesRemaining -= 8; } if (bytesRemaining >= 4) obj.DiskStartNumber = data.ReadUInt32LittleEndian(ref offset); return obj; } /// /// Parse a Stream into an OS2ExtraField /// /// Byte array to parse /// Offset into the byte array /// Filled OS2ExtraField on success, null on error private static OS2ExtraField? ParseOS2ExtraField(byte[] data, ref int offset) { var obj = new OS2ExtraField(); obj.HeaderID = (HeaderID)data.ReadUInt16LittleEndian(ref offset); if (obj.HeaderID != HeaderID.OS2) return null; obj.DataSize = data.ReadUInt16LittleEndian(ref offset); obj.UncompressedBlockSize = data.ReadUInt32LittleEndian(ref offset); obj.CompressionType = data.ReadUInt16LittleEndian(ref offset); obj.CRC32 = data.ReadUInt32LittleEndian(ref offset); obj.Data = data.ReadBytes(ref offset, obj.DataSize - 10); return obj; } /// /// Parse a Stream into a NTFSExtraField /// /// Byte array to parse /// Offset into the byte array /// Filled NTFSExtraField on success, null on error private static NTFSExtraField? ParseNTFSExtraField(byte[] data, ref int offset) { var obj = new NTFSExtraField(); obj.HeaderID = (HeaderID)data.ReadUInt16LittleEndian(ref offset); if (obj.HeaderID != HeaderID.NTFS) return null; obj.DataSize = data.ReadUInt16LittleEndian(ref offset); obj.Reserved = data.ReadUInt32LittleEndian(ref offset); List entries = []; int bytesRemaining = obj.DataSize - 4; while (bytesRemaining > 0) { var entry = new TagSizeVar(); entry.Tag = data.ReadUInt16LittleEndian(ref offset); entry.Size = data.ReadUInt16LittleEndian(ref offset); entry.Var = data.ReadBytes(ref offset, entry.Size); entries.Add(entry); bytesRemaining -= 4 + entry.Size; } obj.TagSizeVars = [.. entries]; return obj; } /// /// Parse a Stream into an OpenVMSExtraField /// /// Byte array to parse /// Offset into the byte array /// Filled OpenVMSExtraField on success, null on error private static OpenVMSExtraField? ParseOpenVMSExtraField(byte[] data, ref int offset) { var obj = new OpenVMSExtraField(); obj.HeaderID = (HeaderID)data.ReadUInt16LittleEndian(ref offset); if (obj.HeaderID != HeaderID.OpenVMS) return null; obj.DataSize = data.ReadUInt16LittleEndian(ref offset); obj.CRC = data.ReadUInt32LittleEndian(ref offset); List entries = []; int bytesRemaining = obj.DataSize - 4; while (bytesRemaining > 0) { var entry = new TagSizeVar(); entry.Tag = data.ReadUInt16LittleEndian(ref offset); entry.Size = data.ReadUInt16LittleEndian(ref offset); entry.Var = data.ReadBytes(ref offset, entry.Size); entries.Add(entry); bytesRemaining -= 4 + entry.Size; } obj.TagSizeVars = [.. entries]; return obj; } /// /// Parse a Stream into a UnixExtraField /// /// Byte array to parse /// Offset into the byte array /// Filled UnixExtraField on success, null on error private static UnixExtraField? ParseUnixExtraField(byte[] data, ref int offset) { var obj = new UnixExtraField(); obj.HeaderID = (HeaderID)data.ReadUInt16LittleEndian(ref offset); if (obj.HeaderID != HeaderID.UNIX) return null; obj.DataSize = data.ReadUInt16LittleEndian(ref offset); obj.FileLastAccessTime = data.ReadUInt32LittleEndian(ref offset); obj.FileLastModificationTime = data.ReadUInt32LittleEndian(ref offset); obj.FileUserID = data.ReadUInt16LittleEndian(ref offset); obj.FileGroupID = data.ReadUInt16LittleEndian(ref offset); obj.Data = data.ReadBytes(ref offset, obj.DataSize - 12); return obj; } /// /// Parse a Stream into a PatchDescriptorExtraField /// /// Byte array to parse /// Offset into the byte array /// Filled PatchDescriptorExtraField on success, null on error private static PatchDescriptorExtraField? ParsePatchDescriptorExtraField(byte[] data, ref int offset) { var obj = new PatchDescriptorExtraField(); obj.HeaderID = (HeaderID)data.ReadUInt16LittleEndian(ref offset); if (obj.HeaderID != HeaderID.PatchDescriptor) return null; obj.DataSize = data.ReadUInt16LittleEndian(ref offset); obj.Version = data.ReadUInt16LittleEndian(ref offset); obj.Flags = (ActionsReactions)data.ReadUInt32LittleEndian(ref offset); obj.OldSize = data.ReadUInt32LittleEndian(ref offset); obj.OldCRC = data.ReadUInt32LittleEndian(ref offset); obj.NewSize = data.ReadUInt32LittleEndian(ref offset); obj.NewCRC = data.ReadUInt32LittleEndian(ref offset); return obj; } /// /// Parse a Stream into a PKCS7Store /// /// Byte array to parse /// Offset into the byte array /// Filled PKCS7Store on success, null on error private static PKCS7Store? ParsePKCS7Store(byte[] data, ref int offset) { var obj = new PKCS7Store(); obj.HeaderID = (HeaderID)data.ReadUInt16LittleEndian(ref offset); if (obj.HeaderID != HeaderID.PKCSStore) return null; obj.DataSize = data.ReadUInt16LittleEndian(ref offset); obj.TData = data.ReadBytes(ref offset, obj.DataSize); return obj; } /// /// Parse a Stream into a X509IndividualFile /// /// Byte array to parse /// Offset into the byte array /// Filled X509IndividualFile on success, null on error private static X509IndividualFile? ParseX509IndividualFile(byte[] data, ref int offset) { var obj = new X509IndividualFile(); obj.HeaderID = (HeaderID)data.ReadUInt16LittleEndian(ref offset); if (obj.HeaderID != HeaderID.X509IndividualFile) return null; obj.DataSize = data.ReadUInt16LittleEndian(ref offset); obj.TData = data.ReadBytes(ref offset, obj.DataSize); return obj; } /// /// Parse a Stream into a X509CentralDirectory /// /// Byte array to parse /// Offset into the byte array /// Filled X509CentralDirectory on success, null on error private static X509CentralDirectory? ParseX509CentralDirectory(byte[] data, ref int offset) { var obj = new X509CentralDirectory(); obj.HeaderID = (HeaderID)data.ReadUInt16LittleEndian(ref offset); if (obj.HeaderID != HeaderID.X509CentralDirectory) return null; obj.DataSize = data.ReadUInt16LittleEndian(ref offset); obj.TData = data.ReadBytes(ref offset, obj.DataSize); return obj; } /// /// Parse a Stream into a StrongEncryptionHeader /// /// Byte array to parse /// Offset into the byte array /// Filled StrongEncryptionHeader on success, null on error private static StrongEncryptionHeader? ParseStrongEncryptionHeader(byte[] data, ref int offset) { var obj = new StrongEncryptionHeader(); obj.HeaderID = (HeaderID)data.ReadUInt16LittleEndian(ref offset); if (obj.HeaderID != HeaderID.StrongEncryptionHeader) return null; obj.DataSize = data.ReadUInt16LittleEndian(ref offset); obj.Format = data.ReadUInt16LittleEndian(ref offset); obj.AlgID = data.ReadUInt16LittleEndian(ref offset); obj.Bitlen = data.ReadUInt16LittleEndian(ref offset); obj.Flags = data.ReadUInt16LittleEndian(ref offset); obj.CertData = data.ReadBytes(ref offset, obj.DataSize - 8); return obj; } /// /// Parse a Stream into an RecordManagementControls /// /// Byte array to parse /// Offset into the byte array /// Filled RecordManagementControls on success, null on error private static RecordManagementControls? ParseRecordManagementControls(byte[] data, ref int offset) { var obj = new RecordManagementControls(); obj.HeaderID = (HeaderID)data.ReadUInt16LittleEndian(ref offset); if (obj.HeaderID != HeaderID.RecordManagementControls) return null; obj.DataSize = data.ReadUInt16LittleEndian(ref offset); List entries = []; int bytesRemaining = obj.DataSize - 4; while (bytesRemaining > 0) { var entry = new TagSizeVar(); entry.Tag = data.ReadUInt16LittleEndian(ref offset); entry.Size = data.ReadUInt16LittleEndian(ref offset); entry.Var = data.ReadBytes(ref offset, entry.Size); entries.Add(entry); bytesRemaining -= 4 + entry.Size; } obj.TagSizeVars = [.. entries]; return obj; } /// /// Parse a Stream into an PKCS7EncryptionRecipientCertificateList /// /// Byte array to parse /// Offset into the byte array /// Filled PKCS7EncryptionRecipientCertificateList on success, null on error private static PKCS7EncryptionRecipientCertificateList? ParsePKCS7EncryptionRecipientCertificateList(byte[] data, ref int offset) { var obj = new PKCS7EncryptionRecipientCertificateList(); obj.HeaderID = (HeaderID)data.ReadUInt16LittleEndian(ref offset); if (obj.HeaderID != HeaderID.PKCSCertificateList) return null; obj.DataSize = data.ReadUInt16LittleEndian(ref offset); obj.Version = data.ReadUInt16LittleEndian(ref offset); obj.CStore = data.ReadBytes(ref offset, obj.DataSize - 2); return obj; } /// /// Parse a Stream into a PolicyDecryptionKeyRecordExtraField /// /// Byte array to parse /// Offset into the byte array /// Filled PolicyDecryptionKeyRecordExtraField on success, null on error private static PolicyDecryptionKeyRecordExtraField? ParsePolicyDecryptionKeyRecordExtraField(byte[] data, ref int offset) { var obj = new PolicyDecryptionKeyRecordExtraField(); obj.HeaderID = (HeaderID)data.ReadUInt16LittleEndian(ref offset); if (obj.HeaderID != HeaderID.PolicyDecryptionKey) return null; obj.DataSize = data.ReadUInt16LittleEndian(ref offset); obj.TData = data.ReadBytes(ref offset, obj.DataSize); return obj; } /// /// Parse a Stream into a KeyProviderRecordExtraField /// /// Byte array to parse /// Offset into the byte array /// Filled KeyProviderRecordExtraField on success, null on error private static KeyProviderRecordExtraField? ParseKeyProviderRecordExtraField(byte[] data, ref int offset) { var obj = new KeyProviderRecordExtraField(); obj.HeaderID = (HeaderID)data.ReadUInt16LittleEndian(ref offset); if (obj.HeaderID != HeaderID.SmartcryptKeyProvider) return null; obj.DataSize = data.ReadUInt16LittleEndian(ref offset); obj.TData = data.ReadBytes(ref offset, obj.DataSize); return obj; } /// /// Parse a Stream into a PolicyKeyDataRecordRecordExtraField /// /// Byte array to parse /// Offset into the byte array /// Filled PolicyKeyDataRecordRecordExtraField on success, null on error private static PolicyKeyDataRecordRecordExtraField? ParsePolicyKeyDataRecordRecordExtraField(byte[] data, ref int offset) { var obj = new PolicyKeyDataRecordRecordExtraField(); obj.HeaderID = (HeaderID)data.ReadUInt16LittleEndian(ref offset); if (obj.HeaderID != HeaderID.SmartcryptPolicyKeyData) return null; obj.DataSize = data.ReadUInt16LittleEndian(ref offset); obj.TData = data.ReadBytes(ref offset, obj.DataSize); return obj; } /// /// Parse a Stream into a AS400ExtraFieldAttribute /// /// Byte array to parse /// Offset into the byte array /// Filled AS400ExtraFieldAttribute on success, null on error /// /// This header ID is shared with MVSExtraField, OS400ExtraField, and ZOSExtraFieldAttribute. /// This code makes an assumption that it's always AS400ExtraFieldAttribute. /// private static AS400ExtraFieldAttribute? ParseAS400ExtraFieldAttribute(byte[] data, ref int offset) { var obj = new AS400ExtraFieldAttribute(); obj.HeaderID = (HeaderID)data.ReadUInt16LittleEndian(ref offset); if (obj.HeaderID != HeaderID.IBMS390AttributesUncompressed) return null; obj.DataSize = data.ReadUInt16LittleEndian(ref offset); obj.FieldLength = data.ReadUInt16BigEndian(ref offset); obj.FieldCode = (AS400ExtraFieldAttributeFieldCode)data.ReadUInt16LittleEndian(ref offset); obj.Data = data.ReadBytes(ref offset, obj.DataSize - 4); return obj; } /// /// Parse a Stream into a ZipItMacintoshExtraField /// /// Byte array to parse /// Offset into the byte array /// Filled ZipItMacintoshExtraField on success, null on error private static ZipItMacintoshExtraField? ParseZipItMacintoshExtraField(byte[] data, ref int offset) { var obj = new ZipItMacintoshExtraField(); obj.HeaderID = (HeaderID)data.ReadUInt16LittleEndian(ref offset); if (obj.HeaderID != HeaderID.ZipItMacintosh) return null; obj.DataSize = data.ReadUInt16LittleEndian(ref offset); obj.ExtraFieldSignature = data.ReadUInt32LittleEndian(ref offset); obj.FnLen = data.ReadByteValue(ref offset); byte[] filenameBytes = data.ReadBytes(ref offset, obj.FnLen); obj.FileName = Encoding.ASCII.GetString(filenameBytes); obj.FileType = data.ReadBytes(ref offset, 4); obj.Creator = data.ReadBytes(ref offset, 4); return obj; } /// /// Parse a Stream into a ZipItMacintoshShortFileExtraField /// /// Byte array to parse /// Offset into the byte array /// Filled ZipItMacintoshShortFileExtraField on success, null on error private static ZipItMacintoshShortFileExtraField? ParseZipItMacintoshShortFileExtraField(byte[] data, ref int offset) { var obj = new ZipItMacintoshShortFileExtraField(); obj.HeaderID = (HeaderID)data.ReadUInt16LittleEndian(ref offset); if (obj.HeaderID != HeaderID.ZipItMacintosh135Plus) return null; obj.DataSize = data.ReadUInt16LittleEndian(ref offset); obj.ExtraFieldSignature = data.ReadUInt32LittleEndian(ref offset); obj.FileType = data.ReadBytes(ref offset, 4); obj.Creator = data.ReadBytes(ref offset, 4); if (obj.DataSize > 12) obj.FdFlags = data.ReadUInt16LittleEndian(ref offset); if (obj.DataSize > 14) obj.Reserved = data.ReadUInt16LittleEndian(ref offset); return obj; } /// /// Parse a Stream into a ZipItMacintoshShortDirectoryExtraField /// /// Byte array to parse /// Offset into the byte array /// Filled ZipItMacintoshShortDirectoryExtraField on success, null on error private static ZipItMacintoshShortDirectoryExtraField? ParseZipItMacintoshShortDirectoryExtraField(byte[] data, ref int offset) { var obj = new ZipItMacintoshShortDirectoryExtraField(); obj.HeaderID = (HeaderID)data.ReadUInt16LittleEndian(ref offset); if (obj.HeaderID != HeaderID.ZipItMacintosh135PlusAlt) return null; obj.DataSize = data.ReadUInt16LittleEndian(ref offset); obj.ExtraFieldSignature = data.ReadUInt32LittleEndian(ref offset); if (obj.DataSize > 4) obj.FrFlags = data.ReadUInt16LittleEndian(ref offset); if (obj.DataSize > 6) obj.View = (ZipItInternalSettings)data.ReadUInt16LittleEndian(ref offset); return obj; } /// /// Parse a Stream into a FWKCSMD5ExtraField /// /// Byte array to parse /// Offset into the byte array /// Filled FWKCSMD5ExtraField on success, null on error private static FWKCSMD5ExtraField? ParseFWKCSMD5ExtraField(byte[] data, ref int offset) { var obj = new FWKCSMD5ExtraField(); obj.HeaderID = (HeaderID)data.ReadUInt16LittleEndian(ref offset); if (obj.HeaderID != HeaderID.FWKCSMD5) return null; obj.DataSize = data.ReadUInt16LittleEndian(ref offset); obj.Preface = data.ReadBytes(ref offset, 3); obj.MD5 = data.ReadBytes(ref offset, 16); return obj; } /// /// Parse a Stream into a InfoZIPUnicodeCommentExtraField /// /// Byte array to parse /// Offset into the byte array /// Filled InfoZIPUnicodeCommentExtraField on success, null on error private static InfoZIPUnicodeCommentExtraField? ParseInfoZIPUnicodeCommentExtraField(byte[] data, ref int offset) { var obj = new InfoZIPUnicodeCommentExtraField(); obj.HeaderID = (HeaderID)data.ReadUInt16LittleEndian(ref offset); if (obj.HeaderID != HeaderID.InfoZIPUnicodeComment) return null; obj.DataSize = data.ReadUInt16LittleEndian(ref offset); obj.Version = data.ReadByteValue(ref offset); obj.ComCRC32 = data.ReadUInt32LittleEndian(ref offset); byte[] unicodeBytes = data.ReadBytes(ref offset, obj.DataSize - 5); obj.UnicodeCom = Encoding.UTF8.GetString(unicodeBytes); return obj; } /// /// Parse a Stream into a InfoZIPUnicodePathExtraField /// /// Byte array to parse /// Offset into the byte array /// Filled InfoZIPUnicodePathExtraField on success, null on error private static InfoZIPUnicodePathExtraField? ParseInfoZIPUnicodePathExtraField(byte[] data, ref int offset) { var obj = new InfoZIPUnicodePathExtraField(); obj.HeaderID = (HeaderID)data.ReadUInt16LittleEndian(ref offset); if (obj.HeaderID != HeaderID.InfoZIPUnicodePath) return null; obj.DataSize = data.ReadUInt16LittleEndian(ref offset); obj.Version = data.ReadByteValue(ref offset); obj.NameCRC32 = data.ReadUInt32LittleEndian(ref offset); byte[] unicodeBytes = data.ReadBytes(ref offset, obj.DataSize - 5); obj.UnicodeName = Encoding.UTF8.GetString(unicodeBytes); return obj; } /// /// Parse a Stream into a DataStreamAlignment /// /// Byte array to parse /// Offset into the byte array /// Filled DataStreamAlignment on success, null on error private static DataStreamAlignment? ParseDataStreamAlignment(byte[] data, ref int offset) { var obj = new DataStreamAlignment(); obj.HeaderID = (HeaderID)data.ReadUInt16LittleEndian(ref offset); if (obj.HeaderID != HeaderID.DataStreamAlignment) return null; obj.DataSize = data.ReadUInt16LittleEndian(ref offset); obj.Alignment = data.ReadUInt16LittleEndian(ref offset); obj.Padding = data.ReadBytes(ref offset, obj.DataSize - 2); return obj; } /// /// Parse a Stream into a MicrosoftOpenPackagingGrowthHint /// /// Byte array to parse /// Offset into the byte array /// Filled MicrosoftOpenPackagingGrowthHint on success, null on error private static MicrosoftOpenPackagingGrowthHint? ParseMicrosoftOpenPackagingGrowthHint(byte[] data, ref int offset) { var obj = new MicrosoftOpenPackagingGrowthHint(); obj.HeaderID = (HeaderID)data.ReadUInt16LittleEndian(ref offset); if (obj.HeaderID != HeaderID.MicrosoftOpenPackagingGrowthHint) return null; obj.DataSize = data.ReadUInt16LittleEndian(ref offset); obj.Sig = data.ReadUInt16LittleEndian(ref offset); obj.PadVal = data.ReadUInt16LittleEndian(ref offset); obj.Padding = data.ReadBytes(ref offset, obj.DataSize - 2); return obj; } #endregion #region Helpers /// /// Determine if a data descriptor is 64-bit /// /// Stream to parse /// True if the descriptor is 64-bit, false otherwise private static bool IsZip64Descriptor(Stream data) { // Last item in the stream if (data.Position + 12 == data.Length) // Short 32-bit return false; else if (data.Position + 16 == data.Length) // Long 32-bit return false; else if (data.Position + 20 == data.Length) // Short 64-bit return true; else if (data.Position + 24 == data.Length) // Long 64-bit return true; // Cache the current position long currentPosition = data.Position; // Short 32-bit data.SeekIfPossible(12, SeekOrigin.Current); byte[] nextBlock = data.ReadBytes(2); data.SeekIfPossible(currentPosition, SeekOrigin.Begin); if (nextBlock.EqualsExactly([0x50, 0x4B])) return false; // Long 32-bit data.SeekIfPossible(16, SeekOrigin.Current); nextBlock = data.ReadBytes(2); data.SeekIfPossible(currentPosition, SeekOrigin.Begin); if (nextBlock.EqualsExactly([0x50, 0x4B])) return false; // Assume 64-bit otherwise return true; } #endregion } }