Range measured: all 27 levels, each sky region from the core index's sky offset to the next region. Method: read the 32-bit offset table at +0x20, bounded by staying inside the region, and parse each sub-object it names. Result: 149 sub-objects across 27 levels. Each opens with a count, then a bounding sphere of four floats at +0x10, then a texture offset at +0x20 and dimensions at +0x24/+0x26: +000: 26 00 00 00 | 00 00 00 00 00 00 00 00 00 00 00 00 +010: e0 8d b2 c1 | 9f d6 f6 3d | ef fc fb be | 1c 61 4e 41 +020: 90 99 05 00 | 1d 00 | 1d 00 | 00 00 e8 00 ... Count 1 to 38, width 3 to 81, radius 1.95 to 14.56, and the radius is positive on 149 of 149 -- the same sphere shape and the same reasoning as the shrub class header's. Correction to an earlier claim: the word at +0x0c is not the sub-object count. It reads 5 on level 0 where the table holds 6 entries, 3 on level 1 against 4, 7 on level 3 against 8, and 196617 on level 2 against 4. Across the disc offsets.len() == count+1 holds on 10 of 27 levels. The field is reported as SkyHeader::field_0c and nothing depends on it; the offset table enumerates the sub-objects and is self-limiting. That earlier claim came from level 0 alone, where 5 against 6 looked like a consistent off-by-one. It is not an off-by-one -- it is a field that happens to be small on most levels and is not a count at all. gc_formats::sky parses the header and the sub-objects. The gated test asserts the sphere is finite, the radius positive where positive at all, the dimensions non-zero, and the sub-object list non-empty on every level.
53 lines
1.6 KiB
Rust
53 lines
1.6 KiB
Rust
use gc_formats::level_core::LevelCoreHeader;
|
|
fn main() {
|
|
let core = std::fs::read("/home/reaper/Documents/gc-extracted/level0/core_data.bin").unwrap();
|
|
let idx = std::fs::read("/home/reaper/Documents/gc-extracted/level0/core_index.bin").unwrap();
|
|
let h = LevelCoreHeader::parse(&idx).unwrap();
|
|
let sky = h.sky as usize;
|
|
let end = [
|
|
h.collision as usize,
|
|
h.moby_classes.offset as usize,
|
|
core.len(),
|
|
]
|
|
.into_iter()
|
|
.filter(|&v| v > sky)
|
|
.min()
|
|
.unwrap_or(core.len());
|
|
let b = &core[sky..end];
|
|
let mut offs = Vec::new();
|
|
for k in 0..64 {
|
|
let at = 0x20 + k * 4;
|
|
if at + 4 > b.len() {
|
|
break;
|
|
}
|
|
let v = u32::from_le_bytes(b[at..at + 4].try_into().unwrap()) as usize;
|
|
if v == 0 || v >= b.len() {
|
|
break;
|
|
}
|
|
offs.push(v);
|
|
}
|
|
println!(
|
|
"RANGE: level0 sky, {} sub-objects from the +0x20 table",
|
|
offs.len()
|
|
);
|
|
for (i, &o) in offs.iter().enumerate() {
|
|
let next = offs.get(i + 1).copied().unwrap_or(b.len());
|
|
println!(" sub{i}: {o}..{next} = {} bytes", next - o);
|
|
let e2 = next.min(b.len());
|
|
let seg = &b[o..e2];
|
|
for r in 0..3 {
|
|
let q = r * 16;
|
|
if q + 16 <= seg.len() {
|
|
println!(
|
|
" +{q:03x}: {}",
|
|
seg[q..q + 16]
|
|
.iter()
|
|
.map(|x| format!("{x:02x}"))
|
|
.collect::<Vec<_>>()
|
|
.join(" ")
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|