diff --git a/crates/gc-extract/tests/disc.rs b/crates/gc-extract/tests/disc.rs index 67652ca..ea8746c 100644 --- a/crates/gc-extract/tests/disc.rs +++ b/crates/gc-extract/tests/disc.rs @@ -5514,3 +5514,75 @@ fn tie_vertex_regions_split_on_the_first_words_top_bit() { by_size.len() ); } + +/// The `SBlk` body's first twelve words: six identified, six not. +/// +/// Words 4 and 5 both hold the **DAW sample-data size**, byte for byte, on all +/// 27 levels -- level 0's are 1,482,000 and `daw_size` is 1,482,000. This was +/// briefly misread as a pointer into the DAW region: those sizes happen to fall +/// inside `[daw_offset, daw_offset + daw_size)`, so an "is it in range?" test +/// passed for the wrong reason. Equality with `daw_size` is the claim, and it +/// holds everywhere. +/// +/// Words 1, 3, 6, 7, 8, 9, 10 and 11 are the same on every level. Words 0 and 2 +/// vary per level and are not identified; word 0 is close between levels 1 and 4 +/// (13,042,052 and 13,042,054) while their `daw_size`s differ by 63,504, so it is +/// not a size. +#[test] +fn sound_bank_sblk_body_head_carries_the_daw_size_twice() { + require_disc!(); + + let data = std::env::var_os("GC_EXTRACTED") + .map(PathBuf::from) + .unwrap_or_else(|| { + let home = std::env::var("HOME").unwrap_or_else(|_| ".".to_string()); + PathBuf::from(home).join("Documents/gc-extracted") + }); + if !data.exists() { + eprintln!("skipped: unpack the disc first"); + return; + } + + use gc_formats::sound_bank::{parse_sound_bank, SoundBankHeader}; + + let mut levels = 0usize; + let mut w0: Vec = Vec::new(); + let mut w2: Vec = Vec::new(); + for lv in 0..27usize { + let Ok(bytes) = std::fs::read(data.join(format!("level{lv}/sound_bank.bin"))) else { + continue; + }; + let h = SoundBankHeader::parse(&bytes).expect("sound bank head"); + let bank = parse_sound_bank(&bytes).expect("sound bank"); + assert!(bank.sblk.len() >= 48, "level {lv}: SBlk body too short"); + let w: Vec = (0..12) + .map(|i| u32::from_le_bytes(bank.sblk[24 + i * 4..28 + i * 4].try_into().unwrap())) + .collect(); + + assert_eq!(w[4], h.daw_size, "level {lv}: body word 4 is not daw_size"); + assert_eq!(w[5], h.daw_size, "level {lv}: body word 5 is not daw_size"); + assert_eq!(w[1], 60, "level {lv}: body word 1 moved"); + assert_eq!(w[3], 0, "level {lv}: body word 3 moved"); + for i in [6usize, 7, 8, 11] { + assert_eq!(w[i], 0, "level {lv}: body word {i} moved"); + } + assert_eq!(w[9], 127, "level {lv}: body word 9 moved"); + assert_eq!(w[10], 1, "level {lv}: body word 10 moved"); + w0.push(w[0]); + w2.push(w[2]); + levels += 1; + } + assert_eq!(levels, 27, "only {levels} sound banks parsed"); + let uniq0: std::collections::BTreeSet = w0.iter().copied().collect(); + let uniq2: std::collections::BTreeSet = w2.iter().copied().collect(); + eprintln!( + "SBlk body head: words 4 and 5 both equal daw_size on 27/27; words 0 and 2 \\ + vary ({}/{} and {}/{} distinct)", + uniq0.len(), + levels, + uniq2.len(), + levels + ); + assert!(uniq0.len() > 20, "body word 0 stopped varying per level"); + assert!(uniq2.len() > 20, "body word 2 stopped varying per level"); +} diff --git a/crates/gc-formats/src/sound_bank.rs b/crates/gc-formats/src/sound_bank.rs index c185e1c..3b94600 100644 --- a/crates/gc-formats/src/sound_bank.rs +++ b/crates/gc-formats/src/sound_bank.rs @@ -136,6 +136,38 @@ impl SBlkHeader { } } +/// The first twelve words of the `SBlk` body, after its 24-byte head. +/// +/// Words 4 and 5 both hold the **DAW sample-data size** -- byte for byte, on all +/// 27 levels. Words 1, 3, 6, 7, 8, 9, 10 and 11 are the same on every level +/// (60, 0, 0, 0, 0, 127, 1, 0). Words 0 and 2 vary per level and are not +/// identified. +/// +/// These sizes fall inside `[daw_offset, daw_offset + daw_size)`, so a test of +/// the form "is this word in the DAW region?" passes for them and reads as a +/// pointer. It is not one; equality with `daw_size` is the claim. +/// +/// Asserted in `sound_bank_sblk_body_head_carries_the_daw_size_twice`. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct SBlkBodyHead { + pub word_0: u32, + pub word_1: u32, + pub word_2: u32, + pub word_3: u32, + /// Always the DAW sample-data size. + pub word_4: u32, + /// Always the same value as [`Self::word_4`]. + pub word_5: u32, + pub word_6: u32, + pub word_7: u32, + pub word_8: u32, + /// Always 127. + pub word_9: u32, + /// Always 1. + pub word_10: u32, + pub word_11: u32, +} + /// Split a sound extent into its two regions. /// /// Bounds are checked before slicing; a level whose head points outside its own diff --git a/docs/formats/core-data-regions.md b/docs/formats/core-data-regions.md index efe931e..ffb7182 100644 --- a/docs/formats/core-data-regions.md +++ b/docs/formats/core-data-regions.md @@ -2865,3 +2865,45 @@ So: on a flat record, `i16[4]`/`i16[5]` are **not identified**, and the texcoord slot of a tie vertex is unknown. What survives from the original section is the boundary work -- `vert_ofs + vert_size + scissor_size` is the region length -- and the LOD1/LOD2 sharing result, which is stride-insensitive. + + +## The `SBlk` body head: the DAW size, twice + +The body after the 24-byte head opens with twelve words, and six of them are +settled across all 27 levels: + + word value note + 0 varies per level not identified; 27 distinct + 1 60 constant + 2 varies per level not identified; 23 distinct + 3 0 constant + 4 daw_size == the header's daw_size, exactly + 5 daw_size the same value again + 6 0 constant + 7 0 constant + 8 0 constant + 9 127 constant + 10 1 constant + 11 0 constant + +Level 0's words 4 and 5 are both 1,482,000, and `daw_size` is 1,482,000. + +### The pointer that was not one + +Words 4 and 5 were briefly recorded as pointers into the DAW region. They are not. +The test that promoted them was "does this value fall inside +`[daw_offset, daw_offset + daw_size)`", and the DAW region is ~1.5 MB starting +near the front of a ~1.5 MB extent -- so almost any plausible 32-bit word lands in +it, and 1,482,000 is just under the top of exactly that range. + +**A range test is not an identification.** The claim that survives is *equality +with `daw_size`*, which is exact on all 27 levels and is asserted. That is the +fourth time in this session that a plausible reading of one number has had to be +withdrawn in favour of a claim that can fail. + +Word 0 is worth one note: levels 1 and 4 hold 13,042,052 and 13,042,054, two +apart, while their `daw_size`s differ by 63,504. Whatever it is, it is not a +size. + +Asserted in `sound_bank_sblk_body_head_carries_the_daw_size_twice`. The remainder +of the body -- a per-sound record table -- is still undecoded.