Skip to the content.

Compression & packing

Compression methods

compression field of the disc struct:

ID Name Read support Write support Notes
0 None ✔ ✔ identity
1 Purge ✔ (WIA) ✖ (RVZ) WIA-only; rejected by RVZ readers
2 Bzip2 ✔ ✔ level 1–9
3 LZMA ✔ ✔ LZMA1, 7-Zip properties
4 LZMA2 ✔ ✔ raw LZMA2 chunk framing
5 Zstd ✔ ✔ level 1–22 (default 3)

compr_data (disc struct offset 0xD5)

Method Properties
None / Purge / Bzip2 / Zstd empty
LZMA 5 bytes: lc + lp·9 + pb·45 (7-Zip default 0x5D) + 4-byte little-endian dictionary size
LZMA2 1 byte: dictionary-size property (2^(prop/2+12) style, 7-Zip table)

The writer’s LZMA dictionary size depends on the level: 1<<18 (≤1), 1<<20 (≤3), 1<<23 (≤5), 1<<25 (≤7), 1<<27 otherwise; position bits 2, literal context 3, literal position 0.

Stream formats written by RVZSharp

Group compression semantics

RVZ packing (junk detection)

Wii discs are full of padding — pseudo-random junk produced by Nintendo’s generator. Instead of storing it, RVZ stores a seed that regenerates the junk, plus segment headers. Packing is optional (Packing option / --no-packing); a file without packing is perfectly valid.

The junk PRNG

Segment stream

A packed chunk is a sequence of segments; rvz_packed_size is the total bytes of the segment stream (headers included):

Segment Layout
literal u32 BE size (no MSB) + size literal bytes
junk u32 BE size | 0x80000000 + 68-byte seed (junk regenerated, not stored)

Special case: if a chunk contains no junk at all, the writer stores the whole chunk literally without any headers and rvz_packed_size stays 0 — readers treat 0 as “no packing”.

The reader processes segments sequentially and skips the PRNG by the running data offset ((chunkStart + bytesEmitted) % 0x8000) before each junk segment.

Seed recovery (GetSeed)

LaggedFibonacciGenerator.GetSeed(data, size, dataOffsetMod) implements Dolphin’s reverse algorithm:

  1. Quick filter: the first 521 words must satisfy the generator’s bit-structure property (bits 22–23 == bits 20–21 of the swapped word); random data fails almost always.
  2. Fill the 521-word state from the junk and rewind it to position 0 (partial Backward for the word offset, full Backward per 521-word cycle).
  3. Reconstruct the 17-word seed (undoing the transform and recovering bits 16–17 from the neighbouring words), re-derive words 17..520 and verify them.
  4. Re-advance to the junk position and count how many bytes the seed actually reproduces.

GetSeed is best-effort: junk regions that fail the checks (or diverge) are stored literally. The writer scans forward in 32 KiB-aligned steps with a running data offset (offset % 0x8000), records zero runs as zero-junk when uncompressed, and emits segments per chunk.

Recovered seeds are canonical: the writer’s generator and the reader’s PRNG agree on them even though their internal word formats differ (the reconstruction makes the seed invariant under the output transform).

Why Zstd by default?

Dolphin’s default for RVZ is Zstandard — best ratio/speed trade-off for game data, and the only method that consistently beats LZMA2 on random-ish data. LZMA2 is the strongest compressor for highly compressible data; Bzip2 is a middle ground; None is useful for speed-critical round trips.