# The `G/*.WAD` asset containers Target: `SCUS_972.68` v1.01 (USA Black Label, NTSC). Everything except the boot ELF, `SYSTEM.CNF`, `RC2.HDR` and the IOP image lives in a container under `G/` on the disc's ISO 9660 filesystem. R&C2 lists these as real files, unlike R&C1 whose filesystem is vestigial, so enumeration is a directory walk rather than a hunt for a table at a fixed sector. ## Container list 96 containers. The names follow the asset family; `n` runs 0..26 for the per-level containers. | File | Count | Bytes (disc) | Role | |---|---|---|---| | `ARMOR.WAD` | 1 | 2,480,432 | Armor and wrench meshes plus textures | | `AUDIO.WAD` | 1 | 35,283,296 | Global audio banks and music | | `AUDIO.WAD` | 27 | 3,825,304 .. 46,747,520 | Per-level audio | | `BONUS.WAD` | 1 | 31,762,085 | Collectibles, unlockables | | `GADGET.WAD` | 1 | 4,972,992 | Gadget definitions | | `HUD.WAD` | 1 | 37,927,695 | HUD elements and text | | `LEVEL.WAD` | 27 | 8,350,423 .. 25,119,448 | Level core, gameplay, occlusion | | `MISC.WAD` | 1 | 14,840,496 | Miscellaneous assets | | `MPEG.WAD` | 1 | 1,075,267,584 | Indexed video and subtitle streams | | `MPEG1.WAD` | 1 | 1,296,228,352 | Raw concatenated video, not indexed | | `MPEG2.WAD` | 1 | 114,769,924 | Raw concatenated video, not indexed | | `SCENE.WAD` | 1 | 4,466,256 | Global cutscene data | | `SCENE.WAD` | 27 | 4,988 .. 14,964,240 | Per-level cutscene data | | `SPACE.WAD` | 1 | 10,977,664 | Space level assets | The three MPEG WADs are 2.5 GB of the 3.8 GB disc. Video is a data problem before it is a decoding problem; the pipeline reads them in sectors rather than slurping whole files. ## Header Every indexed container begins with: ```text offset size field 0x00 4 header size in bytes 0x04 4 sector count of the whole container 0x08.. ... table, interpreted per container kind (below) ``` The header size is the reliable discriminator: it is the one field every container agrees on, and its value identifies the container family. | Header size | Containers | Table kind | |---|---|---| | `0x0040` | `MISC` | sector/byte pairs | | `0x0060` | `LEVEL` | sector/byte pairs | | `0x00f8` | `ARMOR` | sector/byte pairs | | `0x0170` | `SCENE` | sector/byte pairs | | `0x0328` | `MPEG` | sector/byte pairs | | `0x03c8` | `GADGET` | sector/byte pairs | | `0x0a48` | `BONUS` | sector/byte pairs | | `0x0ba8` | `SPACE` | sector/byte pairs | | `0x1018` | `AUDIO` | sector/byte pairs | | `0x137c` | `SCENE` | **flat sector list** | | `0x1800` | `AUDIO` | sector/byte pairs | | `0x1870` | `HUD` | sector/byte pairs | ## Sector/byte pairs The common form. From `0x08` to `header_size`, a run of 8-byte entries: ```text offset size field 0x00 4 first sector of the extent 0x04 4 length of the extent **in bytes** ``` The offset is in **sectors**, the size is in **bytes**. This is the single most important thing to get right, and it is easy to get backwards because both fields are 32-bit integers in a structure where either reading type-checks. **This was measured, not assumed.** For all 92 indexed pair containers: - Reading the size as bytes places every entry inside its file. - Reading the size as a sector count places entries past the end of the file, and on `LEVEL` and `AUDIO` places *none* of them correctly — 0 of 5 on `LEVEL0.WAD`, 0 of 42 on `AUDIO1.WAD`, and so on for all 27 of each. Offsets chain, which is the confirmation that the reading is right. On `AUDIO0.WAD`: sectors 3, 185, 367, 1458, where `185 = 3 + 182` sectors and each entry's end is the next entry's start. ### Payload is not compressed The bytes behind a pair are raw. Insomniac's LZ77 WAD container (see [`wad.md`](wad.md)) is used elsewhere on the disc — inside level cores and MPEG streams — but these global containers store their sections uncompressed and let the table do the addressing. A decompressor applied here produces garbage. ### Worked example: ARMOR.WAD `header_size = 0xf8`, so `(0xf8 - 8) / 8 = 30` entries. Reading the pairs: ```text entry 0: sector 1, 372,272 bytes entry 1: sector 124, 163,840 bytes (= 73 + 51 sectors earlier) ... entry 29: sector 1200, 24,576 bytes ``` The published structure is nine armor entries then six wrench entries, each a `{mesh, textures}` pair, which is 15 pairs — but the generic parser reads 30 entries because the header also declares the textures of the nine armors as their own ranges. Entry 0 is armor 0's mesh and entry 1 is armor 0's textures; entries 2 and 3 are armor 1's, and so on. Which pair means what is per-container knowledge, recovered when that asset family is parsed. ## Flat sector list (`SCENE.WAD`) The 27 per-level scene containers are the exception. `header_size = 0x137c`, and `0x137c - 8 = 4980` is **not** a multiple of 8, so the bytes cannot be pairs at all. They are a flat run of 1,245 `u32` sector numbers, 30 scenes of 83 values each. Each value is the first sector of one extent. The extent's length is the difference to the next value; the last runs to the end of the container. ```text sectors[0] = 3, sectors[1] = 7 -> extent 0 is sectors 3..7 (4 sectors) ``` The global `SCENE.WAD` is not one of these: it uses the pair form. ## Raw streams `MPEG1.WAD` and `MPEG2.WAD` have no table. Their first word is video payload, not a plausible header size (`7864320` and a negative value respectively), so they are copied through whole and split by the video phase. ## Known trailing sentinels A few containers declare entries whose extent runs past the end of the file: 1 in every `SCENE.WAD`, 10 in `GADGET.WAD`, 35 in `MPEG.WAD`. These are trailing sentinels or descriptors that reference sectors belonging to an adjacent container. The extractor bounds-checks every extent and skips these with a count, rather than reading past the end of the buffer or trusting the header. ## Verification Unpacking the disc produces 5,135 files totalling 4,500,106,960 bytes. Every file's size and SHA-1 are recorded in `crates/gc-extract/data/gc-files.tsv` (hashes only, no content). See [`../plan/disc-pipeline.md`](../plan/disc-pipeline.md) for how that gate is run. ## What is not yet known - Which pair in each container corresponds to which asset. Needed before any asset can be parsed; the geometry is recovered per family in Phase 2. - The `bindata` sub-structures inside audio and scene containers. - How `MPEG.WAD`'s indexed streams relate to the raw `MPEG1`/`MPEG2` files. Two descriptions of the same video data exist on the disc; which the game reads is not yet established.