Add GCZ, Nintendo disc, and WIA models

Code originally by https://github.com/Dimensional and split off from https://github.com/SabreTools/SabreTools.Serialization/pull/85/
This commit is contained in:
Matt Nadareski
2026-05-04 09:59:14 -04:00
parent f6877d82cd
commit f729eae48a
18 changed files with 818 additions and 0 deletions

View File

@@ -0,0 +1,40 @@
namespace SabreTools.Data.Models.GCZ
{
/// <summary>
/// Represents a parsed GCZ (GameCube Zip) compressed disc image.
/// Contains header metadata and block lookup tables.
/// Actual compressed block data is accessed via the source stream.
/// </summary>
public class DiscImage
{
/// <summary>
/// GCZ file header
/// </summary>
public GczHeader Header { get; set; } = new();
/// <summary>
/// Block pointer table (one entry per block).
/// Each value encodes both the offset of the block within the compressed data section
/// and a compression flag in the top bit:
/// <list type="bullet">
/// <item>Top bit CLEAR → block is zlib/deflate-compressed at that offset.</item>
/// <item>Top bit SET → block is stored uncompressed at that offset.</item>
/// </list>
/// Offset is <c>value &amp; ~UncompressedFlag</c>.
/// </summary>
public ulong[] BlockPointers { get; set; } = [];
/// <summary>
/// Adler-32 (stored as CRC32) hashes of the uncompressed block data,
/// one per block. Used for integrity verification.
/// </summary>
public uint[] BlockHashes { get; set; } = [];
/// <summary>
/// Byte offset within the GCZ file where the compressed block data begins.
/// Computed as: <c>HeaderSize + (NumBlocks * 8) + (NumBlocks * 4)</c>.
/// </summary>
/// <remarks>Not parsed from stream; computed during deserialization.</remarks>
public long DataOffset { get; set; }
}
}

View File

@@ -0,0 +1,23 @@
namespace SabreTools.Data.Models.GCZ
{
public static class Constants
{
/// <summary>GCZ magic cookie (little-endian u32 at offset 0)</summary>
public const uint MagicCookie = 0xB10BC001;
/// <summary>Size of the GCZ file header in bytes</summary>
public const int HeaderSize = 32;
// Valid GCZ block sizes (Dolphin-compatible)
public const uint BlockSize32K = 0x8000;
public const uint BlockSize64K = 0x10000;
public const uint BlockSize128K = 0x20000;
public const uint DefaultBlockSize = BlockSize32K;
/// <summary>
/// Top bit of a block-pointer value: when CLEAR the block is zlib/deflate compressed;
/// when SET the block is stored uncompressed.
/// </summary>
public const ulong UncompressedFlag = 0x8000000000000000;
}
}

View File

@@ -0,0 +1,39 @@
namespace SabreTools.Data.Models.GCZ
{
/// <summary>
/// GCZ (GameCube Zip) file header — 32 bytes at the start of the file
/// </summary>
/// <see href="https://github.com/dolphin-emu/dolphin/blob/master/Source/Core/DiscIO/CompressedBlob.h"/>
public sealed class GczHeader
{
/// <summary>
/// Magic cookie identifying a GCZ file (0xB10BC001)
/// </summary>
public uint MagicCookie { get; set; }
/// <summary>
/// Sub-type; always 0 for GameCube / Wii disc images
/// </summary>
public uint SubType { get; set; }
/// <summary>
/// Total size of the compressed block data section in bytes
/// </summary>
public ulong CompressedDataSize { get; set; }
/// <summary>
/// Total decompressed (ISO) size in bytes
/// </summary>
public ulong DataSize { get; set; }
/// <summary>
/// Size of each uncompressed block in bytes (must be 32 KiB, 64 KiB, or 128 KiB)
/// </summary>
public uint BlockSize { get; set; }
/// <summary>
/// Number of blocks in the image
/// </summary>
public uint NumBlocks { get; set; }
}
}

View File

@@ -0,0 +1,120 @@
namespace SabreTools.Data.Models.NintendoDisc
{
public static class Constants
{
#region Disc identification magic values
/// <summary>Magic word present at offset 0x01C on GameCube discs</summary>
public const uint GCMagicWord = 0xC2339F3D;
/// <summary>Magic word present at offset 0x018 on Wii discs</summary>
public const uint WiiMagicWord = 0x5D1C9EA3;
#endregion
#region Disc header layout
// Offsets within the 0x440-byte boot block.
// Layout confirmed against Dolphin source (VolumeDisc.cpp / DiscUtils.h):
// 0x0000x003 Title code (4 chars, e.g. "GAFE")
// 0x0040x005 Maker code (2 chars, e.g. "01") — Dolphin Read(0x4, 2)
// 0x006 Disc number — Dolphin GetDiscNumber() Read(6)
// 0x007 Revision — Dolphin GetRevision() Read(7)
// 0x008 Audio streaming
// 0x009 Streaming buffer size
// 0x00A0x017 Unused (14 bytes)
// 0x018 Wii magic (0x5D1C9EA3)
// 0x01C GC magic (0xC2339F3D)
// 0x0200x07F Game title (0x60 bytes)
// 0x080 Disable hash verification
// 0x081 Disable disc encryption
public const int TitleCodeOffset = 0x000;
public const int TitleCodeLength = 4;
public const int MakerCodeOffset = 0x004;
public const int MakerCodeLength = 2;
/// <summary>Full 6-char game ID = TitleCode[4] + MakerCode[2]</summary>
public const int GameIdOffset = 0x000;
public const int GameIdLength = 6;
public const int DiscNumberOffset = 0x006;
public const int DiscVersionOffset = 0x007;
public const int AudioStreamingOffset = 0x008;
public const int StreamingBufferSizeOffset = 0x009;
public const int WiiMagicOffset = 0x018;
public const int GCMagicOffset = 0x01C;
public const int GameTitleOffset = 0x020;
public const int GameTitleLength = 0x060;
public const int DisableHashVerificationOffset = 0x080;
public const int DisableDiscEncryptionOffset = 0x081;
public const int DolOffsetField = 0x420;
public const int FstOffsetField = 0x424;
public const int FstSizeField = 0x428;
public const int DiscHeaderSize = 0x440;
#endregion
#region BI2 data
public const int Bi2Address = 0x000440;
public const int Bi2Size = 0x2000;
#endregion
#region Apploader
public const int ApploaderAddress = 0x002440;
public const int ApploaderCodeSizeOffset = 0x14;
public const int ApploaderTrailerSizeOffset = 0x18;
public const int ApploaderHeaderSize = 0x20;
#endregion
#region Wii-specific disc layout
public const int WiiPartitionTableAddress = 0x40000;
public const int WiiPartitionGroupCount = 4;
public const int WiiRegionDataAddress = 0x04E000;
public const int WiiRegionDataSize = 0x20;
#endregion
#region Wii partition header fields
// Offsets relative to partition start
public const int WiiTicketSize = 0x2A4;
public const int WiiTmdSizeAddress = 0x2A4;
public const int WiiTmdOffsetAddress = 0x2A8;
public const int WiiCertSizeAddress = 0x2AC;
public const int WiiCertOffsetAddress = 0x2B0;
public const int WiiH3OffsetAddress = 0x2B4;
public const int WiiH3Size = 0x18000;
public const int WiiDataOffsetAddress = 0x2B8;
#endregion
#region Wii block / group structure
public const int WiiBlockSize = 0x8000;
public const int WiiBlockHeaderSize = 0x0400;
public const int WiiBlockDataSize = 0x7C00;
public const int WiiBlocksPerGroup = 64;
public const int WiiGroupSize = WiiBlocksPerGroup * WiiBlockSize;
public const int WiiGroupDataSize = WiiBlocksPerGroup * WiiBlockDataSize;
#endregion
#region DVD sector size
public const int DvdSectorSize = 0x800;
#endregion
#region Wii ticket fields
// Offsets relative to ticket start
public const int TicketEncryptedTitleKeyOffset = 0x1BF;
public const int TicketTitleIdOffset = 0x1DC;
public const int TicketCommonKeyIndexOffset = 0x1F1;
#endregion
}
}

View File

@@ -0,0 +1,28 @@
namespace SabreTools.Data.Models.NintendoDisc
{
/// <summary>
/// Represents a parsed GameCube or Wii disc image
/// </summary>
public class Disc
{
/// <summary>
/// Disc boot block header (first 0x440 bytes)
/// </summary>
public DiscHeader Header { get; set; } = new();
/// <summary>
/// Detected platform (GameCube or Wii)
/// </summary>
public Platform Platform { get; set; }
/// <summary>
/// Wii partition table entries (Wii discs only)
/// </summary>
public WiiPartitionTableEntry[]? PartitionTableEntries { get; set; }
/// <summary>
/// Wii region data at disc offset 0x4E000 (Wii discs only)
/// </summary>
public WiiRegionData? RegionData { get; set; }
}
}

View File

@@ -0,0 +1,81 @@
namespace SabreTools.Data.Models.NintendoDisc
{
/// <summary>
/// GameCube / Wii disc boot block header (first 0x440 bytes of the disc)
/// </summary>
/// <see href="https://wiibrew.org/wiki/Wii_disc"/>
public sealed class DiscHeader
{
/// <summary>
/// 6-character ASCII game ID (e.g. "GALE01")
/// </summary>
/// <remarks>6 bytes at offset 0x000</remarks>
public string GameId { get; set; } = string.Empty;
/// <summary>
/// 2-character ASCII maker / publisher code (e.g. "01")
/// </summary>
/// <remarks>Derived from GameId bytes at offset 0x0040x005; not a separate on-disc field</remarks>
public string MakerCode { get; set; } = string.Empty;
/// <summary>
/// Zero-based disc number for multi-disc games
/// </summary>
public byte DiscNumber { get; set; }
/// <summary>
/// Disc version
/// </summary>
public byte DiscVersion { get; set; }
/// <summary>
/// Non-zero if audio streaming is enabled
/// </summary>
public byte AudioStreaming { get; set; }
/// <summary>
/// Audio streaming buffer size (in 16 KiB units)
/// </summary>
public byte StreamingBufferSize { get; set; }
/// <summary>
/// Wii magic word at offset 0x018 (0x5D1C9EA3 for Wii discs, 0 for GameCube)
/// </summary>
public uint WiiMagic { get; set; }
/// <summary>
/// GameCube magic word at offset 0x01C (0xC2339F3D for GameCube discs)
/// </summary>
public uint GCMagic { get; set; }
/// <summary>
/// Null-terminated ASCII game title (up to 0x60 bytes at offset 0x020)
/// </summary>
public string GameTitle { get; set; } = string.Empty;
/// <summary>
/// Non-zero to disable hash verification (GameCube only)
/// </summary>
public byte DisableHashVerification { get; set; }
/// <summary>
/// Non-zero to disable disc encryption (GameCube only)
/// </summary>
public byte DisableDiscEncryption { get; set; }
/// <summary>
/// Offset of the main DOL executable (no shift for GameCube; &lt;&lt;2 for Wii)
/// </summary>
public uint DolOffset { get; set; }
/// <summary>
/// Offset of the File System Table (no shift for GameCube; &lt;&lt;2 for Wii)
/// </summary>
public uint FstOffset { get; set; }
/// <summary>
/// Maximum size of the File System Table in bytes
/// </summary>
public uint FstSize { get; set; }
}
}

View File

