diff --git a/SabreTools.Serialization/Deserializers/GZip.cs b/SabreTools.Serialization/Deserializers/GZip.cs index 936cd9ba..186f46f9 100644 --- a/SabreTools.Serialization/Deserializers/GZip.cs +++ b/SabreTools.Serialization/Deserializers/GZip.cs @@ -1,5 +1,8 @@ +using System.Collections.Generic; using System.IO; +using SabreTools.IO.Extensions; using SabreTools.Models.GZIP; +using static SabreTools.Models.GZIP.Constants; namespace SabreTools.Serialization.Deserializers { @@ -22,7 +25,28 @@ namespace SabreTools.Serialization.Deserializers var archive = new Archive(); - // TODO: Implement everything + #region Header + + var header = ParseHeader(data); + if (header == null) + return null; + + archive.Header = header; + + #endregion + + // Seek to the end to read the trailer + data.Seek(-8, SeekOrigin.End); + + #region Trailer + + var trailer = ParseTrailer(data); + if (header == null) + return null; + + archive.Trailer = trailer; + + #endregion return archive; } @@ -32,5 +56,106 @@ namespace SabreTools.Serialization.Deserializers return null; } } + + /// + /// Parse a Stream into a Header + /// + /// Stream to parse + /// Filled Header on success, null on error + public static Header? ParseHeader(Stream data) + { + var obj = new Header(); + + obj.ID1 = data.ReadByteValue(); + obj.ID2 = data.ReadByteValue(); + if (obj.ID1 != ID1 || obj.ID2 != ID2) + return null; + + obj.CM = (CompressionMethod)data.ReadByteValue(); + obj.FLG = (Flags)data.ReadByteValue(); + obj.MTIME = data.ReadUInt32LittleEndian(); + obj.XFL = (ExtraFlags)data.ReadByteValue(); + obj.OS = (OperatingSystem)data.ReadByteValue(); + +#if NET20 || NET35 + if ((obj.FLG & Flags.FEXTRA) != 0) +#else + if (obj.FLG.HasFlag(Flags.FEXTRA)) +#endif + { + obj.XLEN = data.ReadUInt16LittleEndian(); + + // Cache the current position + long currentPosition = data.Position; + + List extraFields = []; + while (data.Position < currentPosition + obj.XLEN) + { + var extraField = ParseExtraFieldData(data); + if (extraField == null) + break; + + extraFields.Add(extraField); + } + + obj.ExtraField = [.. extraFields]; + } + +#if NET20 || NET35 + if ((obj.FLG & Flags.FNAME) != 0) +#else + if (obj.FLG.HasFlag(Flags.FNAME)) +#endif + obj.OriginalFileName = data.ReadNullTerminatedAnsiString(); + +#if NET20 || NET35 + if ((obj.FLG & Flags.FCOMMENT) != 0) +#else + if (obj.FLG.HasFlag(Flags.FCOMMENT)) +#endif + obj.FileComment = data.ReadNullTerminatedAnsiString(); + + +#if NET20 || NET35 + if ((obj.FLG & Flags.FHCRC) != 0) +#else + if (obj.FLG.HasFlag(Flags.FHCRC)) +#endif + obj.CRC16 = data.ReadUInt16LittleEndian(); + + return obj; + } + + /// + /// Parse a Stream into an ExtraFieldData + /// + /// Stream to parse + /// Filled ExtraFieldData on success, null on error + public static ExtraFieldData? ParseExtraFieldData(Stream data) + { + var obj = new ExtraFieldData(); + + obj.SI1 = data.ReadByteValue(); + obj.SI2 = data.ReadByteValue(); + obj.LEN = data.ReadUInt16LittleEndian(); + obj.Data = data.ReadBytes(obj.LEN); + + return obj; + } + + /// + /// Parse a Stream into a Trailer + /// + /// Stream to parse + /// Filled Trailer on success, null on error + public static Trailer? ParseTrailer(Stream data) + { + var obj = new Trailer(); + + obj.CRC32 = data.ReadUInt32LittleEndian(); + obj.ISIZE = data.ReadUInt32LittleEndian(); + + return obj; + } } } diff --git a/SabreTools.Serialization/Printers/GZip.cs b/SabreTools.Serialization/Printers/GZip.cs index 2f71cc10..6c5cf076 100644 --- a/SabreTools.Serialization/Printers/GZip.cs +++ b/SabreTools.Serialization/Printers/GZip.cs @@ -16,7 +16,74 @@ namespace SabreTools.Serialization.Printers builder.AppendLine("-------------------------"); builder.AppendLine(); - // TODO: Implement + Print(builder, file.Header); + // TODO: Capture or print the compressed data + Print(builder, file.Trailer); + } + + private static void Print(StringBuilder builder, Header? header) + { + builder.AppendLine(" Header:"); + builder.AppendLine(" -------------------------"); + if (header == null) + { + builder.AppendLine(" No header"); + builder.AppendLine(); + return; + } + + builder.AppendLine(header.ID1, " ID1"); + builder.AppendLine(header.ID2, " ID1"); + builder.AppendLine($" Compression method: {header.CM} (0x{(byte)header.CM:X2})"); + builder.AppendLine($" Flags: {header.FLG} (0x{(byte)header.FLG:X2})"); + builder.AppendLine(header.MTIME, " Last modified time"); + builder.AppendLine($" Extra flags: {header.XFL} (0x{(byte)header.XFL:X2})"); + builder.AppendLine($" Operating system: {header.OS} (0x{(byte)header.OS:X2})"); + builder.AppendLine(header.XLEN, " Extra length"); + Print(builder, header.ExtraField); + builder.AppendLine(header.OriginalFileName, " Original file name"); + builder.AppendLine(header.FileComment, " File comment"); + builder.AppendLine(header.CRC16, " CRC-16"); + builder.AppendLine(); + } + + private static void Print(StringBuilder builder, ExtraFieldData[]? entries) + { + builder.AppendLine(" Extra Fields:"); + builder.AppendLine(" -------------------------"); + if (entries == null || entries.Length == 0) + { + builder.AppendLine(" No extra fields"); + builder.AppendLine(); + return; + } + + for (int i = 0; i < entries.Length; i++) + { + var entry = entries[i]; + + builder.AppendLine($" Extra Field {i}:"); + builder.AppendLine(entry.SI1, " Subfield ID1"); + builder.AppendLine(entry.SI2, " Subfield ID2"); + builder.AppendLine(entry.LEN, " Length"); + builder.AppendLine(entry.Data, " Data"); + } + } + + private static void Print(StringBuilder builder, Trailer? trailer) + { + builder.AppendLine(" Trailer:"); + builder.AppendLine(" -------------------------"); + if (trailer == null) + { + builder.AppendLine(" No trailer"); + builder.AppendLine(); + return; + } + + builder.AppendLine(trailer.CRC32, " CRC-32"); + builder.AppendLine(trailer.ISIZE, " Input size"); + builder.AppendLine(); } } } diff --git a/SabreTools.Serialization/Wrappers/GZip.cs b/SabreTools.Serialization/Wrappers/GZip.cs index b5f6e080..d75c1481 100644 --- a/SabreTools.Serialization/Wrappers/GZip.cs +++ b/SabreTools.Serialization/Wrappers/GZip.cs @@ -15,6 +15,62 @@ namespace SabreTools.Serialization.Wrappers #endregion + #region Extension Properties + + /// + /// Offset to the compressed data + /// + /// Returns -1 on error + public long DataOffset + { + get + { + if (_dataOffset != null) + return _dataOffset.Value; + + if (Header == null) + return -1; + + // Minimum offset is 10 bytes: + // - ID1 (1) + // - ID2 (1) + // - CompressionMethod (1) + // - Flags (1) + // - LastModifiedTime (4) + // - ExtraFlags (1) + // - OperatingSystem (1) + _dataOffset = 10; + + // Add extra lengths + _dataOffset += Header.XLEN; + if (Header.OriginalFileName != null) + _dataOffset += Header.OriginalFileName.Length + 1; + if (Header.FileComment != null) + _dataOffset += Header.FileComment.Length + 1; + if (Header.CRC16 != null) + _dataOffset += 2; + + return _dataOffset.Value; + } + } + + /// + public Header? Header => Model.Header; + + /// + public Trailer? Trailer => Model.Trailer; + + #endregion + + #region Instance Variables + + /// + /// Offset to the compressed data + /// + private long? _dataOffset = null; + + #endregion + #region Constructors ///