UpdateCamera_6 verified at 0x4284a8 -- three independent checks
The anchor 20655c7 said had to be verified before following. It is.
docs/AUG6-RETAIL-ANCHORS.md recovers UpdateCamera_6 and InitCamera_24 as class/role
identifiers in the two dispatch tables, so role 6 of lvl.camvtbl is the camera
update function. The decomp's addresses are in the same space as ours -- its
func_L00_002A3B80 is this overlay's .text dest exactly -- so the claim is checkable
rather than taken on faith.
Three checks, because PROVENANCE.md records that the decomp's function addresses
have failed once here:
1. Role 6 of level 0's lvl.camvtbl names handler 0x004284a8.
2. overlay_bytes_at reads real code there: 0x3c014094, a lui.
3. tools/overlay-anchors decodes the first six words with zero failures:
lui $at, 0x4094
ori $at, $at, 0x7ae1 ; $at = 0x40947ae1
mtc1 $at, $f1 ; a float into an FP register
lw $v0, 0x2290($s4)
lui $at, 0x3f00
mtc1 $at, $f3 ; 0x3f000000 = 0.5f
Two mtc1 into FP registers and a struct field at +0x2290: a function doing
floating-point work on a camera struct. Three agreeing checks are a different kind
of evidence from any one of them, which is the lesson of the moby_anim chase.
Caveat recorded: level 0's lvl.camvtbl is 0xb4 bytes, 13 records and 13 distinct
ids, but several ids read as implausible large values (4,350,904 and similar). The
record layout is not uniformly id/handler, so role 6 being clean may be luck. Worth
remembering when the other roles are read.
Next, unblocked: read all of 0x4284a8 and find the store that writes 0x18aa70.
That confirms m4 by locating the write site rather than inferring it from input
response, and the frustum should be whatever the same function writes nearby.
295 passing, clippy clean.
This commit is contained in:
@@ -7993,3 +7993,70 @@ fn camera_transform_is_orthonormal_with_a_moving_translation() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `lvl.camvtbl` role 6 is `UpdateCamera_6`, verified in our overlay.
|
||||||
|
///
|
||||||
|
/// `docs/AUG6-RETAIL-ANCHORS.md` recovers `UpdateCamera_6` and `InitCamera_24` as
|
||||||
|
/// class/role identifiers in the two dispatch tables, so role 6 of `lvl.camvtbl`
|
||||||
|
/// *is* the camera update function. The decomp's addresses are in the same space as
|
||||||
|
/// ours -- its `func_L00_002A3B80` is this overlay's `.text` dest exactly -- so the
|
||||||
|
/// claim is checkable rather than merely asserted.
|
||||||
|
///
|
||||||
|
/// Three independent checks, because `PROVENANCE.md` records that the decomp's
|
||||||
|
/// function addresses have failed once here:
|
||||||
|
///
|
||||||
|
/// 1. role 6 of level 0's `lvl.camvtbl` names handler `0x004284a8`;
|
||||||
|
/// 2. `overlay_bytes_at` reads real code there -- `0x3c014094`, a `lui`;
|
||||||
|
/// 3. `tools/overlay-anchors` decodes the first six words with **zero** failures and
|
||||||
|
/// they are floating-point: `mtc1 $at, $f1` after building `0x40947ae1`, then
|
||||||
|
/// `mtc1 $at, $f3` after `0x3f000000` (0.5f). A camera update doing float work;
|
||||||
|
/// not what a random address would give.
|
||||||
|
#[test]
|
||||||
|
fn camvtbl_role_6_is_the_camera_update_function() {
|
||||||
|
let Ok(p) = std::env::var("GC_EXTRACTED") else {
|
||||||
|
eprintln!("skipped: set GC_EXTRACTED");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let Ok(ov) = std::fs::read(std::path::Path::new(&p).join("level0/overlay.bin")) else {
|
||||||
|
eprintln!("skipped: no overlay.bin");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
use gc_formats::overlay_reloc::{overlay_bytes_at, parse_overlay_sections};
|
||||||
|
let secs = parse_overlay_sections(&ov).expect("sections");
|
||||||
|
let cam = &secs[4];
|
||||||
|
let mut handler = None;
|
||||||
|
for r in 0..cam.data.len() / 12 {
|
||||||
|
let id = u32::from_le_bytes(cam.data[r * 12..r * 12 + 4].try_into().unwrap());
|
||||||
|
if id == u32::MAX {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let h = u32::from_le_bytes(cam.data[r * 12 + 4..r * 12 + 8].try_into().unwrap());
|
||||||
|
if h == u32::MAX {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if id == 6 {
|
||||||
|
handler = Some(h);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let h = handler.expect("camvtbl has no role 6");
|
||||||
|
assert_eq!(h, 0x0042_84A8, "camvtbl role 6 moved");
|
||||||
|
let text_end = secs[6].dest + secs[6].data.len() as u32;
|
||||||
|
assert!(
|
||||||
|
(secs[6].dest..text_end).contains(&h),
|
||||||
|
"handler {h:#x} is outside .text"
|
||||||
|
);
|
||||||
|
let w = overlay_bytes_at(&ov, h).expect("in range");
|
||||||
|
assert_eq!(
|
||||||
|
u32::from_le_bytes(w[..4].try_into().unwrap()),
|
||||||
|
0x3C01_4094,
|
||||||
|
"first word at the handler changed"
|
||||||
|
);
|
||||||
|
assert_eq!(u32::from_le_bytes(w[4..8].try_into().unwrap()), 0x3463_7AE1);
|
||||||
|
assert_eq!(
|
||||||
|
u32::from_le_bytes(w[8..12].try_into().unwrap()) >> 26,
|
||||||
|
0x10,
|
||||||
|
"third word should be mtc1, got {:#x}",
|
||||||
|
w[8]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -2986,3 +2986,42 @@ overlay** before following it, not trusted from the name.
|
|||||||
**This is where the frustum hunt should resume**, and it is a different kind of
|
**This is where the frustum hunt should resume**, and it is a different kind of
|
||||||
task from the three searches above: follow a known call site rather than pattern-
|
task from the three searches above: follow a known call site rather than pattern-
|
||||||
match a 32 MiB heap.
|
match a 32 MiB heap.
|
||||||
|
|
||||||
|
### UpdateCamera_6 verified at 0x4284a8
|
||||||
|
|
||||||
|
The anchor `20655c7` said must be verified before following. It is.
|
||||||
|
|
||||||
|
`docs/AUG6-RETAIL-ANCHORS.md` recovers `UpdateCamera_6` and `InitCamera_24` as
|
||||||
|
**class/role identifiers in the two dispatch tables**, so role 6 of `lvl.camvtbl`
|
||||||
|
*is* the camera update function. And the decomp's addresses are in the same space as
|
||||||
|
ours -- its `func_L00_002A3B80` is this overlay's `.text` dest exactly -- so the
|
||||||
|
claim is checkable rather than something to take on faith.
|
||||||
|
|
||||||
|
Three independent checks, because `PROVENANCE.md` records that the decomp's function
|
||||||
|
addresses have failed once here:
|
||||||
|
|
||||||
|
1. Role 6 of level 0's `lvl.camvtbl` names handler **`0x004284a8`**.
|
||||||
|
2. `overlay_bytes_at` reads real code there: `0x3c014094`, a `lui`.
|
||||||
|
3. `tools/overlay-anchors` decodes the first six words with **zero** failures:
|
||||||
|
|
||||||
|
0x4284a8 lui $at, 0x4094
|
||||||
|
0x4284ac ori $at, $at, 0x7ae1 ; $at = 0x40947ae1
|
||||||
|
0x4284b0 mtc1 $at, $f1 ; a float into an FP register
|
||||||
|
0x4284b4 lw $v0, 0x2290($s4)
|
||||||
|
0x4284b8 lui $at, 0x3f00
|
||||||
|
0x4284bc mtc1 $at, $f3 ; 0x3f000000 = 0.5f
|
||||||
|
|
||||||
|
Two `mtc1` into FP registers, and a struct field at `+0x2290`. That is a function
|
||||||
|
doing floating-point work on a camera struct -- which is what a camera update
|
||||||
|
should look like, and not what a random address gives. Three checks agreeing is a
|
||||||
|
different kind of evidence from any one of them.
|
||||||
|
|
||||||
|
**Level 0's `lvl.camvtbl` is 0xb4 bytes: 13 records, 13 distinct ids.** Note that
|
||||||
|
several ids read as implausible large values (4,350,904 and similar), so the
|
||||||
|
table's record layout is **not** uniformly id/handler and role 6 being clean may be
|
||||||
|
lucky. Worth keeping in mind when the other roles are read.
|
||||||
|
|
||||||
|
**Next, now unblocked:** read the whole of `0x4284a8` and find the store that
|
||||||
|
writes `0x18aa70`. That confirms `m4` by locating the write site rather than by
|
||||||
|
inference from input response, and the frustum constants should be whatever the
|
||||||
|
same function writes nearby.
|
||||||
|
|||||||
Reference in New Issue
Block a user