diff --git a/crates/gc-extract/tests/disc.rs b/crates/gc-extract/tests/disc.rs index ea8746c..ed433b9 100644 --- a/crates/gc-extract/tests/disc.rs +++ b/crates/gc-extract/tests/disc.rs @@ -5586,3 +5586,103 @@ fn sound_bank_sblk_body_head_carries_the_daw_size_twice() { assert!(uniq0.len() > 20, "body word 0 stopped varying per level"); assert!(uniq2.len() > 20, "body word 2 stopped varying per level"); } + +/// `overlay.bin` is the whole seven-section program, not the `.lit` section. +/// +/// This corrects a committed claim. `overlay.bin +0x00` is `0x1A7C80` on all 27 +/// levels, and that address is indeed the `.lit` load base -- but it is the load +/// base of the program's **first section**, which says nothing about the file. +/// The earlier note reasoned from a constant to "the load base of the section +/// this file is" and concluded the file *was* `.lit`. It is not. +/// +/// What the sections actually are, now that `parse_overlay_sections` exists: +/// +/// section 0 .lit at 0x1A7C80 on all 27 levels +/// section 1 .bss NOBITS (kind 8) on all 27, 0 non-zero bytes +/// sections 2-6 .data, lvl.vtbl, lvl.camvtbl, lvl.sndvtbl, .text +/// +/// The kind sequence `[1, 8, 1, 1, 1, 1, 1]` is identical on every level, and the +/// collision assert string is in **section 0 on all 27** -- which is why the +/// earlier `$gp`-relative search over `overlay.bin` found the string in the right +/// place even though the reasoning about the file was wrong. +/// +/// The sections do not tile the lump exactly: their sizes sum to 112 bytes short +/// on every level, because each section's `size` counts its own 16-byte header. +#[test] +fn overlay_bin_is_the_seven_section_program_not_the_lit_section() { + 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::overlay_reloc::parse_overlay_sections; + use std::collections::{BTreeMap, BTreeSet}; + + let needle = b"Camera_CollPrimTest WARNING!"; + let mut first_dest = BTreeSet::new(); + let mut kinds: BTreeSet> = BTreeSet::new(); + let mut shortfall: BTreeMap = BTreeMap::new(); + let mut string_in: BTreeMap = BTreeMap::new(); + let mut levels = 0usize; + + for lv in 0..27usize { + let Ok(ov) = std::fs::read(data.join(format!("level{lv}/overlay.bin"))) else { + continue; + }; + let secs = parse_overlay_sections(&ov).expect("sections"); + assert_eq!( + secs.len(), + 7, + "level {lv}: {} sections, expected 7", + secs.len() + ); + first_dest.insert(secs[0].dest); + kinds.insert(secs.iter().map(|s| s.kind).collect()); + let sum: usize = secs.iter().map(|s| s.data.len()).sum(); + *shortfall.entry(ov.len() as u32 - sum as u32).or_default() += 1; + for (i, s) in secs.iter().enumerate() { + if s.data.windows(needle.len()).any(|w| w == needle) { + *string_in.entry(i).or_default() += 1; + } + } + levels += 1; + } + + assert_eq!(levels, 27, "only {levels} overlays parsed"); + assert_eq!( + first_dest.iter().copied().collect::>(), + vec![0x1A7C80], + "the first section's load base moved" + ); + assert_eq!( + kinds.len(), + 1, + "the section kind sequence is no longer identical across levels" + ); + assert_eq!( + kinds.iter().next().unwrap().as_slice(), + &[1, 8, 1, 1, 1, 1, 1] + ); + assert_eq!( + string_in.len(), + 1, + "the assert string moved between sections" + ); + assert_eq!( + string_in.get(&0).copied(), + Some(27), + "the string is not in section 0" + ); + eprintln!( + "overlay.bin: 7 sections, first at 0x1A7C80 on 27/27, kinds \ + [1,8,1,1,1,1,1]; section sizes fall {shortfall:?} bytes short of the lump" + ); +}