@@ -0,0 +1,32 @@
namespace SabreTools.Data.Models.NintendoDisc
{
/// <summary>
/// Platform / console type for a Nintendo disc image
/// </summary>
public enum Platform
{
/// <summary>Platform could not be determined</summary>
Unknown = 0,
/// <summary>Nintendo GameCube</summary>
GameCube = 1,
/// <summary>Nintendo Wii</summary>
Wii = 2,
}
/// <summary>
/// Wii partition type
/// </summary>
public enum WiiPartitionType : uint
{
/// <summary>Game data partition (DATA)</summary>
Data = 0,
/// <summary>System update partition (UPDATE)</summary>
Update = 1,
/// <summary>Channel installer partition (CHANNEL)</summary>
Channel = 2,
}
}

View File

@@ -0,0 +1,21 @@
namespace SabreTools.Data.Models.NintendoDisc
{
/// <summary>
/// A single entry in the Wii disc partition table.
/// The table lives at 0x400000x4FFFF on the disc.
/// </summary>
/// <see href="https://wiibrew.org/wiki/Wii_disc#Partition_table"/>
public sealed class WiiPartitionTableEntry
{
/// <summary>
/// Absolute byte offset of the partition on the disc.
/// Stored on-disc as <c>offset &gt;&gt; 2</c> (big-endian u32).
/// </summary>
public long Offset { get; set; }
/// <summary>
/// Partition type: 0 = DATA, 1 = UPDATE, 2 = CHANNEL, or an ASCII title ID.
/// </summary>
public uint Type { get; set; }
}
}

View File

@@ -0,0 +1,22 @@
namespace SabreTools.Data.Models.NintendoDisc
{
/// <summary>
/// Wii disc region data block (0x20 bytes at disc offset 0x4E000)
/// </summary>
/// <see href="https://wiibrew.org/wiki/Wii_disc#Region_setting"/>
public sealed class WiiRegionData
{
/// <summary>
/// Region setting byte:
/// 0 = Japan, 1 = USA, 2 = Europe, 3 = Korea,
/// 4 = China, 5 = Taiwan, 6 = Germany, 7 = France
/// </summary>
public uint RegionSetting { get; set; }
/// <summary>
/// Age ratings for various regions (0x10 bytes)
/// </summary>
/// <remarks>16 bytes</remarks>
public byte[] AgeRatings { get; set; } = new byte[16];
}
}

View File

@@ -0,0 +1,45 @@
namespace SabreTools.Data.Models.WIA
{
/// <summary>
/// Represents a parsed WIA or RVZ compressed disc image.
/// Contains the two headers and all lookup tables.
/// Actual group (compressed block) data is accessed via the source stream.
/// </summary>
public class DiscImage
{
/// <summary>
/// WIA / RVZ primary header (0x48 bytes)
/// </summary>
public WiaHeader1 Header1 { get; set; } = new();
/// <summary>
/// WIA / RVZ secondary header (0xDC bytes)
/// </summary>
public WiaHeader2 Header2 { get; set; } = new();
/// <summary>
/// Wii partition entries. Null or empty for GameCube discs.
/// </summary>
public PartitionEntry[]? PartitionEntries { get; set; }
/// <summary>
/// Raw (non-partition) data region entries
/// </summary>
public RawDataEntry[] RawDataEntries { get; set; } = [];
/// <summary>
/// WIA group entries (populated when <see cref="IsRvz"/> is false)
/// </summary>
public WiaGroupEntry[]? GroupEntries { get; set; }
/// <summary>
/// RVZ group entries (populated when <see cref="IsRvz"/> is true)
/// </summary>
public RvzGroupEntry[]? RvzGroupEntries { get; set; }
/// <summary>
/// True if this is an RVZ file; false if this is a WIA file
/// </summary>
public bool IsRvz { get; set; }
}
}

View File

