mirror of
https://github.com/SabreTools/SabreTools.Serialization.git
synced 2026-09-22 14:55:11 +00:00
Implement gzip parsing and printing
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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/>
|
||||
|
||||
Reference in New Issue
Block a user