//! RE diagnostic: raw-dump a composite scene graph's node records — name, table //! pointers, and the FULL joint-table slot values (not just the 8 the decoder //! currently reads) — to derive the true TRS slot layout against captured //! ground truth. //! cargo run --release --example node_dump -- [slots] use sylpheed_formats::mesh::xbg7_descriptor_range; fn be32(d: &[u8], o: usize) -> u32 { if o + 4 > d.len() { return 0; } u32::from_be_bytes([d[o], d[o + 1], d[o + 2], d[o + 3]]) } fn be_f64(d: &[u8], o: usize) -> f64 { if o + 8 > d.len() { return f64::NAN; } f64::from_be_bytes(d[o..o + 8].try_into().unwrap()) } fn main() { let a: Vec = std::env::args().collect(); let bytes = std::fs::read(&a[1]).unwrap(); let comp = &a[2]; let nslots: usize = a.get(3).and_then(|s| s.parse().ok()).unwrap_or(12); let (desc, desc_end) = xbg7_descriptor_range(&bytes, comp).expect("composite found"); let d = &bytes[desc..desc_end]; println!("descriptor [{desc:#x}..{desc_end:#x}) len {:#x}", d.len()); let mut i = 0usize; while i + 3 <= d.len() { let starts = (i + 4 <= d.len() && &d[i..i + 4] == b"rou_") || (i + 3 <= d.len() && &d[i..i + 3] == b"GN_"); if !starts { i += 1; continue; } let mut j = i; while j < d.len() && d[j] != 0 && d[j].is_ascii_graphic() { j += 1; } let name = std::str::from_utf8(&d[i..j]).unwrap_or("?").to_string(); if name.ends_with("_col") || name.ends_with("_spc") || name.ends_with("_gls") || name.ends_with("_lum") { i = j.max(i + 1); continue; } // find the 0xFFFFFFFF end marker let mut ff = i; let scan_end = (i + 0x90).min(d.len().saturating_sub(4)); while ff < scan_end && be32(d, ff) != 0xFFFF_FFFF { ff += 4; } if ff >= scan_end || ff < 0x10 { i = j.max(i + 1); continue; } let t44 = be32(d, ff - 0x10) as usize; let t48 = be32(d, ff - 0x0C) as usize; let child = be32(d, ff - 0x08) as usize; let sib = be32(d, ff - 0x04) as usize; // Dump the record's u32s between name end and marker for structure context. let rec_u32: Vec = ((j + 1)..ff) .step_by(4) .map(|o| format!("{:08X}", be32(d, o))) .collect(); println!("\nNODE {name} @{i:#x} ff@{ff:#x} t44={t44:#x} t48={t48:#x} child={child:#x} sib={sib:#x}"); println!(" rec: {}", rec_u32.join(" ")); if t44 != 0 && t44 < d.len() { // Joint table: nslots pointer slots. Each slot record appears to be a // keyframe track: a value ALSO sits 0x48 BEFORE the pointed address // (the rest-pose key A), while the decoder currently reads ptr+8 (B). for k in 0..nslots { let p = be32(d, t44 + k * 4) as usize; if p == 0 || p + 24 > d.len() { continue; } let a = if p >= 0x48 { be_f64(d, p - 0x48) } else { f64::NAN }; let b = be_f64(d, p + 8); let head = if p >= 0x50 { be32(d, p - 0x50) } else { 0 }; let t_a = if p >= 0x50 { be_f64(d, p - 0x50) } else { f64::NAN }; let t_b = be_f64(d, p); println!( " slot{k:2} @{p:#x} head={head:08X}: A={a:12.5} (t={t_a:8.3}) B={b:12.5} (t={t_b:8.3})" ); } } i = j.max(i + 1); } }