PROT.DAT / DMY.DAT TOC Confirmed
PROT.DAT is the disc's single big asset archive: 1233 numbered entries holding nearly everything in the game - every texture (TIM), 3D model (TMD), sound bank (VAB), dialog container (MES), animation set (ANM), move table (MDT), streaming buffer, scene asset table, and runtime code overlay. Only the movies, streamed audio, and the executable live outside it. This page documents its table of contents (TOC): the index that says where each entry starts and how big it is.
Overview
An "entry" (or "slot") is one numbered region of the archive - the unit the game requests when it loads assets. The TOC measures everything in sectors and LBAs (a sector is the disc's 2048-byte block; an LBA is a sector number counting from the start of the file). Two wrinkles make this TOC more interesting than a plain offset table, and both are covered below:
- Trailing-overlay sectors: for ~24% of entries the game reads past the size the TOC declares - the extra sectors carry real content (e.g. PROT 899's trailing 60 sectors are the title-screen overlay code, a chunk of MIPS code the game loads into RAM on demand).
- Two index spaces, off by 2: the game's in-RAM copy of the TOC counts entries from a different origin than this project's extraction, so every index recovered from game code must be shifted by 2 - the root cause of a whole family of historical mislabelings.
DMY.DAT is a sibling archive at the disc root that turns out to be developer fixtures (memory-bus test pattern + paired random blobs), not game data; see DMY.DAT. Implementation: crates/prot/src/archive.rs.
Header (8 bytes at offset 0x000 OR 0x800)
u32 file_count_minus_1
u32 header_sectors // size of TOC in 0x800-byte sectors
The detector tries offset 0x000 first, then 0x800, accepting whichever yields plausible values. PROT.DAT uses 0x000.
TOC (immediately after header)
The TOC is a flat array of u32 words (toc[] below - word 0 is the first u32 after the 8-byte header). p is the 0-based entry index (the extraction index); each entry contributes one start-LBA word at toc[p+2], so the formulas below reach into neighbouring entries' words (toc[p+3] is both entry p's footprint end and entry p+1's start). For entry index p:
start_lba = toc[p + 2] // absolute LBA into PROT.DAT
indexed_size_sectors = toc[p + 5] - toc[p + 3] + 4 // TOC-declared payload size
footprint_sectors = toc[p + 3] - toc[p + 2] // on-disc span to next entry
size_sectors = max(indexed_size_sectors, footprint_sectors)
byte_offset = start_lba * 0x800
size_bytes = size_sectors * 0x800
toc[p+5] is the absolute LBA of entry p+3 (an end-marker that aliases the next-entry's start), so toc[p+5] - toc[p+3] + 4 recovers the indexed size in sectors.
Trailing-overlay sectors
For ~24% of entries the on-disc contiguous range to the next entry's start LBA is larger than the indexed payload - the trailing sectors carry overlay content the SCUS boot loader (SCUS_942.54 is the game's main executable) reads via a multi-sector ReadN past the TOC-claimed end. PROT entry 899 is the canonical example: indexed payload is 14 sectors (28 KiB, the options menu), but the on-disc footprint is 74 sectors - the trailing 60 sectors are the title-screen overlay code (see boot.md). The extractor surfaces these trailing sectors so the on-disc footprint matches what the boot loader actually reads.
Archive::read_entry reads the full footprint; Archive::read_entry_indexed reads only the indexed sub-region. Scene-side parsers were designed for the indexed view and use read_entry_indexed via ProtIndex::entry_bytes; asset-viewer / disc-browser consumers use the full footprint so trailing-overlay content is visible.
In-RAM TOC + the two index spaces
SCUS_942.54 keeps a copy of the TOC at RAM address 0x801C70F0. Used at FUN_8003E8A8 (the LBA resolver; FUN_80xxxxxx names are Ghidra-traced function addresses in the executable):
start_lba = TABLE[(idx + 2) * 4 + 0x801C70F0]
end_lba = TABLE[(idx + 3) * 4 + 0x801C70F0]
size_sectors = end_lba - start_lba
The in-RAM copy is raw PROT.DAT from byte 0 - FUN_8003E4E8 reads the first three sectors of PROT.DAT into 0x801C70F0 at boot, header words included (byte-verified against a live save state's RAM). There is no transformation; but the index space differs by 2 from the extraction's: the extraction (crates/prot, and the NNNN in extracted/PROT/NNNN_*.BIN) builds its toc[] array after the two file-header words, so extraction entry p's start_lba sits at file word p + 4, while the resolver's TABLE[(idx + 2)] is file word idx + 2.
The canonical rule: raw TOC index = extraction index + 2. Any PROT index recovered from a FUN_8003E8A8 argument must subtract 2 to land in extraction space (byte-verified for the battle side-band files: raw TOC indices 0x37F/0x380 resolve to extraction entries 893/894, see summon-readef). Raw-TOC entries 0 and 1 cover the pre-init_data boot-UI region (LBA 3..120) that extraction indexing leaves unindexed: two TIM-packs holding the boot-resident system-UI bundle (menu-glyph atlas, sprite sheets, cursor parts; uploaded once at boot by FUN_800198E0 with flat-strip CLUT semantics, parser legaia_asset::system_ui_bundle)
- see tim-pack § boot-resident system-UI instance. CDNAME.TXT's #define numbers are authored in this raw-TOC space - the extractor's filename labels are shifted +2 relative to the content the defines name; see cdname § numbering space.
Resolving entries by name vs by index
Two entry points:
FUN_8003E8A8- index-based (consumed directly by the streaming loader and the dev-build sound branch).FUN_8003E6BC- path-based; resolves dev paths likedata\battle\efect.datorh:\PROT\FIELD\<scene>\…into an index via the CDNAME-driven name map, then delegates to the LBA resolver. Most retail-build code paths land here.
Names come from CDNAME.TXT, which lives at the top level of the disc.
Superseded readings
toc[p+5] - toc[p+2]computes the SIZE in sectors, not the start LBA. Read as a start LBA it collapses ~80% of PROT entries onto the same few low-LBA byte ranges - anything derived from that formula's outputs is artefacted; trust onlystart_lba = toc[p+2]extractions.- "The boot loader transforms the TOC into a different in-RAM layout" - falsified; the in-RAM copy at
0x801C70F0is rawPROT.DATfrom byte 0, and only the index space differs (by 2).