diff --git a/SabreTools.Data.Models/GCZ/Archive.cs b/SabreTools.Data.Models/GCZ/Archive.cs
new file mode 100644
index 00000000..63931bee
--- /dev/null
+++ b/SabreTools.Data.Models/GCZ/Archive.cs
@@ -0,0 +1,40 @@
+namespace SabreTools.Data.Models.GCZ
+{
+ ///
+ /// 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.
+ ///
+ public class DiscImage
+ {
+ ///
+ /// GCZ file header
+ ///
+ public GczHeader Header { get; set; } = new();
+
+ ///
+ /// 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:
+ ///
+ /// - Top bit CLEAR → block is zlib/deflate-compressed at that offset.
+ /// - Top bit SET → block is stored uncompressed at that offset.
+ ///
+ /// Offset is value & ~UncompressedFlag.
+ ///
+ public ulong[] BlockPointers { get; set; } = [];
+
+ ///
+ /// Adler-32 (stored as CRC32) hashes of the uncompressed block data,
+ /// one per block. Used for integrity verification.
+ ///
+ public uint[] BlockHashes { get; set; } = [];
+
+ ///
+ /// Byte offset within the GCZ file where the compressed block data begins.
+ /// Computed as: HeaderSize + (NumBlocks * 8) + (NumBlocks * 4).
+ ///
+ /// Not parsed from stream; computed during deserialization.
+ public long DataOffset { get; set; }
+ }
+}
diff --git a/SabreTools.Data.Models/GCZ/Constants.cs b/SabreTools.Data.Models/GCZ/Constants.cs
new file mode 100644
index 00000000..3b5388a5
--- /dev/null
+++ b/SabreTools.Data.Models/GCZ/Constants.cs
@@ -0,0 +1,23 @@
+namespace SabreTools.Data.Models.GCZ
+{
+ public static class Constants
+ {
+ /// GCZ magic cookie (little-endian u32 at offset 0)
+ public const uint MagicCookie = 0xB10BC001;
+
+ /// Size of the GCZ file header in bytes
+ 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;
+
+ ///
+ /// Top bit of a block-pointer value: when CLEAR the block is zlib/deflate compressed;
+ /// when SET the block is stored uncompressed.
+ ///
+ public const ulong UncompressedFlag = 0x8000000000000000;
+ }
+}
diff --git a/SabreTools.Data.Models/GCZ/GczHeader.cs b/SabreTools.Data.Models/GCZ/GczHeader.cs
new file mode 100644
index 00000000..e78b0d5f
--- /dev/null
+++ b/SabreTools.Data.Models/GCZ/GczHeader.cs
@@ -0,0 +1,39 @@
+namespace SabreTools.Data.Models.GCZ
+{
+ ///
+ /// GCZ (GameCube Zip) file header — 32 bytes at the start of the file
+ ///
+ ///
+ public sealed class GczHeader
+ {
+ ///
+ /// Magic cookie identifying a GCZ file (0xB10BC001)
+ ///
+ public uint MagicCookie { get; set; }
+
+ ///
+ /// Sub-type; always 0 for GameCube / Wii disc images
+ ///
+ public uint SubType { get; set; }
+
+ ///
+ /// Total size of the compressed block data section in bytes
+ ///
+ public ulong CompressedDataSize { get; set; }
+
+ ///
+ /// Total decompressed (ISO) size in bytes
+ ///
+ public ulong DataSize { get; set; }
+
+ ///
+ /// Size of each uncompressed block in bytes (must be 32 KiB, 64 KiB, or 128 KiB)
+ ///
+ public uint BlockSize { get; set; }
+
+ ///
+ /// Number of blocks in the image
+ ///
+ public uint NumBlocks { get; set; }
+ }
+}
diff --git a/SabreTools.Data.Models/NintendoDisc/Constants.cs b/SabreTools.Data.Models/NintendoDisc/Constants.cs
new file mode 100644
index 00000000..0550c902
--- /dev/null
+++ b/SabreTools.Data.Models/NintendoDisc/Constants.cs
@@ -0,0 +1,120 @@
+namespace SabreTools.Data.Models.NintendoDisc
+{
+ public static class Constants
+ {
+ #region Disc identification magic values
+
+ /// Magic word present at offset 0x01C on GameCube discs
+ public const uint GCMagicWord = 0xC2339F3D;
+
+ /// Magic word present at offset 0x018 on Wii discs
+ 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):
+ // 0x000–0x003 Title code (4 chars, e.g. "GAFE")
+ // 0x004–0x005 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
+ // 0x00A–0x017 Unused (14 bytes)
+ // 0x018 Wii magic (0x5D1C9EA3)
+ // 0x01C GC magic (0xC2339F3D)
+ // 0x020–0x07F 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;
+ /// Full 6-char game ID = TitleCode[4] + MakerCode[2]
+ 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
+ }
+}
diff --git a/SabreTools.Data.Models/NintendoDisc/Disc.cs b/SabreTools.Data.Models/NintendoDisc/Disc.cs
new file mode 100644
index 00000000..ac4afc4b
--- /dev/null
+++ b/SabreTools.Data.Models/NintendoDisc/Disc.cs
@@ -0,0 +1,28 @@
+namespace SabreTools.Data.Models.NintendoDisc
+{
+ ///
+ /// Represents a parsed GameCube or Wii disc image
+ ///
+ public class Disc
+ {
+ ///
+ /// Disc boot block header (first 0x440 bytes)
+ ///
+ public DiscHeader Header { get; set; } = new();
+
+ ///
+ /// Detected platform (GameCube or Wii)
+ ///
+ public Platform Platform { get; set; }
+
+ ///
+ /// Wii partition table entries (Wii discs only)
+ ///
+ public WiiPartitionTableEntry[]? PartitionTableEntries { get; set; }
+
+ ///
+ /// Wii region data at disc offset 0x4E000 (Wii discs only)
+ ///
+ public WiiRegionData? RegionData { get; set; }
+ }
+}
diff --git a/SabreTools.Data.Models/NintendoDisc/DiscHeader.cs b/SabreTools.Data.Models/NintendoDisc/DiscHeader.cs
new file mode 100644
index 00000000..c1dc58e8
--- /dev/null
+++ b/SabreTools.Data.Models/NintendoDisc/DiscHeader.cs
@@ -0,0 +1,81 @@
+namespace SabreTools.Data.Models.NintendoDisc
+{
+ ///
+ /// GameCube / Wii disc boot block header (first 0x440 bytes of the disc)
+ ///
+ ///
+ public sealed class DiscHeader
+ {
+ ///
+ /// 6-character ASCII game ID (e.g. "GALE01")
+ ///
+ /// 6 bytes at offset 0x000
+ public string GameId { get; set; } = string.Empty;
+
+ ///
+ /// 2-character ASCII maker / publisher code (e.g. "01")
+ ///
+ /// Derived from GameId bytes at offset 0x004–0x005; not a separate on-disc field
+ public string MakerCode { get; set; } = string.Empty;
+
+ ///
+ /// Zero-based disc number for multi-disc games
+ ///
+ public byte DiscNumber { get; set; }
+
+ ///
+ /// Disc version
+ ///
+ public byte DiscVersion { get; set; }
+
+ ///
+ /// Non-zero if audio streaming is enabled
+ ///
+ public byte AudioStreaming { get; set; }
+
+ ///
+ /// Audio streaming buffer size (in 16 KiB units)
+ ///
+ public byte StreamingBufferSize { get; set; }
+
+ ///
+ /// Wii magic word at offset 0x018 (0x5D1C9EA3 for Wii discs, 0 for GameCube)
+ ///
+ public uint WiiMagic { get; set; }
+
+ ///
+ /// GameCube magic word at offset 0x01C (0xC2339F3D for GameCube discs)
+ ///
+ public uint GCMagic { get; set; }
+
+ ///
+ /// Null-terminated ASCII game title (up to 0x60 bytes at offset 0x020)
+ ///
+ public string GameTitle { get; set; } = string.Empty;
+
+ ///
+ /// Non-zero to disable hash verification (GameCube only)
+ ///
+ public byte DisableHashVerification { get; set; }
+
+ ///
+ /// Non-zero to disable disc encryption (GameCube only)
+ ///
+ public byte DisableDiscEncryption { get; set; }
+
+ ///
+ /// Offset of the main DOL executable (no shift for GameCube; <<2 for Wii)
+ ///
+ public uint DolOffset { get; set; }
+
+ ///
+ /// Offset of the File System Table (no shift for GameCube; <<2 for Wii)
+ ///
+ public uint FstOffset { get; set; }
+
+ ///
+ /// Maximum size of the File System Table in bytes
+ ///
+ public uint FstSize { get; set; }
+ }
+}
diff --git a/SabreTools.Data.Models/NintendoDisc/Enums.cs b/SabreTools.Data.Models/NintendoDisc/Enums.cs
new file mode 100644
index 00000000..6fcc5640
--- /dev/null
+++ b/SabreTools.Data.Models/NintendoDisc/Enums.cs
@@ -0,0 +1,32 @@
+namespace SabreTools.Data.Models.NintendoDisc
+{
+ ///
+ /// Platform / console type for a Nintendo disc image
+ ///
+ public enum Platform
+ {
+ /// Platform could not be determined
+ Unknown = 0,
+
+ /// Nintendo GameCube
+ GameCube = 1,
+
+ /// Nintendo Wii
+ Wii = 2,
+ }
+
+ ///
+ /// Wii partition type
+ ///
+ public enum WiiPartitionType : uint
+ {
+ /// Game data partition (DATA)
+ Data = 0,
+
+ /// System update partition (UPDATE)
+ Update = 1,
+
+ /// Channel installer partition (CHANNEL)
+ Channel = 2,
+ }
+}
diff --git a/SabreTools.Data.Models/NintendoDisc/WiiPartitionTableEntry.cs b/SabreTools.Data.Models/NintendoDisc/WiiPartitionTableEntry.cs
new file mode 100644
index 00000000..c463b110
--- /dev/null
+++ b/SabreTools.Data.Models/NintendoDisc/WiiPartitionTableEntry.cs
@@ -0,0 +1,21 @@
+namespace SabreTools.Data.Models.NintendoDisc
+{
+ ///
+ /// A single entry in the Wii disc partition table.
+ /// The table lives at 0x40000–0x4FFFF on the disc.
+ ///
+ ///
+ public sealed class WiiPartitionTableEntry
+ {
+ ///
+ /// Absolute byte offset of the partition on the disc.
+ /// Stored on-disc as offset >> 2 (big-endian u32).
+ ///
+ public long Offset { get; set; }
+
+ ///
+ /// Partition type: 0 = DATA, 1 = UPDATE, 2 = CHANNEL, or an ASCII title ID.
+ ///
+ public uint Type { get; set; }
+ }
+}
diff --git a/SabreTools.Data.Models/NintendoDisc/WiiRegionData.cs b/SabreTools.Data.Models/NintendoDisc/WiiRegionData.cs
new file mode 100644
index 00000000..a71adfb6
--- /dev/null
+++ b/SabreTools.Data.Models/NintendoDisc/WiiRegionData.cs
@@ -0,0 +1,22 @@
+namespace SabreTools.Data.Models.NintendoDisc
+{
+ ///
+ /// Wii disc region data block (0x20 bytes at disc offset 0x4E000)
+ ///
+ ///
+ public sealed class WiiRegionData
+ {
+ ///
+ /// Region setting byte:
+ /// 0 = Japan, 1 = USA, 2 = Europe, 3 = Korea,
+ /// 4 = China, 5 = Taiwan, 6 = Germany, 7 = France
+ ///
+ public uint RegionSetting { get; set; }
+
+ ///
+ /// Age ratings for various regions (0x10 bytes)
+ ///
+ /// 16 bytes
+ public byte[] AgeRatings { get; set; } = new byte[16];
+ }
+}
diff --git a/SabreTools.Data.Models/WIA/Archive.cs b/SabreTools.Data.Models/WIA/Archive.cs
new file mode 100644
index 00000000..4bab45c8
--- /dev/null
+++ b/SabreTools.Data.Models/WIA/Archive.cs
@@ -0,0 +1,45 @@
+namespace SabreTools.Data.Models.WIA
+{
+ ///
+ /// 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.
+ ///
+ public class DiscImage
+ {
+ ///
+ /// WIA / RVZ primary header (0x48 bytes)
+ ///
+ public WiaHeader1 Header1 { get; set; } = new();
+
+ ///
+ /// WIA / RVZ secondary header (0xDC bytes)
+ ///
+ public WiaHeader2 Header2 { get; set; } = new();
+
+ ///
+ /// Wii partition entries. Null or empty for GameCube discs.
+ ///
+ public PartitionEntry[]? PartitionEntries { get; set; }
+
+ ///
+ /// Raw (non-partition) data region entries
+ ///
+ public RawDataEntry[] RawDataEntries { get; set; } = [];
+
+ ///
+ /// WIA group entries (populated when is false)
+ ///
+ public WiaGroupEntry[]? GroupEntries { get; set; }
+
+ ///
+ /// RVZ group entries (populated when is true)
+ ///
+ public RvzGroupEntry[]? RvzGroupEntries { get; set; }
+
+ ///
+ /// True if this is an RVZ file; false if this is a WIA file
+ ///
+ public bool IsRvz { get; set; }
+ }
+}
diff --git a/SabreTools.Data.Models/WIA/Constants.cs b/SabreTools.Data.Models/WIA/Constants.cs
new file mode 100644
index 00000000..105c94ec
--- /dev/null
+++ b/SabreTools.Data.Models/WIA/Constants.cs
@@ -0,0 +1,47 @@
+namespace SabreTools.Data.Models.WIA
+{
+ public static class Constants
+ {
+ /// WIA magic (little-endian u32): "WIA\x01"
+ public const uint WiaMagic = 0x01414957;
+
+ /// RVZ magic (little-endian u32): "RVZ\x01"
+ public const uint RvzMagic = 0x015A5652;
+
+ /// Size of WiaHeader1 in bytes
+ public const int Header1Size = 0x48;
+
+ /// Size of WiaHeader2 in bytes
+ public const int Header2Size = 0xDC;
+
+ /// Size of a PartitionEntry in bytes
+ public const int PartitionEntrySize = 0x30;
+
+ /// Size of a PartitionDataEntry in bytes
+ public const int PartitionDataEntrySize = 0x10;
+
+ /// Size of a RawDataEntry in bytes
+ public const int RawDataEntrySize = 0x18;
+
+ /// Size of a WiaGroupEntry in bytes
+ public const int WiaGroupEntrySize = 0x08;
+
+ /// Size of an RvzGroupEntry in bytes
+ public const int RvzGroupEntrySize = 0x0C;
+
+ /// Size of a HashExceptionEntry in bytes (2-byte offset + 20-byte SHA-1)
+ public const int HashExceptionEntrySize = 0x16;
+
+ /// Number of bytes of disc header stored in WiaHeader2.DiscHeader
+ 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;
+ }
+}
diff --git a/SabreTools.Data.Models/WIA/Enums.cs b/SabreTools.Data.Models/WIA/Enums.cs
new file mode 100644
index 00000000..c68ed2fa
--- /dev/null
+++ b/SabreTools.Data.Models/WIA/Enums.cs
@@ -0,0 +1,40 @@
+namespace SabreTools.Data.Models.WIA
+{
+ ///
+ /// WIA / RVZ disc type
+ ///
+ public enum WiaDiscType : uint
+ {
+ /// Nintendo GameCube disc
+ GameCube = 1,
+
+ /// Nintendo Wii disc
+ Wii = 2,
+ }
+
+ ///
+ /// 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.
+ ///
+ public enum WiaRvzCompressionType : uint
+ {
+ /// No compression — data stored verbatim
+ None = 0,
+
+ /// Purge — strips known-zero regions (hash blocks, padding). WIA only.
+ Purge = 1,
+
+ /// bzip2 block compression
+ Bzip2 = 2,
+
+ /// LZMA compression
+ LZMA = 3,
+
+ /// LZMA2 compression
+ LZMA2 = 4,
+
+ /// Zstandard compression. RVZ only.
+ Zstd = 5,
+ }
+}
diff --git a/SabreTools.Data.Models/WIA/GroupEntries.cs b/SabreTools.Data.Models/WIA/GroupEntries.cs
new file mode 100644
index 00000000..cf0e79f0
--- /dev/null
+++ b/SabreTools.Data.Models/WIA/GroupEntries.cs
@@ -0,0 +1,44 @@
+namespace SabreTools.Data.Models.WIA
+{
+ ///
+ /// 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. >>2).
+ ///
+ public sealed class WiaGroupEntry
+ {
+ ///
+ /// Actual byte offset of this group's data within the WIA file.
+ /// (On disk this value is stored as offset >> 2.)
+ ///
+ public ulong DataOffset { get; set; }
+
+ ///
+ /// Compressed size of this group's data in bytes (0 means group contains only zeroes)
+ ///
+ public uint DataSize { get; set; }
+ }
+
+ ///
+ /// RVZ group entry — extends with a packed-data size field.
+ /// Size: 0x0C bytes.
+ ///
+ public sealed class RvzGroupEntry
+ {
+ ///
+ /// Actual byte offset of this group's data within the RVZ file.
+ /// (On disk this value is stored as offset >> 2.)
+ ///
+ public ulong DataOffset { get; set; }
+
+ ///
+ /// Total size of this group's data (compressed + any RVZ-pack section) in bytes
+ ///
+ public uint DataSize { get; set; }
+
+ ///
+ /// Size of the RVZ-packed (junk-stripped) portion within this group's data.
+ /// 0 means no RVZ packing was applied.
+ ///
+ public uint RvzPackedSize { get; set; }
+ }
+}
diff --git a/SabreTools.Data.Models/WIA/HashExceptionEntry.cs b/SabreTools.Data.Models/WIA/HashExceptionEntry.cs
new file mode 100644
index 00000000..66003964
--- /dev/null
+++ b/SabreTools.Data.Models/WIA/HashExceptionEntry.cs
@@ -0,0 +1,23 @@
+namespace SabreTools.Data.Models.WIA
+{
+ ///
+ /// 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).
+ ///
+ public sealed class HashExceptionEntry
+ {
+ ///
+ /// Byte offset within the reconstructed 0x400-byte hash block where
+ /// this SHA-1 value must be written
+ ///
+ public ushort Offset { get; set; }
+
+ ///
+ /// SHA-1 hash value (20 bytes)
+ ///
+ /// 20 bytes
+ public byte[] Hash { get; set; } = new byte[20];
+ }
+}
diff --git a/SabreTools.Data.Models/WIA/PartitionEntry.cs b/SabreTools.Data.Models/WIA/PartitionEntry.cs
new file mode 100644
index 00000000..13fdb514
--- /dev/null
+++ b/SabreTools.Data.Models/WIA/PartitionEntry.cs
@@ -0,0 +1,40 @@
+namespace SabreTools.Data.Models.WIA
+{
+ ///
+ /// Describes a contiguous range of sectors within a Wii partition.
+ /// Part of a . Size: 0x10 bytes.
+ ///
+ public sealed class PartitionDataEntry
+ {
+ /// Zero-based index of the first sector covered by this range
+ public uint FirstSector { get; set; }
+
+ /// Number of sectors covered by this range
+ public uint NumberOfSectors { get; set; }
+
+ /// Index into the group-entry array of the first group for this range
+ public uint GroupIndex { get; set; }
+
+ /// Number of groups covering this range
+ public uint NumberOfGroups { get; set; }
+ }
+
+ ///
+ /// Describes a single Wii partition: its AES title key and two sector ranges.
+ /// Size: 0x30 bytes.
+ ///
+ public sealed class PartitionEntry
+ {
+ ///
+ /// Decrypted AES-128 partition title key
+ ///
+ /// 16 bytes
+ public byte[] PartitionKey { get; set; } = new byte[16];
+
+ /// First sector range for this partition (typically encrypted data)
+ public PartitionDataEntry DataEntry0 { get; set; } = new();
+
+ /// Second sector range for this partition (typically decrypted/raw data)
+ public PartitionDataEntry DataEntry1 { get; set; } = new();
+ }
+}
diff --git a/SabreTools.Data.Models/WIA/RawDataEntry.cs b/SabreTools.Data.Models/WIA/RawDataEntry.cs
new file mode 100644
index 00000000..8d6c845c
--- /dev/null
+++ b/SabreTools.Data.Models/WIA/RawDataEntry.cs
@@ -0,0 +1,21 @@
+namespace SabreTools.Data.Models.WIA
+{
+ ///
+ /// Describes a region of non-partition data (e.g. disc header, partition table).
+ /// Size: 0x18 bytes.
+ ///
+ public sealed class RawDataEntry
+ {
+ /// Byte offset of this region within the equivalent ISO image
+ public ulong DataOffset { get; set; }
+
+ /// Size of this region in bytes
+ public ulong DataSize { get; set; }
+
+ /// Index into the group-entry array of the first group for this region
+ public uint GroupIndex { get; set; }
+
+ /// Number of groups covering this region
+ public uint NumberOfGroups { get; set; }
+ }
+}
diff --git a/SabreTools.Data.Models/WIA/WiaHeader1.cs b/SabreTools.Data.Models/WIA/WiaHeader1.cs
new file mode 100644
index 00000000..9e9703d2
--- /dev/null
+++ b/SabreTools.Data.Models/WIA/WiaHeader1.cs
@@ -0,0 +1,52 @@
+namespace SabreTools.Data.Models.WIA
+{
+ ///
+ /// 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.
+ ///
+ ///
+ public sealed class WiaHeader1
+ {
+ ///
+ /// Format magic: 0x01414957 ("WIA\x01") or 0x015A5652 ("RVZ\x01")
+ ///
+ public uint Magic { get; set; }
+
+ ///
+ /// Format version (e.g. 0x01000000)
+ ///
+ public uint Version { get; set; }
+
+ ///
+ /// Minimum version required to read this file
+ ///
+ public uint VersionCompatible { get; set; }
+
+ ///
+ /// Size of WiaHeader2 in bytes
+ ///
+ public uint Header2Size { get; set; }
+
+ ///
+ /// SHA-1 hash of WiaHeader2
+ ///
+ /// 20 bytes
+ public byte[] Header2Hash { get; set; } = new byte[20];
+
+ ///
+ /// Total size of the equivalent uncompressed ISO image in bytes
+ ///
+ public ulong IsoFileSize { get; set; }
+
+ ///
+ /// Total size of this WIA / RVZ file in bytes
+ ///
+ public ulong WiaFileSize { get; set; }
+
+ ///
+ /// SHA-1 hash of this header, excluding this field itself
+ ///
+ /// 20 bytes
+ public byte[] Header1Hash { get; set; } = new byte[20];
+ }
+}
diff --git a/SabreTools.Data.Models/WIA/WiaHeader2.cs b/SabreTools.Data.Models/WIA/WiaHeader2.cs
new file mode 100644
index 00000000..f97dbac3
--- /dev/null
+++ b/SabreTools.Data.Models/WIA/WiaHeader2.cs
@@ -0,0 +1,100 @@
+namespace SabreTools.Data.Models.WIA
+{
+ ///
+ /// 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.
+ ///
+ public sealed class WiaHeader2
+ {
+ ///
+ /// Disc type: 1 = GameCube, 2 = Wii
+ ///
+ public WiaDiscType DiscType { get; set; }
+
+ ///
+ /// Compression algorithm applied to group data
+ ///
+ public WiaRvzCompressionType CompressionType { get; set; }
+
+ ///
+ /// Informational compression level used when writing (1–9)
+ ///
+ public int CompressionLevel { get; set; }
+
+ ///
+ /// Group / chunk size in bytes.
+ /// WIA requires exactly 2 MiB; RVZ accepts powers of 2 between 32 KiB and 2 MiB.
+ ///
+ public uint ChunkSize { get; set; }
+
+ ///
+ /// First 0x80 bytes of the disc image (unencrypted disc header)
+ ///
+ /// 0x80 bytes
+ public byte[] DiscHeader { get; set; } = new byte[0x80];
+
+ ///
+ /// Number of PartitionEntry structures that follow the raw-data entries
+ ///
+ public uint NumberOfPartitionEntries { get; set; }
+
+ ///
+ /// Size of each PartitionEntry in bytes
+ ///
+ public uint PartitionEntrySize { get; set; }
+
+ ///
+ /// File offset of the PartitionEntry array
+ ///
+ public ulong PartitionEntriesOffset { get; set; }
+
+ ///
+ /// SHA-1 hash of all PartitionEntry data
+ ///
+ /// 20 bytes
+ public byte[] PartitionEntriesHash { get; set; } = new byte[20];
+
+ ///
+ /// Number of RawDataEntry structures
+ ///
+ public uint NumberOfRawDataEntries { get; set; }
+
+ ///
+ /// File offset of the RawDataEntry array
+ ///
+ public ulong RawDataEntriesOffset { get; set; }
+
+ ///
+ /// Total size in bytes of all RawDataEntry structures
+ ///
+ public uint RawDataEntriesSize { get; set; }
+
+ ///
+ /// Number of group entries (WiaGroupEntry or RvzGroupEntry)
+ ///
+ public uint NumberOfGroupEntries { get; set; }
+
+ ///
+ /// File offset of the group-entry array
+ ///
+ public ulong GroupEntriesOffset { get; set; }
+
+ ///
+ /// Total size in bytes of all group entries
+ ///
+ public uint GroupEntriesSize { get; set; }
+
+ ///
+ /// Number of valid bytes in
+ ///
+ public byte CompressorDataSize { get; set; }
+
+ ///
+ /// Algorithm-specific compressor parameters (up to 7 bytes).
+ /// LZMA: 5-byte prop block. LZMA2: 1-byte dict-size code. Others: unused.
+ ///
+ /// 7 bytes
+ public byte[] CompressorData { get; set; } = new byte[7];
+ }
+}