diff --git a/SabreTools.Serialization/Models/README.MD b/SabreTools.Serialization/Models/README.MD
index 81edc149..1a582ab3 100644
--- a/SabreTools.Serialization/Models/README.MD
+++ b/SabreTools.Serialization/Models/README.MD
@@ -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 |
diff --git a/SabreTools.Serialization/Models/XZ/Archive.cs b/SabreTools.Serialization/Models/XZ/Archive.cs
index 0d27eacf..1ae389ec 100644
--- a/SabreTools.Serialization/Models/XZ/Archive.cs
+++ b/SabreTools.Serialization/Models/XZ/Archive.cs
@@ -1,10 +1,26 @@
namespace SabreTools.Data.Models.XZ
{
- ///
- /// This is a placeholder model for future work
- ///
+ ///
public class Archive
{
+ ///
+ /// Pre-blocks header
+ ///
+ public Header? Header { get; set; }
+ ///
+ /// Sequence of 0 or more blocks
+ ///
+ public Block[]? Blocks { get; set; }
+
+ ///
+ /// Index structure
+ ///
+ public Index? Index { get; set; }
+
+ ///
+ /// Post-blocks footer
+ ///
+ public Footer? Footer { get; set; }
}
}
diff --git a/SabreTools.Serialization/Models/XZ/Block.cs b/SabreTools.Serialization/Models/XZ/Block.cs
new file mode 100644
index 00000000..590efefc
--- /dev/null
+++ b/SabreTools.Serialization/Models/XZ/Block.cs
@@ -0,0 +1,80 @@
+namespace SabreTools.Data.Models.XZ
+{
+ ///
+ /// Represents a single compressed block in the stream
+ ///
+ public class Block
+ {
+ ///
+ /// Size of the header
+ ///
+ ///
+ /// The real header size can be calculated by the following:
+ /// (HeaderSize + 1) * 4
+ ///
+ public byte HeaderSize { get; set; }
+
+ ///
+ /// The Block Flags field is a bit field
+ ///
+ public BlockFlags Flags { get; set; }
+
+ ///
+ /// Size of the compressed data
+ /// Present if is set.
+ ///
+ /// Stored as a variable-length integer
+ public byte[]? CompressedSize { get; set; }
+
+ ///
+ /// Size of the block after decompression
+ /// Present if is set.
+ ///
+ /// Stored as a variable-length integer
+ public byte[]? UncompressedSize { get; set; }
+
+ ///
+ /// List of filter flags
+ ///
+ ///
+ /// The number of filter flags is given by the first two
+ /// bits of
+ ///
+ public FilterFlag[]? FilterFlags { get; set; }
+
+ ///
+ /// This field contains as many null byte as it is needed to make
+ /// the Block Header have the size specified in Block Header Size.
+ ///
+ public byte[]? HeaderPadding { get; set; }
+
+ ///
+ /// 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.
+ ///
+ public uint Crc32 { get; set; }
+
+ ///
+ /// The format of Compressed Data depends on Block Flags and List
+ /// of Filter Flags
+ ///
+ public byte[]? CompressedData { get; set; }
+
+ ///
+ /// 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.
+ ///
+ public byte[]? BlockPadding { get; set; }
+
+ ///
+ /// 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.
+ ///
+ public byte[]? Check { get; set; }
+ }
+}
diff --git a/SabreTools.Serialization/Models/XZ/Constants.cs b/SabreTools.Serialization/Models/XZ/Constants.cs
index df70fe3d..d8d0c5b9 100644
--- a/SabreTools.Serialization/Models/XZ/Constants.cs
+++ b/SabreTools.Serialization/Models/XZ/Constants.cs
@@ -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];
}
}
\ No newline at end of file
diff --git a/SabreTools.Serialization/Models/XZ/Enums.cs b/SabreTools.Serialization/Models/XZ/Enums.cs
new file mode 100644
index 00000000..16b1f3e2
--- /dev/null
+++ b/SabreTools.Serialization/Models/XZ/Enums.cs
@@ -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
+
+ ///
+ /// Compressed size field present
+ ///
+ CompressedSize = 0x40,
+
+ ///
+ /// Uncompressed size field present
+ ///
+ 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,
+ }
+}
diff --git a/SabreTools.Serialization/Models/XZ/FilterFlags.cs b/SabreTools.Serialization/Models/XZ/FilterFlags.cs
new file mode 100644
index 00000000..e6216884
--- /dev/null
+++ b/SabreTools.Serialization/Models/XZ/FilterFlags.cs
@@ -0,0 +1,23 @@
+namespace SabreTools.Data.Models.XZ
+{
+ public class FilterFlag
+ {
+ ///
+ /// Filter ID
+ ///
+ /// Stored as a variable-length integer
+ public byte[]? FilterID { get; set; }
+
+ ///
+ /// Filter ID
+ ///
+ /// Stored as a variable-length integer
+ public byte[]? SizeOfProperties { get; set; }
+
+ ///
+ /// Properties of the filter whose length is given by
+ ///
+ ///
+ public byte[]? Properties { get; set; }
+ }
+}
diff --git a/SabreTools.Serialization/Models/XZ/Footer.cs b/SabreTools.Serialization/Models/XZ/Footer.cs
new file mode 100644
index 00000000..0ef86d18
--- /dev/null
+++ b/SabreTools.Serialization/Models/XZ/Footer.cs
@@ -0,0 +1,39 @@
+namespace SabreTools.Data.Models.XZ
+{
+ ///
+ /// Represents the post-block data in the stream
+ ///
+ public class Footer
+ {
+ ///
+ /// The CRC32 is calculated from the Backward Size and Stream Flags
+ /// fields. It is stored as an unsigned 32-bit little endian
+ /// integer.
+ ///
+ public uint Crc32 { get; set; }
+
+ ///
+ /// 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.
+ ///
+ ///
+ /// The real index size can be calculated by the following:
+ /// (BackwardSize + 1) * 4
+ ///
+ public uint BackwardSize { get; set; }
+
+ ///
+ /// 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.
+ ///
+ public HeaderFlags Flags { get; set; }
+
+ ///
+ /// Header magic number ("YZ")
+ ///
+ /// 2 bytes
+ public byte[]? Signature { get; set; }
+ }
+}
diff --git a/SabreTools.Serialization/Models/XZ/Header.cs b/SabreTools.Serialization/Models/XZ/Header.cs
new file mode 100644
index 00000000..40eb935a
--- /dev/null
+++ b/SabreTools.Serialization/Models/XZ/Header.cs
@@ -0,0 +1,27 @@
+namespace SabreTools.Data.Models.XZ
+{
+ ///
+ /// Represents the pre-block data in the stream
+ ///
+ public class Header
+ {
+ ///
+ /// Header magic number (0xFD, '7', 'z', 'X', 'Z', 0x00)
+ ///
+ /// 6 bytes
+ public byte[]? Signature { get; set; }
+
+ ///
+ /// 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.
+ ///
+ public HeaderFlags Flags { get; set; }
+
+ ///
+ /// The CRC32 is calculated from the Stream Flags field. It is
+ /// stored as an unsigned 32-bit little endian integer.
+ ///
+ public uint Crc32 { get; set; }
+ }
+}
diff --git a/SabreTools.Serialization/Models/XZ/Index.cs b/SabreTools.Serialization/Models/XZ/Index.cs
new file mode 100644
index 00000000..094f1480
--- /dev/null
+++ b/SabreTools.Serialization/Models/XZ/Index.cs
@@ -0,0 +1,36 @@
+namespace SabreTools.Data.Models.XZ
+{
+ public class Index
+ {
+ ///
+ /// The value of Index Indicator is always 0x00
+ ///
+ public byte IndexIndicator { get; set; }
+
+ ///
+ /// This field indicates how many Records there are in the List
+ /// of Records field, and thus how many Blocks there are in the
+ /// Stream
+ ///
+ /// Stored as a variable-length integer
+ public byte[]? NumberOfRecords { get; set; }
+
+ ///
+ /// One record per block
+ ///
+ public Record[]? Records { get; set; }
+
+ ///
+ /// This field MUST contain 0-3 null bytes to pad the Index to
+ /// a multiple of four bytes.
+ ///
+ public byte[]? Padding { get; set; }
+
+ ///
+ /// 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.
+ ///
+ public uint Crc32 { get; set; }
+ }
+}
diff --git a/SabreTools.Serialization/Models/XZ/Record.cs b/SabreTools.Serialization/Models/XZ/Record.cs
new file mode 100644
index 00000000..1f3aab4b
--- /dev/null
+++ b/SabreTools.Serialization/Models/XZ/Record.cs
@@ -0,0 +1,17 @@
+namespace SabreTools.Data.Models.XZ
+{
+ public class Record
+ {
+ ///
+ /// Unpadded size of the block
+ ///
+ /// Stored as a variable-length integer
+ public byte UnpaddedSize { get; set; }
+
+ ///
+ /// Uncompressed size of the block
+ ///
+ /// Stored as a variable-length integer
+ public byte[]? NumberOfRecords { get; set; }
+ }
+}
diff --git a/SabreTools.Serialization/WrapperFactory.cs b/SabreTools.Serialization/WrapperFactory.cs
index 3cf1dd0f..16cd18c7 100644
--- a/SabreTools.Serialization/WrapperFactory.cs
+++ b/SabreTools.Serialization/WrapperFactory.cs
@@ -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))
diff --git a/SabreTools.Serialization/Wrappers/NewExecutable.Extraction.cs b/SabreTools.Serialization/Wrappers/NewExecutable.Extraction.cs
index dbb2147a..a3cf7f93 100644
--- a/SabreTools.Serialization/Wrappers/NewExecutable.Extraction.cs
+++ b/SabreTools.Serialization/Wrappers/NewExecutable.Extraction.cs
@@ -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;
diff --git a/SabreTools.Serialization/Wrappers/PortableExecutable.Extraction.cs b/SabreTools.Serialization/Wrappers/PortableExecutable.Extraction.cs
index a1f09d89..de0f8a9e 100644
--- a/SabreTools.Serialization/Wrappers/PortableExecutable.Extraction.cs
+++ b/SabreTools.Serialization/Wrappers/PortableExecutable.Extraction.cs
@@ -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;