@@ -0,0 +1,47 @@
namespace SabreTools.Data.Models.WIA
{
public static class Constants
{
/// <summary>WIA magic (little-endian u32): "WIA\x01"</summary>
public const uint WiaMagic = 0x01414957;
/// <summary>RVZ magic (little-endian u32): "RVZ\x01"</summary>
public const uint RvzMagic = 0x015A5652;
/// <summary>Size of WiaHeader1 in bytes</summary>
public const int Header1Size = 0x48;
/// <summary>Size of WiaHeader2 in bytes</summary>
public const int Header2Size = 0xDC;
/// <summary>Size of a PartitionEntry in bytes</summary>
public const int PartitionEntrySize = 0x30;
/// <summary>Size of a PartitionDataEntry in bytes</summary>
public const int PartitionDataEntrySize = 0x10;
/// <summary>Size of a RawDataEntry in bytes</summary>
public const int RawDataEntrySize = 0x18;
/// <summary>Size of a WiaGroupEntry in bytes</summary>
public const int WiaGroupEntrySize = 0x08;
/// <summary>Size of an RvzGroupEntry in bytes</summary>
public const int RvzGroupEntrySize = 0x0C;
/// <summary>Size of a HashExceptionEntry in bytes (2-byte offset + 20-byte SHA-1)</summary>
public const int HashExceptionEntrySize = 0x16;
/// <summary>Number of bytes of disc header stored in WiaHeader2.DiscHeader</summary>
public const int DiscHeaderStoredSize = 0x80;
// WIA version numbers
public const uint WiaVersion = 0x01000000;
public const uint WiaVersionWriteCompatible = 0x01000000;
public const uint RvzVersion = 0x01000000;
public const uint RvzVersionWriteCompatible = 0x00030000;
// Default chunk size (2 MiB = one Wii group)
public const uint DefaultChunkSize = 2 * 1024 * 1024;
}
}

View File

@@ -0,0 +1,40 @@
namespace SabreTools.Data.Models.WIA
{
/// <summary>
/// WIA / RVZ disc type
/// </summary>
public enum WiaDiscType : uint
{
/// <summary>Nintendo GameCube disc</summary>
GameCube = 1,
/// <summary>Nintendo Wii disc</summary>
Wii = 2,
}
/// <summary>
/// Compression algorithm used inside a WIA or RVZ file.
/// WIA supports None / Purge / Bzip2 / LZMA / LZMA2.
/// RVZ additionally supports Zstd; Purge is not used in RVZ.
/// </summary>
public enum WiaRvzCompressionType : uint
{
/// <summary>No compression — data stored verbatim</summary>
None = 0,
/// <summary>Purge — strips known-zero regions (hash blocks, padding). WIA only.</summary>
Purge = 1,
/// <summary>bzip2 block compression</summary>
Bzip2 = 2,
/// <summary>LZMA compression</summary>
LZMA = 3,
/// <summary>LZMA2 compression</summary>
LZMA2 = 4,
/// <summary>Zstandard compression. RVZ only.</summary>
Zstd = 5,
}
}

View File

@@ -0,0 +1,44 @@
namespace SabreTools.Data.Models.WIA
{
/// <summary>
/// WIA group entry pointing to compressed data for one group. Size: 0x08 bytes.
/// DataOffset is stored on-disk as the actual byte offset shifted right by 2 (i.e. &gt;&gt;2).
/// </summary>
public sealed class WiaGroupEntry
{
/// <summary>
/// Actual byte offset of this group's data within the WIA file.
/// (On disk this value is stored as <c>offset &gt;&gt; 2</c>.)
/// </summary>
public ulong DataOffset { get; set; }
/// <summary>
/// Compressed size of this group's data in bytes (0 means group contains only zeroes)
/// </summary>
public uint DataSize { get; set; }
}
/// <summary>
/// RVZ group entry — extends <see cref="WiaGroupEntry"/> with a packed-data size field.
/// Size: 0x0C bytes.
/// </summary>
public sealed class RvzGroupEntry
{
/// <summary>
/// Actual byte offset of this group's data within the RVZ file.
/// (On disk this value is stored as <c>offset &gt;&gt; 2</c>.)
/// </summary>
public ulong DataOffset { get; set; }
/// <summary>
/// Total size of this group's data (compressed + any RVZ-pack section) in bytes
/// </summary>
public uint DataSize { get; set; }
/// <summary>
/// Size of the RVZ-packed (junk-stripped) portion within this group's data.
/// 0 means no RVZ packing was applied.
/// </summary>
public uint RvzPackedSize { get; set; }
}
}

