CDNAME.TXT - entry name map Confirmed
The developers left a plain-text file at the root of the disc: a list of C-style #define lines that give human-readable names - town01, battle_data, music_01 - to the numbered entries of PROT.DAT, the disc's single big asset archive. It is the closest thing the disc has to a directory listing, and the game itself reads it at boot. It also hides a numbering trap: its numbers count entries the way the game does, which is two higher than the way this project's extraction does.
At a glance
- Where
CDNAME.TXTat the disc root; ASCII, one#define name Nper line. Retail loads it into a 16-byte-record table at RAM0x80088758- Numbering
Nis a raw TOC index; the content lives at extraction entryN − 2- Parser
crates/prot/src/cdname.rs:parse_str(tolerant),parse_retail_str/retail_name_table(byte-exact),block_for_extraction_index(applies the −2 rule)- Confidence
- Confirmed - loader traced; the shift is pinned by four retail loader constants and the v12-sidecar placement (how we know)
- Used by
- Asset loader,
prot-extractfilenames, every scene window in the engine, boot
What the file says
The file looks like the top of a C header:
#define init_data 0
#define gameover_data 1
#define town01 3
#define town0b 12
#define town0c 21
...
#define vab_01 1072
Each line names a block of consecutive archive entries - usually one scene (a town, a dungeon room, a menu) plus the handful of entries reserved for its assets. Two rules do all the work:
- Names inherit forward.
#define name Nstarts a block; every entry afterNbelongs to it until the next define. Entry 11 istown01becausetown0bonly starts at 12. - The numbers are the game's index space. The game counts raw TOC words, header included; extraction counts payload entries, which start two words later. So
#define name Nnames extraction entryN − 2.
Most blocks reserve 6-8 slots per scene. Unused slots hold the one-sector pochi filler, never a real asset; the edstati3 block is almost entirely filler.
Numbering space (the −2 correction)
The boot loader copies PROT.DAT verbatim - 8-byte header included - into RAM at 0x801C70F0 (PROT TOC), so raw index = extraction index + 2. The extractor's NNNN_<name>.BIN filenames apply define numbers as extraction indices directly, and are kept that way for stability - so a filename label is systematically two slots early.
#define | Raw TOC index | Extraction entry | Content |
|---|---|---|---|
town01 3 | 3 | 0001 | town01 slot 0 - the scene's .MAP |
| (inherits) | 4 | 0002 | slot 1 - the v12 trigger sidecar |
| (inherits) | 5 .. 11 | 0003 .. 0009 | slots 2..8 (prescript, 7-asset table, assets) |
town0b 12 | 12 | 0010 | town0b slot 0 - filename still says 0010_town01.BIN |
A label is a hint, not a verdict. When a shift-corrected block name still conflicts with the bytes, trust the bytes: the leading magic and the loader-call constant in the executable.
What the dev names really cover
With the shift applied the developers' names are accurate far more often than not. The entries most often misread from their filename:
| Extraction | Filename says | Retail block (content) |
|---|---|---|
| raw 0..1 | (unindexed) | init_data (the boot system UI with the menu-glyph atlas) + gameover_data (the GAME OVER screen's art) - raw entries 0 and 1 |
| 0863..0866 | edstati3/battle_data | battle_data - the four player battle files |
| 0867 | battle_data | monster_data - the monster stat / mesh / animation archive |
| 0870..0873 | sound_data/befect_data | befect_data - effect bundles (etim, etmd+vdf, billboards, efect.dat) |
| 0874 | befect_data | player_data - the field character-mesh pack |
| 0891 | level_up | monster_se - monster.snd, the 206-bank monster sound archive |
| 0892 | level_up | card_data - the memory-card screen's JIS X 0208 level-1 kanji font, a two-member pack of 4bpp TIMs (data-field) |
| 0893..0894 | monster_se/card_data | bat_back_dat - summon.dat / readef.DAT (summon-readef) |
| 0895..0969 | bat_back_dat/xxx_dat | xxx_dat - slot 0 is the boot init.pak; then the overlay code blobs |
| 0970..0971 | xxx_dat | move_program_no - movie program table (STR FMV table), not Tactical Arts |
| 0972..0977 | move_program_no/other_game | other_game - casino / minigame overlays |
| 1070..1192 | music_01/vab_01 | vab_01 - every entry a VAB bank |
The music_01 block's bank map is piecewise: BGM id 2000+i is extraction 988+i for i ≤ 67 and 990+i for i ≥ 68 (music tracks).
Caveats that do not contradict the shift
level_upentry 0890 is a streaming carrier with its VABs wrapped inside, so a bare-magic check misses it at every shift.- Entries 0888 (
sound_data2) and 1062 (music_01) carry no VAB header, and both are identified: 0888 isbse.dat, the battle SFX descriptor bank, and 1062 is a SEQ-onlymusic_01entry (sound-test track 72,ALNDRA) that borrows another entry's bank. Each also ends in a foreign tone table copied from a neighbour, which is what a bare-magic sweep sees. - Only 2 of the 6
other_gameentries carry anOTHER<n>banner. - Entry 0893 (
summon.dat) opens with a shape that mimics a sound-address bank; the byte pins show texture streaming slots. other7(extraction 1226+) is a dev-leftover earlier revision ofkoin3's field MAN - a name-exact prefix subset of its records, sharing its gate flags. No code loads it.- One v12-shaped header sits at 1227, outside the scene region.
Engine consequence: scene windows are the retail block
Scene::load converts the raw-TOC block range to the extraction frame so a scene's entries are its retail block: first the .MAP, then the v12 sidecar. An unshifted window is two entries late - it drops those and bleeds in the next block's first two, mis-framing any scene whose load-bearing entry sits at a block edge (an unshifted rikuroa window loads Jeremi's MAN). The two head defines (init_data 0, gameover_data 1) sit inside the TOC header rows and keep their unshifted legacy windows.
What retail holds in RAM: long names are mangled
The boot loader copies each name into a 16-byte record and then stores the index over bytes 12-13 of the same record. The table is BSS, so it starts zeroed; there is no length check and no NUL is ever written.
Read back as a C string, a record therefore holds:
| Name length | What the record holds |
|---|---|
| ≤ 11 | The name, terminated by a table zero. Clean. |
| 12-13 | The name with the index's two bytes overlaid at 12..13 and no terminator of its own. |
| 14-15 | As above, plus bytes 14-15 of the name, which survive past the index store. |
| ≥ 16 | Spills into the next record. No shipped name is this long; a modded CDNAME.TXT can be. |
Clean C-string capacity is 11 bytes, not 12. Five shipped names exceed it and none round-trips:
#define name | Index | In-RAM C string |
|---|---|---|
gameover_data | 1 | gameover_dat + 01 |
monster_data | 869 | monster_data + 65 03 |
bat_back_dat | 895 | bat_back_dat + 7F 03 |
move_program_no | 972 | move_program + CC 03 + o |
monster_test | 980 | monster_test + D4 03 |
- Repo tooling uses the tolerant
parse_str, which keeps the full spelling. Anything compared against retail RAM must go throughretail_name_table/parse_retail_str, which return names as bytes - the index bytes are routinely not valid UTF-8, and a lossy conversion printsfdfor every one of them. - The first line not starting with
#ends the map; a line with an empty name still emits a record. - Index 990 is declared twice (
music_test, thenmusic_01). Retail emits two records; the tolerant map keeps the later name. Compare the two readers by declaration order, not by index. - Retail really does populate the table:
main()sets the source flag at cold boot so the loader readscdname.txtoff the disc rather than over the developer-station link.
How we know
Provenance: the loader and the four index constants
| Function / evidence | Address | What it proves | Dump |
|---|---|---|---|
| CDNAME loader | FUN_8001D8FC | 16-byte records at 0x80088758; unbounded copy at 0x8001D980, index stores at 0x8001DA78/0x8001DA90 | 8001d8fc.txt |
| Source flag write | 0x80015F08 in main() | sh v0,0x5aa(gp) stores the 1 returned by a two-instruction leaf; the only writer among 70 access sites across 84 images, the other 69 all reads | SCUS disassembly |
| PROT resolver | FUN_8003E8A8 | Indexes the raw in-RAM TOC, header included | 8003e8a8.txt |
| Player-file loader | FUN_800558FC | Constants 0x361..0x364 = defines battle_data 865..868 | live trace |
| Monster SE loader | FUN_8003E104 | li v0,0x37d = define monster_se 893 | 8003e104.txt |
| Side-band loader | FUN_801F17F8 | 0x37F/0x380 = bat_back_dat 895; RAM↔disc byte-verified at extraction 893/894 | overlay_battle_801f17f8.txt |
| Overlay pager | FUN_8003EBE4 / FUN_8003EC70 | param + 0x381 = xxx_dat 897; param 2 = extraction 0897 | 8003ec70.txt |
| Structural check | v12 tables | All 96 scene-region sidecars sit at block slot 1 only at shift −2 (scene blocks vary 7..11 slots) | scripts/asset-investigation/cdname_shift_analysis.py |
Source of record: docs/formats/cdname.md.