Correct: overlay.bin is the seven-section program, not the .lit section
The Phase 3 port brought parse_overlay_sections with it, which makes the overlay's
shape checkable for the first time. It corrects a claim I committed when
vendoring the decomp.
What I wrote then: overlay.bin +0x00 is 0x1A7C80, that is the .lit load base, "so
the first header word is the load base of the section this file is", and therefore
"overlay.bin IS the level's .lit section".
It is not. overlay.bin is the whole seven-section program. .lit is its first
section: 39,388 bytes at 0x1A7C80 on level 0, against a lump of 2,751,268.
section 0 .lit 0x1A7C80 on all 27 levels
section 1 .bss kind 8 (NOBITS) on all 27, zero non-zero bytes
sections 2-6 .data, lvl.vtbl, lvl.camvtbl, lvl.sndvtbl, .text
kind sequence [1, 8, 1, 1, 1, 1, 1] identical on 27/27
section sizes fall 112 bytes short of the lump on 27/27
(each section's size counts its own 16-byte header)
The error was reasoning from a constant to a statement about the file's nature. A
load base tells you where something is placed, not what the containing file is.
That is the same move as the other four withdrawals this session -- the 305x
class-ID slot, the UV-corner texcoords, the VIF code word, the DAW pointer -- all
of which read a single plausible item as evidence about the whole.
Worth noting what survives. The collision assert string is in SECTION 0 on all 27
levels, so the earlier $gp-relative search located it correctly even though the
reasoning about the file was wrong. The string being in .lit and the file being
.lit are different statements and only the first is true. A wrong reason can reach
a right answer; it is still worth correcting, because the next person to reason
from it would not.
Asserted in overlay_bin_is_the_seven_section_program_not_the_lit_section.
259 tests.
This commit is contained in:
@@ -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<Vec<u32>> = BTreeSet::new();
|
||||
let mut shortfall: BTreeMap<u32, usize> = BTreeMap::new();
|
||||
let mut string_in: BTreeMap<usize, usize> = 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<_>>(),
|
||||
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"
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user