> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/dolphin-emu/dolphin/llms.txt
> Use this file to discover all available pages before exploring further.

# WIA/RVZ Disc Image Format

> Technical specification for the WIA and RVZ disc image formats used by Dolphin Emulator

The WIA (Wii Archive) and RVZ formats are compressed disc image formats for GameCube and Wii games. These formats offer superior compression compared to older formats like GCZ by supporting modern compression algorithms and storing Wii partition data decrypted without hashes.

## Format Overview

### WIA Features

* Support for compression algorithms: bzip2, LZMA, and LZMA2
* Wii partition data stored decrypted and without hashes for better compression
* Chunk-based compression enabling random access
* Version 1.00 as implemented in wit v2.40a

### RVZ Enhancements

RVZ is based on WIA with the following improvements:

* Zstandard compression support
* Removed PURGE compression method
* Support for chunk sizes smaller than 2 MiB (minimum 32 KiB, must be power of two)
* Enhanced `wia_group_t` struct with per-group compression flags
* Lossless storage of pseudorandom padding data

## Data Format Conventions

<Note>
  All integers are **big endian** unless otherwise noted. Data in WIA/RVZ files can be stored in any order unless explicitly specified.
</Note>

The type `sha1_hash_t` refers to an array of 20 bytes.

## File Structures

### wia\_file\_head\_t

Stored at offset `0x0`, size `0x48` bytes. This format will never be changed according to wit source code.

<Accordion title="Version Number Format">
  ```
  Format: AABBCCDD = A.BB | A.BB.CC
  If D != 0x00 && D != 0xff => append: 'beta' D
  ```
</Accordion>

| Type and Name                | Description                                                                                                                         |
| ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| `char magic[4]`              | Always contains `"WIA\x1"`                                                                                                          |
| `u32 version`                | The WIA format version                                                                                                              |
| `u32 version_compatible`     | Minimum version required to read this file. Can be lower than `version` (wit v2.40a: version=`0x01000000`, compatible=`0x00090000`) |
| `u32 disc_size`              | Size of the `wia_disc_t` struct. wit v2.40a always includes the full 7 bytes of `compr_data`                                        |
| `sha1_hash_t disc_hash`      | SHA-1 hash of the `wia_disc_t` struct. Number of bytes to hash is determined by `disc_size`                                         |
| `u64 iso_file_size`          | Original size of the disc (equivalent ISO file size)                                                                                |
| `u64 wia_file_size`          | Size of this WIA/RVZ file                                                                                                           |
| `sha1_hash_t file_head_hash` | SHA-1 hash of this struct, up to but not including `file_head_hash` itself                                                          |

### wia\_disc\_t

Stored at offset `0x48`, immediately after `wia_file_head_t`.

| Type and Name           | Description                                                                                                                    |
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
| `u32 disc_type`         | 0 = unknown, 1 = GameCube, 2 = Wii                                                                                             |
| `u32 compression`       | 0 = NONE, 1 = PURGE, 2 = BZIP2, 3 = LZMA, 4 = LZMA2, **5 = Zstandard (RVZ only)**                                              |
| `u32 compr_level`       | Compression level used (compressor-specific, informational only). **Treat as signed for RVZ** due to negative Zstandard levels |
| `u32 chunk_size`        | Size of data chunks. **WIA**: must be multiple of 2 MiB. **RVZ**: minimum 32 KiB, must be power of two if \<2 MiB              |
| `u8 dhead[0x80]`        | First 0x80 bytes of the disc image                                                                                             |
| `u32 n_part`            | Number of `wia_part_t` structs                                                                                                 |
| `u32 part_t_size`       | Size of one `wia_part_t` struct. If smaller than expected, fill missing bytes with `0x00`                                      |
| `u64 part_off`          | File offset where `wia_part_t` structs are stored (uncompressed)                                                               |
| `sha1_hash_t part_hash` | SHA-1 hash of the `wia_part_t` structs (`n_part * part_t_size` bytes)                                                          |
| `u32 n_raw_data`        | Number of `wia_raw_data_t` structs                                                                                             |
| `u64 raw_data_off`      | File offset where `wia_raw_data_t` structs are stored (compressed)                                                             |
| `u32 raw_data_size`     | Total compressed size of `wia_raw_data_t` structs                                                                              |
| `u32 n_groups`          | Number of `wia_group_t` structs                                                                                                |
| `u64 group_off`         | File offset where `wia_group_t` structs are stored (compressed)                                                                |
| `u32 group_size`        | Total compressed size of `wia_group_t` structs                                                                                 |
| `u8 compr_data_len`     | Number of used bytes in `compr_data` array                                                                                     |
| `u8 compr_data[7]`      | Compressor-specific data                                                                                                       |

