Working with the DOL
The DOL is the game executable. Everything the game does is here. Most non-trivial patches touch it.
Layout
A DOL is a header of 0x100 bytes followed by raw section data. There are no relocations and no symbol table.
Each section declares the virtual address it must be loaded at and the console copies it.
A DOL holds at most 7 text sections and 11 data sections. Unused slots have a length of zero.
from wiithon import WiiIsoReader
with WiiIsoReader("path/to/iso") as reader:
partition = reader.open_partition(reader.get_data_partition())
dol = partition.read_dol()
print(dol.header)
Which prints something like:
entry: 8000403C
bss: 805F5A40 — 806ADF90 (size: 000B8550)
text[0]: 80004000 — 800064E0 (size: 000024E0)
text[1]: 800070A0 — 8052D280 (size: 005261E0)
data[0]: 800064E0 — 800069A0 (size: 000004C0)
...
Three things to read off that dump:
addresses start at
0x80000000, which is where MEM1 is mapped. A Wii has 24 MB of MEM1, so valid code addresses run up to roughly0x81800000text[0]is almost always the same small block across games. It is the init stubbssis declared but not stored. It is zeroed at load time, which is why it has a size but no offset
Warning
bss_start + bss_size is not the end of the game’s memory. The heap starts about 64 KB further up.
See Code injection before assuming anything past bss is free.
Reading and writing
You address the DOL by virtual address, not by file offset. Wiithon resolves which section contains the address and translates for you.
value = dol.read_at(0x80123456, 4)
dol.write_at(0x80123456, b"\x60\x00\x00\x00")
text = dol.read_until_null_at(0x805A1234)
write_at refuses to write outside any loaded section, so a typo in an address fails loudly instead of
corrupting a neighbouring section.
Finding free space
Compilers leave gaps. Alignment padding, removed code and unused stubs show up as long runs of nop
(0x60000000) or zero words. Those runs are called code caves and they are the simplest place to put a
small routine, since they need no section juggling at all.
for name, address, size in dol.find_code_caves(min_size=0x80):
print(f"{name:8} {address:#010x} {size:#x} bytes")
text[1] 0x801a4f60 0xc0 bytes
text[1] 0x8021b800 0x1a0 bytes
data[4] 0x80251340 0x100 bytes
The same scan is available from the command line:
wiithon dol caves game.iso --min-size 128
Warning
A cave being full of zeros does not prove it is unused. Zeroed data sections are common and a run of nops inside a text section may be a jump table the game fills at runtime. Test in an emulator before trusting one. Dolphin has a memory engine integrated if you run it in debug mode.
Tip
Caves are fine for a few dozen instructions. Past that, add a section instead. See Code injection. It will be more readable.
Adding a section
When there is a free slot, you can declare a whole new section at an address of your choosing.
if dol.has_free_text_section():
dol.add_text_section(0x80600000, my_code)
The address must not overlap any existing section and it must be somewhere the game will not scribble over. That second condition is the hard one and it is the whole subject of Code injection.
See also
DOL for the complete method list and Patching a Wii ISO for wiring a
patch into WiiIsoPatcher.