# RVZ container format RVZ is the modern Dolphin disc container (WIA's successor). This page documents the on-disk layout as implemented and verified by RVZSharp. The authoritative upstream description is `References/dolphin-master/docs/WiaAndRvz.md`; the C++ code in `References/dolphin-master/Source/Core/DiscIO/WIABlob.cpp` is the reference implementation. All integers are big-endian. ## File layout ``` ┌───────────────────────────────────────────┐ │ File head (0x48 bytes) │ ├───────────────────────────────────────────┤ │ Disc struct (0xDC bytes) │ ├───────────────────────────────────────────┤ │ Partition table (uncompressed) │ ├───────────────────────────────────────────┤ │ Raw data table (compressed) │ ├───────────────────────────────────────────┤ │ Group table (compressed) │ ├───────────────────────────────────────────┤ │ Group data (4-byte aligned) │ └───────────────────────────────────────────┘ ``` ## File head (0x48 bytes) | Offset | Size | Field | Meaning | |---|---|---|---| | 0x00 | 4 | magic | `52 56 5A 01` (`"RVZ\x01"`); WIA uses `"WIA\x01"` | | 0x04 | 4 | version | written as `0x01000000` | | 0x08 | 4 | version_compatible | `0x00030000` (RVZ), `0x00080000` (WIA) | | 0x0C | 4 | disc_size | size of the disc struct, `0xDC` | | 0x10 | 20 | disc_hash | SHA-1 over the disc struct bytes | | 0x24 | 8 | iso_file_size | decoded ISO length | | 0x2C | 8 | rvz_file_size | container file length | | 0x34 | 20 | file_head_hash | SHA-1 over bytes `0x00..0x34` of this head | The reader accepts the file when `ImplementedVersion (0x01000000) >= version_compatible`. ## Disc struct (0xDC bytes) | Offset | Size | Field | Meaning | |---|---|---|---| | 0x00 | 4 | disc_type | `1` = GameCube, `2` = Wii | | 0x04 | 4 | compression | 0 None, 1 Purge (WIA only), 2 Bzip2, 3 LZMA, 4 LZMA2, 5 Zstd | | 0x08 | 4 | compr_level | signed compression level | | 0x0C | 4 | chunk_size | power of two, 0x8000–0x200000 | | 0x10 | 0x80 | disc_header | first 0x80 bytes of the disc image (disc header) | | 0x90 | 4 | num_partitions | count of partition-table entries | | 0x94 | 4 | partition_entry_size | `0x30` | | 0x98 | 8 | partition_entries_offset | file offset of the partition table | | 0xA0 | 20 | partition_entries_hash | SHA-1 over `num_partitions × partition_entry_size` bytes | | 0xB4 | 4 | num_raw_data_entries | count of raw-table entries | | 0xB8 | 8 | raw_data_entries_offset | file offset of the raw table | | 0xC0 | 4 | raw_data_entries_size | **compressed** size of the raw table | | 0xC4 | 4 | num_groups | count of group-table entries | | 0xC8 | 8 | group_entries_offset | file offset of the group table | | 0xD0 | 4 | group_entries_size | **compressed** size of the group table | | 0xD4 | 1 | compr_data_len | length of `compr_data` | | 0xD5 | 7 | compr_data | codec properties (see [Compression](compression-packing.md)) | The raw and group tables are compressed with the disc's own compression method (decompressed size = `num_raw_data_entries × 24` and `num_groups × 12` respectively); their SHA-1s are verified over the **decompressed** bytes. The partition table is stored uncompressed. ## Partition table Each entry is 0x30 bytes: | Offset | Size | Field | |---|---|---| | 0x00 | 16 | `part_key` — the partition's title key | | 0x10 | 16 | data entry 0 (segment 0) | | 0x20 | 16 | data entry 1 (segment 1) | Each 16-byte data entry: | Offset | Size | Field | Meaning | |---|---|---|---| | 0x00 | 4 | first_sector | disc sector where this segment starts | | 0x04 | 4 | number_of_sectors | segment length in sectors (0x8000-byte units) | | 0x08 | 4 | group_index | first group of this segment in the group table | | 0x0C | 4 | number_of_groups | group count (informational; readers recompute) | A partition with no FST still produces two data entries; an entry with `number_of_sectors == 0` is skipped by readers. ## Raw data table Each entry is 24 bytes: | Offset | Size | Field | |---|---|---| | 0x00 | 8 | data_offset — disc-relative byte offset | | 0x08 | 8 | data_size | | 0x10 | 4 | group_index | | 0x14 | 4 | padding | Readers align `data_offset` down to the sector size and grow `data_size` by the same amount (that is how the first raw entry, which starts at `0x80`, covers the disc header bytes). ## Group table Each entry is 12 bytes: | Offset | Size | Field | |---|---|---| | 0x00 | 4 | data_offset — file offset **divided by 4** | | 0x04 | 4 | data_size; bit 31 = stored compressed | | 0x08 | 4 | rvz_packed_size — packed segment-stream length, or 0 | A `data_size` of 0 means a **zero group**: the chunk decodes to all zeroes and nothing is stored. ## Groups A group is the stored form of one chunk: ``` ┌───────────────────────────┐ │ exception lists │ (see below) ├───────────────────────────┤ │ main data │ literal payload or packed segment stream └───────────────────────────┘ ``` - For compression methods **> Purge** (Zstd/Bzip2/LZMA/LZMA2), the exception lists are prepended to the main data **before** compression and are part of the compressed stream. - For **None** (and Purge in WIA), the lists are stored uncompressed before the (possibly compressed) main data, and the last list is padded to a 4-byte boundary. - The group is flagged `compressed` only when the compressed size is smaller than the uncompressed size; otherwise the uncompressed bytes are stored. ### Exception lists ``` u16 count count × { u16 hash_offset, 20 bytes SHA-1 } ``` - `hash_offset` addresses a 20-byte hash inside the region's 0x400-byte hash area: `block_index_in_chunk × 0x400 + offset_in_block`. - Offsets are **chunk-relative**; the reader adds the chunk's offset within its 2 MiB region (`additional_offset = (chunkPayloadOffset % 0x1F0000) / 0x7C00 × 0x400`). - The number of lists per chunk is `max(1, chunkPayloadSize / 0x1F0000)` (one for every supported chunk size). ## Chunking and regions - The disc is divided into **chunks** of `chunk_size` bytes (power of two between 32 KiB and 2 MiB, or a multiple of 2 MiB above that — Dolphin's rule). - Raw data: one chunk per group. - Partition data: regions of **64 sectors = 2 MiB**; a chunk maps to one region for 2 MiB chunks, or to a sub-region slice for smaller chunks. - A Wii sector is 0x8000 bytes: 0x400 bytes of hash area + 0x7C00 bytes of data. See [Wii partitions](wii-partitions.md). ## Reading order (writer's output) 1. Validate the file head (magic, versions, `disc_hash`, `iso_file_size`). 2. Load and hash-check the three tables. 3. Serve reads by locating the covering area: - raw areas → decompress group → unpack segments (if `rvz_packed_size > 0`) → slice; - partition areas → decompress group → unpack segments → rebuild the region (decrypt payload, recompute hash tree, apply exceptions, AES re-encrypt).