Correct my own "VIF packets" reading: the split is real, the identification is not
Last commit read bit 31 of a tie vertex region's first word as "VIF code word"
and told a two-encoding story on it. The split is real; the identification is
wrong, and I checked it before building anything on top.
Walking those regions as VIF packets, levels 0-2, 914 records:
walk reaches the region end 17
walk dies after exactly ONE packet 1,571
packets before dying: min 0, median 1, max 4
tfrag walks VIF the same way -- VifCode::decode, then num * 4 payload words -- and
reaches the end of its lists every time. So the walk is not the problem; these
regions are not VIF packet streams. Bit 31 is set on whatever this word actually
is, and that is still unknown.
The specific thing that made me overreach: I quoted 0xf5c1a3c5 as "a VIF code
word, cmd 0x75 num 193" and treated that as establishing the format. Decoding one
word as a packet header says nothing about whether the next 193 words are its
payload. Here they are not -- the walk dies immediately after.
This is the third time in this area that one plausible word produced a confident
story: the 305x class-ID slot, the UV-corner texcoords, and now this. All three
shared a shape -- a single readable item at the head of a population, treated as
evidence about the whole. The bit is the only thing the two kinds reliably
differ on, so it stays; the name goes.
The test now asserts BOTH the split and the walk failure, which is the part worth
keeping. If these ever do turn out to be VIF, the test fails and says so, rather
than two findings drifting apart silently -- which is how "half VIF, half flat"
would have sat in the docs indefinitely.
Docs corrected in both roadmap and format file, including the quoted example.
258 tests.
This commit is contained in:
@@ -91,7 +91,12 @@ pub fn parse_overlay_sections(overlay: &[u8]) -> Result<Vec<OverlaySection>> {
|
||||
let data = b
|
||||
.peek_bytes(pos + 16, size, "overlay section payload")?
|
||||
.to_vec();
|
||||
out.push(OverlaySection { dest, kind, entry, data });
|
||||
out.push(OverlaySection {
|
||||
dest,
|
||||
kind,
|
||||
entry,
|
||||
data,
|
||||
});
|
||||
pos += 16 + size;
|
||||
}
|
||||
if out.is_empty() {
|
||||
@@ -106,13 +111,10 @@ pub fn parse_overlay_sections(overlay: &[u8]) -> Result<Vec<OverlaySection>> {
|
||||
|
||||
/// `n` bytes at EE address `addr` of the loaded overlay.
|
||||
pub fn read_overlay(sections: &[OverlaySection], addr: u32, n: usize) -> Option<&[u8]> {
|
||||
sections
|
||||
.iter()
|
||||
.filter(|s| s.kind != 8)
|
||||
.find_map(|s| {
|
||||
let off = addr.checked_sub(s.dest)? as usize;
|
||||
s.data.get(off..off.checked_add(n)?)
|
||||
})
|
||||
sections.iter().filter(|s| s.kind != 8).find_map(|s| {
|
||||
let off = addr.checked_sub(s.dest)? as usize;
|
||||
s.data.get(off..off.checked_add(n)?)
|
||||
})
|
||||
}
|
||||
|
||||
/// Code-word convenience: four bytes at `addr` as a little-endian `u32`.
|
||||
@@ -217,7 +219,12 @@ impl LevelOverlay {
|
||||
if addr < self.text_start || addr >= self.text_end() {
|
||||
return None;
|
||||
}
|
||||
let next = self.starts.range(addr + 4..).next().copied().unwrap_or(self.text_end());
|
||||
let next = self
|
||||
.starts
|
||||
.range(addr + 4..)
|
||||
.next()
|
||||
.copied()
|
||||
.unwrap_or(self.text_end());
|
||||
Some(
|
||||
(next
|
||||
.min(addr.saturating_add(MAX_EXTENT as u32))
|
||||
@@ -335,21 +342,13 @@ impl HiState {
|
||||
let s = rs(w);
|
||||
// `ori` completes a `lui` with the zero-extended immediate, the
|
||||
// others sign-extend it.
|
||||
let lo = if o == 13 {
|
||||
w & 0xffff
|
||||
} else {
|
||||
sext(w)
|
||||
};
|
||||
let lo = if o == 13 { w & 0xffff } else { sext(w) };
|
||||
let full = if s == 28 {
|
||||
Some(GP.wrapping_add(sext(w)))
|
||||
} else {
|
||||
self.0[s].map(|hi| (hi << 16).wrapping_add(lo))
|
||||
};
|
||||
let masked = if full.is_some() {
|
||||
w & 0xffff_0000
|
||||
} else {
|
||||
w
|
||||
};
|
||||
let masked = if full.is_some() { w & 0xffff_0000 } else { w };
|
||||
if writes_rt(o) && rt(w) != 0 {
|
||||
self.0[rt(w)] = None;
|
||||
}
|
||||
@@ -433,9 +432,9 @@ pub struct Relocation<'a> {
|
||||
impl<'a> Relocation<'a> {
|
||||
/// Identity when the target is the reference itself (same bytes).
|
||||
pub fn new(reference: &'a LevelOverlay, target: &'a LevelOverlay) -> Relocation<'a> {
|
||||
let identity =
|
||||
reference.text_start == target.text_start && reference.text == target.text
|
||||
&& reference.sections == target.sections;
|
||||
let identity = reference.text_start == target.text_start
|
||||
&& reference.text == target.text
|
||||
&& reference.sections == target.sections;
|
||||
let mut r = Relocation {
|
||||
reference,
|
||||
target,
|
||||
@@ -448,7 +447,10 @@ impl<'a> Relocation<'a> {
|
||||
}
|
||||
let t = &target.text;
|
||||
for p in 0..t.len().saturating_sub(3) {
|
||||
r.index.entry(key(&t[p..p + 4])).or_default().push(target.text_start + 4 * p as u32);
|
||||
r.index
|
||||
.entry(key(&t[p..p + 4]))
|
||||
.or_default()
|
||||
.push(target.text_start + 4 * p as u32);
|
||||
}
|
||||
r
|
||||
}
|
||||
@@ -473,7 +475,9 @@ impl<'a> Relocation<'a> {
|
||||
let Some(m) = self.ref_masked(ref_fn) else {
|
||||
return false;
|
||||
};
|
||||
self.target.code(target_fn, m.len()).is_some_and(|c| mask(c) == m)
|
||||
self.target
|
||||
.code(target_fn, m.len())
|
||||
.is_some_and(|c| mask(c) == m)
|
||||
}
|
||||
|
||||
/// Every copy of the reference function `ref_fn` in the target's `.text`.
|
||||
@@ -494,9 +498,7 @@ impl<'a> Relocation<'a> {
|
||||
cands
|
||||
.iter()
|
||||
.copied()
|
||||
.filter(|&c| {
|
||||
self.target.code(c, m.len()).is_some_and(|w| mask(w) == m)
|
||||
})
|
||||
.filter(|&c| self.target.code(c, m.len()).is_some_and(|w| mask(w) == m))
|
||||
.collect()
|
||||
}
|
||||
|
||||
@@ -522,7 +524,10 @@ impl<'a> Relocation<'a> {
|
||||
let xrefs = self.xrefs.get_or_init(|| {
|
||||
let mut x: HashMap<u32, Vec<(u32, usize)>> = HashMap::new();
|
||||
for &f in &self.reference.starts {
|
||||
let Some(code) = self.reference.extent(f).and_then(|n| self.reference.code(f, n))
|
||||
let Some(code) = self
|
||||
.reference
|
||||
.extent(f)
|
||||
.and_then(|n| self.reference.code(f, n))
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
@@ -558,12 +563,12 @@ impl<'a> Relocation<'a> {
|
||||
let hits: Vec<u32> = cands
|
||||
.iter()
|
||||
.copied()
|
||||
.filter(|&c| {
|
||||
self.target.code(c, n).is_some_and(|w| mask(w) == m)
|
||||
})
|
||||
.filter(|&c| self.target.code(c, n).is_some_and(|w| mask(w) == m))
|
||||
.collect();
|
||||
if let [g] = hits.as_slice() {
|
||||
if let Some(&(_, a)) = address_refs(self.target.code(*g, n)?).iter().find(|(o, _)| *o == off)
|
||||
if let Some(&(_, a)) = address_refs(self.target.code(*g, n)?)
|
||||
.iter()
|
||||
.find(|(o, _)| *o == off)
|
||||
{
|
||||
return Some(a);
|
||||
}
|
||||
@@ -582,14 +587,24 @@ mod tests {
|
||||
fn masking_follows_lui_through_moves_and_keeps_constants() {
|
||||
// lui v0,0x17 ; addiu s1,v0,0x42c0 ; move s2,v0 ; addiu s1,s2,0x42c0 ; lui at,0x3f80 ; lw t0,8(a0) ; lw t1,-0x10(gp)
|
||||
let w = [
|
||||
0x3c02_0017, 0x2451_42c0, 0x0040_902d, 0x2651_42c0, 0x3c01_3f80, 0x8c88_0008,
|
||||
0x3c02_0017,
|
||||
0x2451_42c0,
|
||||
0x0040_902d,
|
||||
0x2651_42c0,
|
||||
0x3c01_3f80,
|
||||
0x8c88_0008,
|
||||
0x8f89_fff0,
|
||||
];
|
||||
let m = mask(&w);
|
||||
assert_eq!(
|
||||
m,
|
||||
vec![
|
||||
0x3c02_0000, 0x2451_0000, 0x0040_902d, 0x2651_0000, 0x3c01_3f80, 0x8c88_0008,
|
||||
0x3c02_0000,
|
||||
0x2451_0000,
|
||||
0x0040_902d,
|
||||
0x2651_0000,
|
||||
0x3c01_3f80,
|
||||
0x8c88_0008,
|
||||
0x8f89_0000
|
||||
]
|
||||
);
|
||||
@@ -610,7 +625,10 @@ mod tests {
|
||||
fn mask_finds_one_copy_of_a_level_01_function_on_all_27_overlays() {
|
||||
let data = std::env::var_os("GC_EXTRACTED")
|
||||
.map(PathBuf::from)
|
||||
.unwrap_or_else(|| PathBuf::from(std::env::var("HOME").unwrap_or_default()).join("Documents/gc-extracted"));
|
||||
.unwrap_or_else(|| {
|
||||
PathBuf::from(std::env::var("HOME").unwrap_or_default())
|
||||
.join("Documents/gc-extracted")
|
||||
});
|
||||
if !data.exists() {
|
||||
eprintln!("skipped: unpack the disc first");
|
||||
return;
|
||||
@@ -618,7 +636,8 @@ mod tests {
|
||||
// Parse all 27 overlays and build their LevelOverlay.
|
||||
let mut overlays = Vec::with_capacity(27);
|
||||
for lv in 0..27usize {
|
||||
let ov = std::fs::read(data.join(format!("level{lv}/overlay.bin"))).expect(&format!("level{lv}/overlay.bin"));
|
||||
let ov = std::fs::read(data.join(format!("level{lv}/overlay.bin")))
|
||||
.expect(&format!("level{lv}/overlay.bin"));
|
||||
let secs = parse_overlay_sections(&ov).expect(&format!("level{lv} section parse"));
|
||||
let lvl = LevelOverlay::from_sections(secs).expect("LevelOverlay::from_sections");
|
||||
overlays.push(lvl.unwrap_or_else(|| panic!("level {lv}: no .text section")));
|
||||
@@ -630,8 +649,12 @@ mod tests {
|
||||
let mut ref_fn = 0;
|
||||
let mut m = Vec::new();
|
||||
for &fn_start in &ref_lv.starts {
|
||||
let ext = ref_lv.extent(fn_start).expect("extent for a function start");
|
||||
let c = ref_lv.code(fn_start, ext).expect("code for a function start");
|
||||
let ext = ref_lv
|
||||
.extent(fn_start)
|
||||
.expect("extent for a function start");
|
||||
let c = ref_lv
|
||||
.code(fn_start, ext)
|
||||
.expect("code for a function start");
|
||||
let masked = mask(c);
|
||||
if masked.len() >= 4 {
|
||||
ref_fn = fn_start;
|
||||
@@ -639,7 +662,10 @@ mod tests {
|
||||
break;
|
||||
}
|
||||
}
|
||||
assert!(!m.is_empty(), "no function with masked extent >= 4 in level 0 .text");
|
||||
assert!(
|
||||
!m.is_empty(),
|
||||
"no function with masked extent >= 4 in level 0 .text"
|
||||
);
|
||||
eprintln!("ref_fn 0x{ref_fn:x} masked to {} words", m.len());
|
||||
// On every other overlay, its relocated copy must be found exactly once.
|
||||
for (i, target) in overlays.iter().enumerate().skip(1) {
|
||||
|
||||
Reference in New Issue
Block a user