View File

@@ -0,0 +1,23 @@
namespace SabreTools.Data.Models.WIA
{
/// <summary>
/// A single hash exception entry within a WIA / RVZ Wii-partition group.
/// Used to restore the correct SHA-1 hash values that were stripped when
/// the Wii block hash data was removed during compression.
/// Size: 0x16 bytes (2-byte offset + 20-byte SHA-1).
/// </summary>
public sealed class HashExceptionEntry
{
/// <summary>
/// Byte offset within the reconstructed 0x400-byte hash block where
/// this SHA-1 value must be written
/// </summary>
public ushort Offset { get; set; }
/// <summary>
/// SHA-1 hash value (20 bytes)
/// </summary>
/// <remarks>20 bytes</remarks>
public byte[] Hash { get; set; } = new byte[20];
}
}

View File

@@ -0,0 +1,40 @@
namespace SabreTools.Data.Models.WIA
{
/// <summary>
/// Describes a contiguous range of sectors within a Wii partition.
/// Part of a <see cref="PartitionEntry"/>. Size: 0x10 bytes.
/// </summary>
public sealed class PartitionDataEntry
{
/// <summary>Zero-based index of the first sector covered by this range</summary>
public uint FirstSector { get; set; }
/// <summary>Number of sectors covered by this range</summary>
public uint NumberOfSectors { get; set; }
/// <summary>Index into the group-entry array of the first group for this range</summary>
public uint GroupIndex { get; set; }
/// <summary>Number of groups covering this range</summary>
public uint NumberOfGroups { get; set; }
}
/// <summary>
/// Describes a single Wii partition: its AES title key and two sector ranges.
/// Size: 0x30 bytes.
/// </summary>
public sealed class PartitionEntry
{
/// <summary>
/// Decrypted AES-128 partition title key
/// </summary>
/// <remarks>16 bytes</remarks>
public byte[] PartitionKey { get; set; } = new byte[16];
/// <summary>First sector range for this partition (typically encrypted data)</summary>
public PartitionDataEntry DataEntry0 { get; set; } = new();
/// <summary>Second sector range for this partition (typically decrypted/raw data)</summary>
public PartitionDataEntry DataEntry1 { get; set; } = new();
}
}

View File

@@ -0,0 +1,21 @@
namespace SabreTools.Data.Models.WIA
{
/// <summary>
/// Describes a region of non-partition data (e.g. disc header, partition table).
/// Size: 0x18 bytes.
/// </summary>
public sealed class RawDataEntry
{
/// <summary>Byte offset of this region within the equivalent ISO image</summary>
public ulong DataOffset { get; set; }
/// <summary>Size of this region in bytes</summary>
public ulong DataSize { get; set; }
/// <summary>Index into the group-entry array of the first group for this region</summary>
public uint GroupIndex { get; set; }
/// <summary>Number of groups covering this region</summary>
public uint NumberOfGroups { get; set; }
}
}

View File

