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]; + } +}