<Accordion title="Compression-Specific Data">
  **NONE, PURGE, BZIP2**: `compr_data_len` is 0

  **LZMA** (5 bytes, 7-Zip SDK format):

  * Byte 0: Encodes `lc`, `pb`, `lp` parameters
  * Bytes 1-4: Dictionary size (little endian)

  Decoding byte 0:

  ```c theme={null}
  d = data[0];
  if (d >= (9 * 5 * 5))
    return SZ_ERROR_UNSUPPORTED;

  p->lc = d % 9;
  d /= 9;
  p->pb = d / 5;
  p->lp = d % 5;
  ```

  **LZMA2** (1 byte, 7-Zip SDK format):

  * Single byte encoding dictionary size

  ```c theme={null}
  #define LZMA2_DIC_SIZE_FROM_PROP(p) (((UInt32)2 | ((p) & 1)) << ((p) / 2 + 11))

  if (prop > 40)
    return SZ_ERROR_UNSUPPORTED;
  dicSize = (prop == 40) ? 0xFFFFFFFF : LZMA2_DIC_SIZE_FROM_PROP(prop);
  ```

  **Zstandard (RVZ)**: No compressor-specific data

  Preset dictionaries are **not used** for any compression method.
</Accordion>

### wia\_part\_data\_t

| Type and Name      | Description                                                                          |
| ------------------ | ------------------------------------------------------------------------------------ |
| `u32 first_sector` | Starting sector on disc for this data (1 sector = 32 KiB or 31 KiB excluding hashes) |
| `u32 n_sectors`    | Number of sectors covered by this struct                                             |
| `u32 group_index`  | Index of first `wia_group_t` struct. Other indices follow sequentially               |
| `u32 n_groups`     | Number of `wia_group_t` structs used for this data                                   |

### wia\_part\_t

Tracks Wii partition data that is encrypted and hashed on actual discs. Does not include the unencrypted partition header (ticket, TMD, certificate chain, H3 table).

<Note>
  For a typical game partition, `pd[0].first_sector * 0x8000` would be `0x0F820000`, not `0x0F800000`.
</Note>

Wii partition data is stored **decrypted with hashes removed**. For each `0x8000` bytes on disc, `0x7C00` bytes are stored in the WIA file (before compression).

| Type and Name           | Description                                                                                                                                           |
| ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| `u8 part_key[16]`       | Title key for this partition (128-bit AES). Already decrypted, can be used directly without Wii common key                                            |
| `wia_part_data_t pd[2]` | **Segment 0**: Small segment for management data (boot to FST). **Segment 1**: Remaining data. Split point is FST end offset rounded up to next 2 MiB |

### wia\_raw\_data\_t

Tracks disc data not stored as `wia_part_t`. Data is stored as-is (with compression applied).

<Warning>
  The first `wia_raw_data_t` has `raw_data_off` = `0x80` and `raw_data_size` = `0x4FF80`, but actually contains `0x50000` bytes. Handle this by rounding offset down to previous multiple of `0x8000` and adjusting size accordingly.
</Warning>

| Type and Name       | Description                                                            |
| ------------------- | ---------------------------------------------------------------------- |
| `u64 raw_data_off`  | Offset on disc where this data starts                                  |
| `u64 raw_data_size` | Number of bytes on disc covered by this struct                         |
| `u32 group_index`   | Index of first `wia_group_t` struct. Other indices follow sequentially |
| `u32 n_groups`      | Number of `wia_group_t` structs used for this data                     |

### wia\_group\_t (WIA)

Points directly to compressed disc data. Interpretation differs based on whether referenced by `wia_part_data_t` or `wia_raw_data_t`.

A group normally contains `chunk_size` bytes of decompressed data (or `chunk_size / 0x8000 * 0x7C00` for Wii partition data excluding hashes). The last group may contain less data.

| Type and Name   | Description                                                                                                                                                                                  |
| --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `u32 data_off4` | File offset where compressed data is stored, **divided by 4**                                                                                                                                |
| `u32 data_size` | Size of compressed data, including `wia_except_list_t` structs and required padding. **0 = special case**: all decompressed data is `0x00` and exception lists (if any) contain 0 exceptions |

### rvz\_group\_t (RVZ)

Expanded version of `wia_group_t` with per-group compression control.

| Type and Name         | Description                                                                                                                                                                                                            |
| --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `u32 data_off4`       | File offset where compressed data is stored, **divided by 4**                                                                                                                                                          |
| `u32 data_size`       | **Bit 31**: 1 if using compression method from `wia_disc_t`, 0 if using NONE. **Bits 0-30**: Size of compressed data including exception lists and padding. **0 = special case**: all data is `0x00` with 0 exceptions |
| `u32 rvz_packed_size` | Size after decompression but before RVZ unpacking. **0 = RVZ packing not used** for this group                                                                                                                         |

### wia\_exception\_t

Represents a 20-byte difference between recalculated hash data and original hash data.

<Accordion title="Implementation Notes">
  * When recalculating hashes for a group not evenly divisible by 2 MiB, treat missing bytes as zeroes
  * **wit**: Only outputs exceptions for actual hash data, not padding
  * **Dolphin**: Outputs exceptions for both hash data and padding. For 32-byte padding areas, writes two overlapping exceptions (first 20 bytes, last 20 bytes = 12 bytes overlap)
