Implement gzip parsing and printing

This commit is contained in:
Matt Nadareski
2025-08-28 09:56:52 -04:00
parent 2ec008b3b7
commit 9feaadd32a
3 changed files with 250 additions and 2 deletions

View File

@@ -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;
}
}
/// <summary>
/// Parse a Stream into a Header
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled Header on success, null on error</returns>
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<ExtraFieldData> 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;
}
/// <summary>
/// Parse a Stream into an ExtraFieldData
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled ExtraFieldData on success, null on error</returns>
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;
}
/// <summary>
/// Parse a Stream into a Trailer
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled Trailer on success, null on error</returns>
public static Trailer? ParseTrailer(Stream data)
{
var obj = new Trailer();
obj.CRC32 = data.ReadUInt32LittleEndian();
obj.ISIZE = data.ReadUInt32LittleEndian();
return obj;
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -15,6 +15,62 @@ namespace SabreTools.Serialization.Wrappers
#endregion
#region Extension Properties
/// <summary>
/// Offset to the compressed data
/// </summary>
/// <remarks>Returns -1 on error</remarks>
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;
}
}
/// <inheritdoc cref="Archive.Header"/>
public Header? Header => Model.Header;
/// <inheritdoc cref="Archive.Trailer"/>
public Trailer? Trailer => Model.Trailer;
#endregion
#region Instance Variables
/// <summary>
/// Offset to the compressed data
/// </summary>
private long? _dataOffset = null;
#endregion
#region Constructors
/// <inheritdoc/>