main.dol Model Routing

The game finds every model block in dt_na.dat through a table in main.dol: open the file by name, seek to an offset, read a number of bytes. Nothing inside dt_na.dat says where a model lives, so relocating a model (hammerspace) only means writing the block somewhere new and repointing its DOL entry. All values are big-endian. The constants below are for the US version.

Directory pointer table

A flat array of uint32 virtual addresses at DOL file offsets 0x69C828–0x69CAD8, one per directory. Convert an address to a DOL file offset by subtracting 0x80003F00. The index into this table is the chunk number (ChunkNumber in a .sluggie; also the number in front of each export folder name).

Directory entry

Each directory is an array of 48-byte entries (12 × uint32). The index into it is the file index (FileIndex in a .sluggie). The array ends at the first entry whose word 0 is not the dt_na.dat name pointer, or where the next directory's entries begin. Directories are stored back to back, so a walk that checks only the name pointer runs on into the following directories. It then meets the same physical entry again under other chunk numbers: for example, (76,197), (77,182) … (88,17) and (89,2) are all the entry at DOL offset 0x688A28. Each entry has three language slots of four words each, and every slot has the same layout:

Offset Type Description
0x0 uint32 File name pointer. Always 0x8067F658, the RAM address of the string "dt_na.dat"
0x4 uint32 Length of the block in bytes
0x8 uint32 Byte offset of the block inside dt_na.dat
0xc uint32 Allocation size. Equal to the length in every entry the tools write
Slot Entry offset
English (en) 0x00
Spanish (sp) 0x10
French (fr) 0x20

In every character directory, all three slots point at the same data. 118 entries do have diverging slots, all in non-character chunks (9, 10, 16, 119-123, 128, 138 and 145-148; surveyed in the input DOL on 2026-09-27). The tools read the en slot and write all three, which is correct for every character entry.

What an entry points at

Relocating a model (hammerspace)

  1. Build the new block (or the whole archive, with one member replaced and the later member offsets shifted by the size change).
  2. Write it at an offset at or above BASE_SIZE (715,046,144) in the extended dt_na.dat, on a 32-byte boundary.
  3. Write the new offset and length into all three language slots of the entry, and of every entry that shares its route (length also goes into the allocation-size word).
  4. Grow the dt_na.dat size field in fst.bin (FST entry 1, size at byte 0x14) to match the extended file.

The block a patch replaces is zero-filled, but only when no entry in the output main.dol still points into it (in any language slot). Otherwise it is left in place with a warning, so that one route's patch never blanks another route's live bytes.

Unpatching restores the entry from the untouched input main.dol, restores the original bytes from the input dt_na.dat, and zero-fills the hammerspace range. Unused-character entries are the exception (below). The implementation is SluggiesTools/Hammerspace/HammerspaceHelper.py (readDolEntry, patchDolEntry, findSharedEntries, liveRoutesInto, removeModelFromHammerspace) and ArchiveContainer.py.

Defragmenting hammerspace

Zero-filled blocks are reused only when a later block fits into them, and dt_na.dat never shrinks by itself. start.py --defrag-hammerspace [--dry-run] (the GUI's Maintenance tab, Defragment) packs hammerspace again. The implementation is SluggiesTools/Hammerspace/Defrag.py.

First run on a real output (2026-10-13): 94 extents, 24.80 MB live of 25.40 MB, 93 moved, 629,568 bytes saved. All 8,460 routes read the same bytes as before, and only the 273 offset words of moved routes changed in main.dol.

Unused characters (chunks 89-94)

The six unused characters have no model data of their own in the stock game. Every file entry of theirs points at a block that a playable character also uses (input DOL, 2026-09-27):

Unused chunk Shares with Files
89 Unused Yoshi A24 Yoshi, 900-4, 6-14
90 Unused Yoshi B24 Yoshi, 890-4, 6-14
91 Unused Toad50 Purple Toad0-4, 6-14
92 Unused Pianta39 Pianta0-4, 6-14
93 Unused Kritter76 Kritter0-14
94 Unused Koopa30 Koopa0-4, 6-14

File layout of a character directory: 0 high-poly model, 1 L_ model, 2 bat, 3 left glove, 4 right glove, 5 placeholder, 6-14 ANMs. File 5 of about 65 character directories, including 89-92 and 94, is one shared 64-byte block at 0x4B33140. It is an archive header with count 1 and a slot at +0x20, and is all zeros after that.

File 5 survey of the character directories (2026-10-05): only Peach (dir 22, a second bat, 94103648_bat.gpl, 10,688 bytes) and Wario (dir 28, chall_g.gpl, 26,208 bytes) hold a real block. 30 directories (76-79, 83, 93 and the Miis' 95-118) hold a 32-byte all-zero block, the others the shared 64-byte block. Peach's second bat appears during her Star Swing (user observation, Dolphin, 2026-10-05), so file 5 seems tied to that animation. Whether the load is keyed on the file's content or on a per-character setting, what Wario's block does, and whether another character uses a block placed in its file 5 are not known. Bats and gloves (files 2-4) are one-bone models inside a 32-byte archive container, and are widely shared between directories: one vanilla bat block serves up to 13 routes.

Split routes

--untangle exports (export.py, clone_unused_dirs_to_hammerspace_for_untangle) copy every entry of chunks 89-94 into hammerspace and repoint only those entries. The playable owner keeps the vanilla location. The list of chunks lives in SluggiesTools/Hammerspace/UntanglePolicy.py.

Patching and unpatching a split route

Confirmed in Dolphin on Unused Yoshi A (2026-09-28/29). In each case green Yoshi and Unused Yoshi B stayed vanilla throughout: