File Formats

Wiithon can parse several Nintendo formats directly, whether they come from an ISO or from a standalone file on disk.

Note

Every format class follows the same shape: read(stream) to parse, write(stream) or get_bytes() to serialise back. All of them are importable from the top-level package, for example from wiithon import Rarc.

Tip

To edit a file that lives inside an ISO. possibly nested in an archive. you usually want patcher.edit_as() rather than these classes directly. See Patching a Wii ISO.

RARC

RARC is Nintendo’s archive format, used to bundle multiple files into one.

from io import BytesIO
from wiithon import Rarc

with open("archive.arc", "rb") as f:
    rarc = Rarc.read(f)

# Listing
for entry in rarc.entries:
    print(entry.name, entry.data_size)

# Reading and replacing, by name
data = rarc.get_file("model.bmd")
rarc.replace_file("model.bmd", new_data)

# ...or by full path inside the archive
data = rarc.get_file_by_path("scene/model.bmd")
rarc.replace_file_by_path("scene/model.bmd", new_data)

# Serialising back
result = rarc.get_bytes()

# Extracting to disk
rarc.extract_to("output/")

See also

RARC Format for the on-disc structure and the hashing scheme.

Yaz0

Yaz0 is Nintendo’s compression format. Many RARC archives are Yaz0-compressed. look for the Yaz0 magic bytes at offset 0.

from io import BytesIO
from wiithon import Yaz0

# Decompressing
with open("file.szs", "rb") as f:
    yaz0 = Yaz0.read(f)
raw = yaz0.data

# Compressing
compressed = Yaz0.from_data(raw)
result = compressed.get_bytes()

Important

Yaz0.read() decompresses immediately: yaz0.data is the uncompressed payload, not the compressed stream. Reading a non-Yaz0 file raises InvalidFormatError.

See also

Yaz0 Format for the block and pointer encoding.

U8

U8 is the archive format used for banners and channel content.

from wiithon import U8

with open("banner.bin", "rb") as f:
    u8 = U8.read(f)

data = u8.get_file("meta/banner.tpl")
u8.replace_file("meta/banner.tpl", new_data)

u8.extract_to("output/")
result = u8.get_bytes()

BNR

opening.bnr holds the banner shown in the Wii menu: the localised titles (IMET header) plus an embedded U8 archive with the icon, banner and sound.

from io import BytesIO
from wiithon import BNR

bnr = BNR.read(BytesIO(patcher.read_file("opening.bnr")))

print(bnr.title)

bnr.imet.set_title("Modded game", language="English")

icon   = bnr.get_icon()      # meta/icon.bin
banner = bnr.get_banner()    # meta/banner.bin
sound  = bnr.get_sound()     # meta/sound.bin

bnr.replace_sound(custom_sound)

patcher.replace_file("opening.bnr", bnr.get_bytes())

Note

For a simple title change, patcher.modify_banner_title() does all of the above in one call.

DOL

The DOL is the main executable format for GameCube and Wii games.

from wiithon import DOL

with open("main.dol", "rb") as f:
    dol = DOL.read(f)

value = dol.read_at(0x80123456, 4)
dol.write_at(0x80123456, b"\\xde\\xad\\xbe\\xef")

result = dol.to_bytes()

See also

Patching a Wii ISO for injecting new code, and PowerPC Instruction Reference for the PowerPC instruction encodings.

BCSV

BCSV is a table format. Think of a CSV with a typed, hashed header. Field names are stored as hashes, so you have to supply the names you expect.

from wiithon import BCSV
from wiithon.formats.bcsv import calculate_field_hash

field_names = {
    calculate_field_hash("ScenarioNo"): "ScenarioNo",
}

bcsv = BCSV.read(stream, field_names=field_names, str_fmt="shift_jis")

for entry in bcsv.entries:
    print(entry["ScenarioNo"])

Warning

The hash-to-name function is one-way: a field whose name you did not supply shows up under its stringified hash. Passing the wrong str_fmt silently produces some issues rather than an error. shift_jis is the usual choice for Japanese-developed titles.

LZ77

LZ77 compression, used inside banner assets.

from wiithon import Lz77

compressed = Lz77.compress(raw_data)
raw_data   = Lz77.uncompress(compressed, len(raw_data))