mirror of
https://github.com/SabreTools/SabreTools.Serialization.git
synced 2026-11-15 17:00:38 +00:00
* Locate the PKZIP file data instead of reading it into the model Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Keep only the offset of the PKZIP file data The length is the compressed size from the local file header or the ZIP64 extended field, which the printer already shows. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Keep the file data property as a comment for documentation Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
1501 lines
68 KiB
C#
1501 lines
68 KiB
C#
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<Archive>
|
|
{
|
|
/// <inheritdoc/>
|
|
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<LocalFile>();
|
|
var cdrs = new List<CentralDirectoryFileHeader>();
|
|
|
|
// 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;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a Stream into an archive extra data record
|
|
/// </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)
|
|
{
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a Stream into a central directory file header
|
|
/// </summary>
|
|
/// <param name="data">Stream to parse</param>
|
|
/// <param name="includeDebug">True to include debug data, false otherwise</param>
|
|
/// <returns>Filled central directory file header on success, null on error</returns>
|
|
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;
|
|
}
|
|
|
|
/// <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 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;
|
|
}
|
|
|
|
/// <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 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;
|
|
}
|
|
|
|
/// <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.SeekIfPossible(-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 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;
|
|
}
|
|
|
|
/// <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 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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a Stream into a local file
|
|
/// </summary>
|
|
/// <param name="data">Stream to parse</param>
|
|
/// <param name="includeDebug">True to include debug data, false otherwise</param>
|
|
/// <returns>Filled local file on success, null on error</returns>
|
|
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
|
|
|
|
// The file data is skipped, so a large archive is not read into memory
|
|
if (compressedSize > (ulong)(data.Length - data.Position))
|
|
return null;
|
|
|
|
// Set the location of the file data
|
|
obj.FileDataOffset = data.Position;
|
|
data.SeekIfPossible((long)compressedSize, SeekOrigin.Current);
|
|
|
|
#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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a Stream into a local file header
|
|
/// </summary>
|
|
/// <param name="data">Stream to parse</param>
|
|
/// <param name="includeDebug">True to include debug data, false otherwise</param>
|
|
/// <returns>Filled local file header on success, null on error</returns>
|
|
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
|
|
|
|
/// <summary>
|
|
/// Process all extensible data fields in a central directory file extras block
|
|
/// </summary>
|
|
/// <param name="header">Central directory file header</param>
|
|
/// <param name="data">Byte array to parse</param>
|
|
/// <param name="includeDebug">True to include debug data, false otherwise</param>
|
|
/// <returns>Array of data fields on success, null otherwise</returns>
|
|
public static ExtensibleDataField[]? ParseExtraFields(CentralDirectoryFileHeader header, byte[]? data, bool includeDebug = false)
|
|
{
|
|
if (data is null)
|
|
return null;
|
|
|
|
List<ExtensibleDataField> 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];
|
|
}
|
|
|
|
/// <summary>
|
|
/// Process all extensible data fields in a local file extras block
|
|
/// </summary>
|
|
/// <param name="header">Local file header</param>
|
|
/// <param name="data">Byte array to parse</param>
|
|
/// <param name="includeDebug">True to include debug data, false otherwise</param>
|
|
/// <returns>Array of data fields on success, null otherwise</returns>
|
|
public static ExtensibleDataField[]? ParseExtraFields(LocalFileHeader header, byte[]? data, bool includeDebug = false)
|
|
{
|
|
if (data is null)
|
|
return null;
|
|
|
|
List<ExtensibleDataField> 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];
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a Stream into an unknown extras field
|
|
/// </summary>
|
|
/// <param name="data">Byte array to parse</param>
|
|
/// <param name="offset">Offset into the byte array</param>
|
|
/// <param name="includeDebug">True to include debug data, false otherwise</param>
|
|
/// <returns>Filled unknown extras field on success, null on error</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a Stream into a Zip64ExtendedInformationExtraField
|
|
/// </summary>
|
|
/// <param name="data">Byte array to parse</param>
|
|
/// <param name="offset">Offset into the byte array</param>
|
|
/// <returns>Filled Zip64ExtendedInformationExtraField on success, null on error</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a Stream into a Zip64ExtendedInformationExtraField
|
|
/// </summary>
|
|
/// <param name="data">Byte array to parse</param>
|
|
/// <param name="offset">Offset into the byte array</param>
|
|
/// <returns>Filled Zip64ExtendedInformationExtraField on success, null on error</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a Stream into an OS2ExtraField
|
|
/// </summary>
|
|
/// <param name="data">Byte array to parse</param>
|
|
/// <param name="offset">Offset into the byte array</param>
|
|
/// <returns>Filled OS2ExtraField on success, null on error</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a Stream into a NTFSExtraField
|
|
/// </summary>
|
|
/// <param name="data">Byte array to parse</param>
|
|
/// <param name="offset">Offset into the byte array</param>
|
|
/// <returns>Filled NTFSExtraField on success, null on error</returns>
|
|
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<TagSizeVar> 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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a Stream into an OpenVMSExtraField
|
|
/// </summary>
|
|
/// <param name="data">Byte array to parse</param>
|
|
/// <param name="offset">Offset into the byte array</param>
|
|
/// <returns>Filled OpenVMSExtraField on success, null on error</returns>
|
|
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<TagSizeVar> 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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a Stream into a UnixExtraField
|
|
/// </summary>
|
|
/// <param name="data">Byte array to parse</param>
|
|
/// <param name="offset">Offset into the byte array</param>
|
|
/// <returns>Filled UnixExtraField on success, null on error</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a Stream into a PatchDescriptorExtraField
|
|
/// </summary>
|
|
/// <param name="data">Byte array to parse</param>
|
|
/// <param name="offset">Offset into the byte array</param>
|
|
/// <returns>Filled PatchDescriptorExtraField on success, null on error</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a Stream into a PKCS7Store
|
|
/// </summary>
|
|
/// <param name="data">Byte array to parse</param>
|
|
/// <param name="offset">Offset into the byte array</param>
|
|
/// <returns>Filled PKCS7Store on success, null on error</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a Stream into a X509IndividualFile
|
|
/// </summary>
|
|
/// <param name="data">Byte array to parse</param>
|
|
/// <param name="offset">Offset into the byte array</param>
|
|
/// <returns>Filled X509IndividualFile on success, null on error</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a Stream into a X509CentralDirectory
|
|
/// </summary>
|
|
/// <param name="data">Byte array to parse</param>
|
|
/// <param name="offset">Offset into the byte array</param>
|
|
/// <returns>Filled X509CentralDirectory on success, null on error</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a Stream into a StrongEncryptionHeader
|
|
/// </summary>
|
|
/// <param name="data">Byte array to parse</param>
|
|
/// <param name="offset">Offset into the byte array</param>
|
|
/// <returns>Filled StrongEncryptionHeader on success, null on error</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a Stream into an RecordManagementControls
|
|
/// </summary>
|
|
/// <param name="data">Byte array to parse</param>
|
|
/// <param name="offset">Offset into the byte array</param>
|
|
/// <returns>Filled RecordManagementControls on success, null on error</returns>
|
|
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<TagSizeVar> 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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a Stream into an PKCS7EncryptionRecipientCertificateList
|
|
/// </summary>
|
|
/// <param name="data">Byte array to parse</param>
|
|
/// <param name="offset">Offset into the byte array</param>
|
|
/// <returns>Filled PKCS7EncryptionRecipientCertificateList on success, null on error</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a Stream into a PolicyDecryptionKeyRecordExtraField
|
|
/// </summary>
|
|
/// <param name="data">Byte array to parse</param>
|
|
/// <param name="offset">Offset into the byte array</param>
|
|
/// <returns>Filled PolicyDecryptionKeyRecordExtraField on success, null on error</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a Stream into a KeyProviderRecordExtraField
|
|
/// </summary>
|
|
/// <param name="data">Byte array to parse</param>
|
|
/// <param name="offset">Offset into the byte array</param>
|
|
/// <returns>Filled KeyProviderRecordExtraField on success, null on error</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a Stream into a PolicyKeyDataRecordRecordExtraField
|
|
/// </summary>
|
|
/// <param name="data">Byte array to parse</param>
|
|
/// <param name="offset">Offset into the byte array</param>
|
|
/// <returns>Filled PolicyKeyDataRecordRecordExtraField on success, null on error</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a Stream into a AS400ExtraFieldAttribute
|
|
/// </summary>
|
|
/// <param name="data">Byte array to parse</param>
|
|
/// <param name="offset">Offset into the byte array</param>
|
|
/// <returns>Filled AS400ExtraFieldAttribute on success, null on error</returns>
|
|
/// <remarks>
|
|
/// This header ID is shared with MVSExtraField, OS400ExtraField, and ZOSExtraFieldAttribute.
|
|
/// This code makes an assumption that it's always AS400ExtraFieldAttribute.
|
|
/// </remarks>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a Stream into a ZipItMacintoshExtraField
|
|
/// </summary>
|
|
/// <param name="data">Byte array to parse</param>
|
|
/// <param name="offset">Offset into the byte array</param>
|
|
/// <returns>Filled ZipItMacintoshExtraField on success, null on error</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a Stream into a ZipItMacintoshShortFileExtraField
|
|
/// </summary>
|
|
/// <param name="data">Byte array to parse</param>
|
|
/// <param name="offset">Offset into the byte array</param>
|
|
/// <returns>Filled ZipItMacintoshShortFileExtraField on success, null on error</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a Stream into a ZipItMacintoshShortDirectoryExtraField
|
|
/// </summary>
|
|
/// <param name="data">Byte array to parse</param>
|
|
/// <param name="offset">Offset into the byte array</param>
|
|
/// <returns>Filled ZipItMacintoshShortDirectoryExtraField on success, null on error</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a Stream into a FWKCSMD5ExtraField
|
|
/// </summary>
|
|
/// <param name="data">Byte array to parse</param>
|
|
/// <param name="offset">Offset into the byte array</param>
|
|
/// <returns>Filled FWKCSMD5ExtraField on success, null on error</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a Stream into a InfoZIPUnicodeCommentExtraField
|
|
/// </summary>
|
|
/// <param name="data">Byte array to parse</param>
|
|
/// <param name="offset">Offset into the byte array</param>
|
|
/// <returns>Filled InfoZIPUnicodeCommentExtraField on success, null on error</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a Stream into a InfoZIPUnicodePathExtraField
|
|
/// </summary>
|
|
/// <param name="data">Byte array to parse</param>
|
|
/// <param name="offset">Offset into the byte array</param>
|
|
/// <returns>Filled InfoZIPUnicodePathExtraField on success, null on error</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a Stream into a DataStreamAlignment
|
|
/// </summary>
|
|
/// <param name="data">Byte array to parse</param>
|
|
/// <param name="offset">Offset into the byte array</param>
|
|
/// <returns>Filled DataStreamAlignment on success, null on error</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a Stream into a MicrosoftOpenPackagingGrowthHint
|
|
/// </summary>
|
|
/// <param name="data">Byte array to parse</param>
|
|
/// <param name="offset">Offset into the byte array</param>
|
|
/// <returns>Filled MicrosoftOpenPackagingGrowthHint on success, null on error</returns>
|
|
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
|
|
|
|
/// <summary>
|
|
/// Determine if a data descriptor is 64-bit
|
|
/// </summary>
|
|
/// <param name="data">Stream to parse</param>
|
|
/// <returns>True if the descriptor is 64-bit, false otherwise</returns>
|
|
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
|
|
}
|
|
}
|