mirror of
https://github.com/SabreTools/SabreTools.Serialization.git
synced 2026-07-08 18:06:41 +00:00
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:
40
SabreTools.Data.Models/GCZ/Archive.cs
Normal file
40
SabreTools.Data.Models/GCZ/Archive.cs
Normal 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 & ~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; }
|
||||
}
|
||||
}
|
||||
23
SabreTools.Data.Models/GCZ/Constants.cs
Normal file
23
SabreTools.Data.Models/GCZ/Constants.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
39
SabreTools.Data.Models/GCZ/GczHeader.cs
Normal file
39
SabreTools.Data.Models/GCZ/GczHeader.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
120
SabreTools.Data.Models/NintendoDisc/Constants.cs
Normal file
120
SabreTools.Data.Models/NintendoDisc/Constants.cs
Normal 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):
|
||||
// 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;
|
||||
/// <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
|
||||
}
|
||||
}
|
||||
28
SabreTools.Data.Models/NintendoDisc/Disc.cs
Normal file
28
SabreTools.Data.Models/NintendoDisc/Disc.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
81
SabreTools.Data.Models/NintendoDisc/DiscHeader.cs
Normal file
81
SabreTools.Data.Models/NintendoDisc/DiscHeader.cs
Normal 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 0x004–0x005; 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; <<2 for Wii)
|
||||
/// </summary>
|
||||
public uint DolOffset { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Offset of the File System Table (no shift for GameCube; <<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; }
|
||||
}
|
||||
}
|
||||
32
SabreTools.Data.Models/NintendoDisc/Enums.cs
Normal file
32
SabreTools.Data.Models/NintendoDisc/Enums.cs
Normal 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,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
namespace SabreTools.Data.Models.NintendoDisc
|
||||
{
|
||||
/// <summary>
|
||||
/// A single entry in the Wii disc partition table.
|
||||
/// The table lives at 0x40000–0x4FFFF 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 >> 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; }
|
||||
}
|
||||
}
|
||||
22
SabreTools.Data.Models/NintendoDisc/WiiRegionData.cs
Normal file
22
SabreTools.Data.Models/NintendoDisc/WiiRegionData.cs
Normal 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];
|
||||
}
|
||||
}
|
||||
45
SabreTools.Data.Models/WIA/Archive.cs
Normal file
45
SabreTools.Data.Models/WIA/Archive.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
47
SabreTools.Data.Models/WIA/Constants.cs
Normal file
47
SabreTools.Data.Models/WIA/Constants.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
40
SabreTools.Data.Models/WIA/Enums.cs
Normal file
40
SabreTools.Data.Models/WIA/Enums.cs
Normal 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,
|
||||
}
|
||||
}
|
||||
44
SabreTools.Data.Models/WIA/GroupEntries.cs
Normal file
44
SabreTools.Data.Models/WIA/GroupEntries.cs
Normal 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. >>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 >> 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 >> 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; }
|
||||
}
|
||||
}
|
||||
23
SabreTools.Data.Models/WIA/HashExceptionEntry.cs
Normal file
23
SabreTools.Data.Models/WIA/HashExceptionEntry.cs
Normal 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];
|
||||
}
|
||||
}
|
||||
40
SabreTools.Data.Models/WIA/PartitionEntry.cs
Normal file
40
SabreTools.Data.Models/WIA/PartitionEntry.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
21
SabreTools.Data.Models/WIA/RawDataEntry.cs
Normal file
21
SabreTools.Data.Models/WIA/RawDataEntry.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
52
SabreTools.Data.Models/WIA/WiaHeader1.cs
Normal file
52
SabreTools.Data.Models/WIA/WiaHeader1.cs
Normal 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];
|
||||
}
|
||||
}
|
||||
100
SabreTools.Data.Models/WIA/WiaHeader2.cs
Normal file
100
SabreTools.Data.Models/WIA/WiaHeader2.cs
Normal 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 (1–9)
|
||||
/// </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];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user