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
///