Add XZ models

This commit is contained in:
Matt Nadareski
2025-09-29 22:56:53 -04:00
parent 6b7b05eb31
commit bfb206a06d
13 changed files with 296 additions and 8 deletions

View File

@@ -45,6 +45,7 @@ Not all of this information was able to be gathered directly from the files in q
| [Technical Committee T10](https://www.t10.org/) | PIC |
| [The Go tools for Windows + Assembler](https://www.godevtool.com/) | PortableExecutable |
| [The Whole Half-Life](https://twhl.info/wiki/page/Specification:_WAD3) | WAD3 |
| [Tukaani](https://tukaani.org/xz/format.html) | XZ |
| [Unshield](https://github.com/twogood/unshield) | InstallShieldCabinet |
| [unshieldv3](https://github.com/wfr/unshieldv3) | InstallShieldArchiveV3 |
| [Valve Developer Community](https://developer.valvesoftware.com/wiki/Main_Page) | BSP, VPK |

View File

@@ -1,10 +1,26 @@
namespace SabreTools.Data.Models.XZ
{
/// <summary>
/// This is a placeholder model for future work
/// </summary>
/// <see href="https://tukaani.org/xz/xz-file-format.txt"/>
public class Archive
{
/// <summary>
/// Pre-blocks header
/// </summary>
public Header? Header { get; set; }
/// <summary>
/// Sequence of 0 or more blocks
/// </summary>
public Block[]? Blocks { get; set; }
/// <summary>
/// Index structure
/// </summary>
public Index? Index { get; set; }
/// <summary>
/// Post-blocks footer
/// </summary>
public Footer? Footer { get; set; }
}
}

View File

@@ -0,0 +1,80 @@
namespace SabreTools.Data.Models.XZ
{
/// <summary>
/// Represents a single compressed block in the stream
/// </summary>
public class Block
{
/// <summary>
/// Size of the header
/// </summary>
/// <remarks>
/// The real header size can be calculated by the following:
/// (HeaderSize + 1) * 4
/// </remarks>
public byte HeaderSize { get; set; }
/// <summary>
/// The Block Flags field is a bit field
/// </summary>
public BlockFlags Flags { get; set; }
/// <summary>
/// Size of the compressed data
/// Present if <see cref="BlockFlags.CompressedSize"/> is set.
/// </summary>
/// <remarks>Stored as a variable-length integer</remarks>
public byte[]? CompressedSize { get; set; }
/// <summary>
/// Size of the block after decompression
/// Present if <see cref="BlockFlags.UncompressedSize"/> is set.
/// </summary>
/// <remarks>Stored as a variable-length integer</remarks>
public byte[]? UncompressedSize { get; set; }
/// <summary>
/// List of filter flags
/// </summary>
/// <remarks>
/// The number of filter flags is given by the first two
/// bits of <see cref="Flags"/>
/// </remarks>
public FilterFlag[]? FilterFlags { get; set; }
/// <summary>
/// This field contains as many null byte as it is needed to make
/// the Block Header have the size specified in Block Header Size.
/// </summary>
public byte[]? HeaderPadding { get; set; }
/// <summary>
/// The CRC32 is calculated over everything in the Block Header
/// field except the CRC32 field itself. It is stored as an
/// unsigned 32-bit little endian integer.
/// </summary>
public uint Crc32 { get; set; }
/// <summary>
/// The format of Compressed Data depends on Block Flags and List
/// of Filter Flags
/// </summary>
public byte[]? CompressedData { get; set; }
/// <summary>
/// Block Padding MUST contain 0-3 null bytes to make the size of
/// the Block a multiple of four bytes. This can be needed when
/// the size of Compressed Data is not a multiple of four.
/// </summary>
public byte[]? BlockPadding { get; set; }
/// <summary>
/// The type and size of the Check field depends on which bits
/// are set in the Stream Flags field.
///
/// The Check, when used, is calculated from the original
/// uncompressed data.
/// </summary>
public byte[]? Check { get; set; }
}
}

View File

@@ -2,6 +2,8 @@ namespace SabreTools.Data.Models.XZ
{
public static class Constants
{
public static readonly byte[] SignatureBytes = [0xFD, 0x37, 0x7A, 0x58, 0x5A, 0x00];
public static readonly byte[] HeaderSignatureBytes = [0xFD, 0x37, 0x7A, 0x58, 0x5A, 0x00];
public static readonly byte[] FooterSignatureBytes = [0x59, 0x5A];
}
}

View File

@@ -0,0 +1,47 @@
using System;
namespace SabreTools.Data.Models.XZ
{
[Flags]
public enum BlockFlags : byte
{
#region Bits 0-1 - Number of filters
OneFilter = 0x00,
TwoFilters = 0x01,
ThreeFiltrs = 0x02,
FourFilters = 0x03,
#endregion
/// <summary>
/// Compressed size field present
/// </summary>
CompressedSize = 0x40,
/// <summary>
/// Uncompressed size field present
/// </summary>
UncompressedSize = 0x80,
}
public enum HeaderFlags : ushort
{
None = 0x0000,
Crc32 = 0x0001,
Reserved0x02 = 0x0002,
Reserved0x03 = 0x0003,
Crc64 = 0x0004,
Reserved0x05 = 0x0005,
Reserved0x06 = 0x0006,
Reserved0x07 = 0x0007,
Reserved0x08 = 0x0008,
Reserved0x09 = 0x0009,
Sha256 = 0x000A,
Reserved0x0B = 0x000B,
Reserved0x0C = 0x000C,
Reserved0x0D = 0x000D,
Reserved0x0E = 0x000E,
Reserved0x0F = 0x000F,
}
}

View File

@@ -0,0 +1,23 @@
namespace SabreTools.Data.Models.XZ
{
public class FilterFlag
{
/// <summary>
/// Filter ID
/// </summary>
/// <remarks>Stored as a variable-length integer</remarks>
public byte[]? FilterID { get; set; }
/// <summary>
/// Filter ID
/// </summary>
/// <remarks>Stored as a variable-length integer</remarks>
public byte[]? SizeOfProperties { get; set; }
/// <summary>
/// Properties of the filter whose length is given by
/// <see cref="SizeOfProperties"/>
/// </summary>
public byte[]? Properties { get; set; }
}
}

View File

@@ -0,0 +1,39 @@
namespace SabreTools.Data.Models.XZ
{
/// <summary>
/// Represents the post-block data in the stream
/// </summary>
public class Footer
{
/// <summary>
/// The CRC32 is calculated from the Backward Size and Stream Flags
/// fields. It is stored as an unsigned 32-bit little endian
/// integer.
/// </summary>
public uint Crc32 { get; set; }
/// <summary>
/// Backward Size is stored as a 32-bit little endian integer,
/// which indicates the size of the Index field as multiple of
/// four bytes, minimum value being four bytes.
/// </summary>
/// <remarks>
/// The real index size can be calculated by the following:
/// (BackwardSize + 1) * 4
/// </remarks>
public uint BackwardSize { get; set; }
/// <summary>
/// This is a copy of the Stream Flags field from the Stream
/// Header. The information stored to Stream Flags is needed
/// when parsing the Stream backwards.
/// </summary>
public HeaderFlags Flags { get; set; }
/// <summary>
/// Header magic number ("YZ")
/// </summary>
/// <remarks>2 bytes</remarks>
public byte[]? Signature { get; set; }
}
}

View File

@@ -0,0 +1,27 @@
namespace SabreTools.Data.Models.XZ
{
/// <summary>
/// Represents the pre-block data in the stream
/// </summary>
public class Header
{
/// <summary>
/// Header magic number (0xFD, '7', 'z', 'X', 'Z', 0x00)
/// </summary>
/// <remarks>6 bytes</remarks>
public byte[]? Signature { get; set; }
/// <summary>
/// The first byte of Stream Flags is always a null byte. In the
/// future, this byte may be used to indicate a new Stream version
/// or other Stream properties.
/// </summary>
public HeaderFlags Flags { get; set; }
/// <summary>
/// The CRC32 is calculated from the Stream Flags field. It is
/// stored as an unsigned 32-bit little endian integer.
/// </summary>
public uint Crc32 { get; set; }
}
}

View File

@@ -0,0 +1,36 @@
namespace SabreTools.Data.Models.XZ
{
public class Index
{
/// <summary>
/// The value of Index Indicator is always 0x00
/// </summary>
public byte IndexIndicator { get; set; }
/// <summary>
/// This field indicates how many Records there are in the List
/// of Records field, and thus how many Blocks there are in the
/// Stream
/// </summary>
/// <remarks>Stored as a variable-length integer</remarks>
public byte[]? NumberOfRecords { get; set; }
/// <summary>
/// One record per block
/// </summary>
public Record[]? Records { get; set; }
/// <summary>
/// This field MUST contain 0-3 null bytes to pad the Index to
/// a multiple of four bytes.
/// </summary>
public byte[]? Padding { get; set; }
/// <summary>
/// The CRC32 is calculated over everything in the Index field
/// except the CRC32 field itself. The CRC32 is stored as an
/// unsigned 32-bit little endian integer.
/// </summary>
public uint Crc32 { get; set; }
}
}

View File

@@ -0,0 +1,17 @@
namespace SabreTools.Data.Models.XZ
{
public class Record
{
/// <summary>
/// Unpadded size of the block
/// </summary>
/// <remarks>Stored as a variable-length integer</remarks>
public byte UnpaddedSize { get; set; }
/// <summary>
/// Uncompressed size of the block
/// </summary>
/// <remarks>Stored as a variable-length integer</remarks>
public byte[]? NumberOfRecords { get; set; }
}
}

View File

@@ -788,7 +788,7 @@ namespace SabreTools.Serialization
#region XZ
if (magic.StartsWith(Data.Models.XZ.Constants.SignatureBytes))
if (magic.StartsWith(Data.Models.XZ.Constants.HeaderSignatureBytes))
return WrapperType.XZ;
if (extension.Equals("xz", StringComparison.OrdinalIgnoreCase))

View File

@@ -132,7 +132,7 @@ namespace SabreTools.Serialization.Wrappers
extension = "xml";
break;
}
else if (overlaySample.StartsWith(Data.Models.XZ.Constants.SignatureBytes))
else if (overlaySample.StartsWith(Data.Models.XZ.Constants.HeaderSignatureBytes))
{
extension = "xz";
break;

View File

@@ -271,7 +271,7 @@ namespace SabreTools.Serialization.Wrappers
extension = "xml";
break;
}
else if (overlaySample.StartsWith(Data.Models.XZ.Constants.SignatureBytes))
else if (overlaySample.StartsWith(Data.Models.XZ.Constants.HeaderSignatureBytes))
{
extension = "xz";
break;
@@ -474,7 +474,7 @@ namespace SabreTools.Serialization.Wrappers
extension = "xml";
break;
}
else if (resourceSample.StartsWith(Data.Models.XZ.Constants.SignatureBytes))
else if (resourceSample.StartsWith(Data.Models.XZ.Constants.HeaderSignatureBytes))
{
extension = "xz";
break;