</Accordion>

| Type and Name      | Description                                                                                                                                                                                      |
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `u16 offset`       | Offset among the hashes. Offsets `0x0000`-`0x0400` map to `0x0000`-`0x0400` in full 2 MiB; offsets `0x0400`-`0x0800` map to `0x8000`-`0x8400`, etc. Resets to 0 for each new `wia_except_list_t` |
| `sha1_hash_t hash` | Hash to replace the automatically generated hash. Apply after calculating all hashes for current 2 MiB but before encrypting                                                                     |

### wia\_except\_list\_t

Each `wia_group_t` of Wii partition data contains one or more exception lists before the actual data.

**Number of lists per group**: Always `chunk_size / 0x200000`, even for undersized groups at partition end.

<Warning>
  **Implementation limits**:

  * Dolphin reading: 52×64 = 3328 exceptions max (unlimited for NONE/PURGE)
  * wit reading: Appears to support \~3008+ exceptions
  * Safe assumption: Reading code can handle **at least 3328** exceptions per list
</Warning>

| Type and Name                             | Description                              |
| ----------------------------------------- | ---------------------------------------- |
| `u16 n_exceptions`                        | Number of `wia_exception_t` structs      |
| `wia_exception_t exception[n_exceptions]` | Each entry describes one hash difference |

<Accordion title="Compression Method Exceptions">
  **Storage**:

  * **PURGE**: Exception lists stored uncompressed (before first `wia_segment_t`)
  * **BZIP2, LZMA, LZMA2, Zstandard**: Compressed with the rest of the data

  **Padding**:

  * **NONE, PURGE**: Padding inserted after last exception list if end offset not divisible by 4
  * **Other methods**: No padding inserted
</Accordion>

### wia\_segment\_t

Used by the PURGE compression method to efficiently store runs of zeroes.

<Note>
  PURGE is only available in WIA, **not in RVZ**.
</Note>

| Type and Name   | Description                                                             |
| --------------- | ----------------------------------------------------------------------- |
| `u32 offset`    | Offset of `data` within decompressed data (exception lists not counted) |
| `u32 size`      | Number of bytes in `data`                                               |
| `u8 data[size]` | Data bytes                                                              |

Each PURGE chunk contains:

1. Zero or more `wia_segment_t` structs in ascending `offset` order
2. SHA-1 hash (0x14 bytes) of exception lists (if any) and segment structs
3. Bytes not covered by any segment are set to `0x00`

## RVZ Packing

RVZ introduces a packing encoding scheme for pseudorandom padding data, applied before bzip2/LZMA/Zstandard compression.

### Decoding Algorithm

1. Read 4 bytes as 32-bit unsigned big endian integer (`size`)
2. If **bit 31 is clear**: Read `size` bytes and output unchanged
3. If **bit 31 is set**: Clear bit 31 of `size`, read 68 bytes of PRNG seed data, output `size` bytes using PRNG
4. Repeat until all input consumed

### PRNG Algorithm

Lagged Fibonacci generator with parameters: f = xor, j = 32, k = 521

```c theme={null}
u32 buffer[521];

// Initialize buffer with seed data (big endian)
// Copy 68 bytes (17 words) to buffer start, byteswap if needed

// Fill remaining buffer
for (size_t i = 17; i < 521; i++)
    buffer[i] = (buffer[i - 17] << 23) ^ (buffer[i - 16] >> 9) ^ buffer[i - 1];

// Advance state (run 4 times before first output, then once per 521 words)
for (size_t i = 0; i < 32; i++)
    buffer[i] ^= buffer[i + 521 - 32];

for (size_t i = 32; i < 521; i++)
    buffer[i] ^= buffer[i - 32];
```

<Accordion title="Offset Alignment">
  Before outputting data, check if offset (relative to disc start for `wia_raw_data_t`, partition data start for `wia_part_t`) is evenly divisible by 32 KiB.

  If not, advance PRNG state by `offset % 0x8000` bytes first.

  <Note>
    For `wia_part_t`, hashes are not counted in offset calculation, but the number is still 32 KiB (not 31 KiB).
  </Note>
</Accordion>

### Output Generation

```c theme={null}
u8* out;
u32* buffer_ptr;

/* ... */

*(out++) = *buffer_ptr >> 24;
*(out++) = *buffer_ptr >> 18;  // NB: 18, not 16
*(out++) = *buffer_ptr >> 8;
*(out++) = *buffer_ptr;

buffer_ptr++;
```

## Implementation Notes

### Dolphin vs wit Differences

Where Dolphin's implementation differs from wit:

* Hash exception handling for padding areas
* Special handling of first `wia_raw_data_t` offset rounding
* Group size calculations for non-aligned partition ends

<Note>
  Both implementations are compatible for reading, but may produce slightly different files when writing.
</Note>