@@ -0,0 +1,52 @@
namespace SabreTools.Data.Models.WIA
{
/// <summary>
/// WIA / RVZ first header (0x48 bytes at the start of the file).
/// All multi-byte fields are big-endian on disk; the reader converts to host order.
/// </summary>
/// <see href="https://github.com/dolphin-emu/dolphin/blob/master/Source/Core/DiscIO/WIABlob.h"/>
public sealed class WiaHeader1
{
/// <summary>
/// Format magic: 0x01414957 ("WIA\x01") or 0x015A5652 ("RVZ\x01")
/// </summary>
public uint Magic { get; set; }
/// <summary>
/// Format version (e.g. 0x01000000)
/// </summary>
public uint Version { get; set; }
/// <summary>
/// Minimum version required to read this file
/// </summary>
public uint VersionCompatible { get; set; }
/// <summary>
/// Size of WiaHeader2 in bytes
/// </summary>
public uint Header2Size { get; set; }
/// <summary>
/// SHA-1 hash of WiaHeader2
/// </summary>
/// <remarks>20 bytes</remarks>
public byte[] Header2Hash { get; set; } = new byte[20];
/// <summary>
/// Total size of the equivalent uncompressed ISO image in bytes
/// </summary>
public ulong IsoFileSize { get; set; }
/// <summary>
/// Total size of this WIA / RVZ file in bytes
/// </summary>
public ulong WiaFileSize { get; set; }
/// <summary>
/// SHA-1 hash of this header, excluding this field itself
/// </summary>
/// <remarks>20 bytes</remarks>
public byte[] Header1Hash { get; set; } = new byte[20];
}
}

View File

@@ -0,0 +1,100 @@
namespace SabreTools.Data.Models.WIA
{
/// <summary>
/// WIA / RVZ second header (0xDC bytes).
/// Immediately follows WiaHeader1 in the file.
/// All multi-byte fields are big-endian on disk; the reader converts to host order.
/// </summary>
public sealed class WiaHeader2
{
/// <summary>
/// Disc type: 1 = GameCube, 2 = Wii
/// </summary>
public WiaDiscType DiscType { get; set; }
/// <summary>
/// Compression algorithm applied to group data
/// </summary>
public WiaRvzCompressionType CompressionType { get; set; }
/// <summary>
/// Informational compression level used when writing (19)
/// </summary>
public int CompressionLevel { get; set; }
/// <summary>
/// Group / chunk size in bytes.
/// WIA requires exactly 2 MiB; RVZ accepts powers of 2 between 32 KiB and 2 MiB.
/// </summary>
public uint ChunkSize { get; set; }
/// <summary>
/// First 0x80 bytes of the disc image (unencrypted disc header)
/// </summary>
/// <remarks>0x80 bytes</remarks>
public byte[] DiscHeader { get; set; } = new byte[0x80];
/// <summary>
/// Number of PartitionEntry structures that follow the raw-data entries
/// </summary>
public uint NumberOfPartitionEntries { get; set; }
/// <summary>
/// Size of each PartitionEntry in bytes
/// </summary>
public uint PartitionEntrySize { get; set; }
/// <summary>
/// File offset of the PartitionEntry array
/// </summary>
public ulong PartitionEntriesOffset { get; set; }
/// <summary>
/// SHA-1 hash of all PartitionEntry data
/// </summary>
/// <remarks>20 bytes</remarks>
public byte[] PartitionEntriesHash { get; set; } = new byte[20];
/// <summary>
/// Number of RawDataEntry structures
/// </summary>
public uint NumberOfRawDataEntries { get; set; }
/// <summary>
/// File offset of the RawDataEntry array
/// </summary>
public ulong RawDataEntriesOffset { get; set; }
/// <summary>
/// Total size in bytes of all RawDataEntry structures
/// </summary>
public uint RawDataEntriesSize { get; set; }
/// <summary>
/// Number of group entries (WiaGroupEntry or RvzGroupEntry)
/// </summary>
public uint NumberOfGroupEntries { get; set; }
/// <summary>
/// File offset of the group-entry array
/// </summary>
public ulong GroupEntriesOffset { get; set; }
/// <summary>
/// Total size in bytes of all group entries
/// </summary>
public uint GroupEntriesSize { get; set; }
/// <summary>
/// Number of valid bytes in <see cref="CompressorData"/>
/// </summary>
public byte CompressorDataSize { get; set; }
/// <summary>
/// Algorithm-specific compressor parameters (up to 7 bytes).
/// LZMA: 5-byte prop block. LZMA2: 1-byte dict-size code. Others: unused.
/// </summary>
/// <remarks>7 bytes</remarks>
public byte[] CompressorData { get; set; } = new byte[7];
}
}