diff --git a/crates/rrt-runtime/src/smp.rs b/crates/rrt-runtime/src/smp.rs index a171940..21700a5 100644 --- a/crates/rrt-runtime/src/smp.rs +++ b/crates/rrt-runtime/src/smp.rs @@ -1711,6 +1711,8 @@ pub struct SmpSaveRegionRecordTripletEntryProbe { pub pre_name_prefix_len: usize, #[serde(default)] pub pre_name_prefix_hex_bytes: Vec, + #[serde(default)] + pub pre_name_prefix_dword_candidates: Vec, pub policy_chunk_len: usize, pub profile_chunk_len: usize, pub policy_leading_f32_0: f32, @@ -13195,6 +13197,10 @@ fn parse_save_region_record_triplet_probe( } let pre_name_prefix = records_payload.get(record_payload_relative_offset..name_tag_relative_offset)?; + let pre_name_prefix_dword_candidates = build_region_record_prefix_dword_candidates( + record_payload_relative_offset, + pre_name_prefix, + ); let name_payload = records_payload.get(name_tag_relative_offset + 4..policy_tag_relative_offset)?; let name = parse_save_len_prefixed_ascii_name(name_payload)?; @@ -13231,6 +13237,7 @@ fn parse_save_region_record_triplet_probe( .iter() .map(|byte| format!("0x{byte:02x}")) .collect(), + pre_name_prefix_dword_candidates, policy_chunk_len, profile_chunk_len, policy_leading_f32_0, @@ -13291,6 +13298,29 @@ fn parse_save_region_record_triplet_probe( }) } +fn build_region_record_prefix_dword_candidates( + record_payload_relative_offset: usize, + prefix_bytes: &[u8], +) -> Vec { + prefix_bytes + .chunks_exact(4) + .enumerate() + .map(|(index, chunk)| { + let raw_u32 = u32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]); + let relative_offset = record_payload_relative_offset + index * 4; + SmpSaveDwordCandidate { + label: format!("pre_name_prefix_word_{}", index + 1), + relative_offset, + relative_offset_hex: format!("0x{relative_offset:x}"), + raw_u32, + raw_u32_hex: format!("0x{raw_u32:08x}"), + value_i32: raw_u32 as i32, + value_f32: f32::from_bits(raw_u32), + } + }) + .collect() +} + fn parse_save_region_queued_notice_record_probe( bytes: &[u8], file_extension_hint: Option<&str>, @@ -23333,6 +23363,7 @@ mod tests { profile_tag_relative_offset: 0x2e, pre_name_prefix_len: 0, pre_name_prefix_hex_bytes: Vec::new(), + pre_name_prefix_dword_candidates: Vec::new(), policy_chunk_len: 0x1a, profile_chunk_len: 0x40, policy_leading_f32_0: 368.0, @@ -23374,6 +23405,7 @@ mod tests { profile_tag_relative_offset: 0x9c, pre_name_prefix_len: 0, pre_name_prefix_hex_bytes: Vec::new(), + pre_name_prefix_dword_candidates: Vec::new(), policy_chunk_len: 0x1a, profile_chunk_len: 0x20, policy_leading_f32_0: 552.0, @@ -23789,6 +23821,11 @@ mod tests { assert_eq!(triplet_probe.entries[0].policy_leading_f32_0, 368.0); assert_eq!(triplet_probe.entries[0].policy_leading_f32_1, 0.0); assert_eq!(triplet_probe.entries[0].policy_leading_f32_2, 92.0); + assert!( + triplet_probe.entries[0] + .pre_name_prefix_dword_candidates + .is_empty() + ); assert_eq!( triplet_probe.entries[0].policy_reserved_dwords, vec![0, 0, 0] @@ -23798,6 +23835,111 @@ mod tests { assert_eq!(triplet_probe.entries[1].policy_leading_f32_0, 552.0); assert_eq!(triplet_probe.entries[1].policy_leading_f32_1, 1.5); assert_eq!(triplet_probe.entries[1].policy_leading_f32_2, 276.0); + assert!( + triplet_probe.entries[1] + .pre_name_prefix_dword_candidates + .is_empty() + ); + } + + #[test] + fn parses_region_record_triplet_prefix_dword_candidates() { + let mut bytes = vec![0u8; 0x320]; + let metadata_tag_offset = 0x0usize; + let records_tag_offset = 0x100usize; + let close_tag_offset = 0x200usize; + bytes[metadata_tag_offset..metadata_tag_offset + 4] + .copy_from_slice(&0x00005209u32.to_le_bytes()); + bytes[records_tag_offset..records_tag_offset + 4] + .copy_from_slice(&0x0000520au32.to_le_bytes()); + bytes[close_tag_offset..close_tag_offset + 4].copy_from_slice(&0x0000520bu32.to_le_bytes()); + let mut cursor = records_tag_offset + 4; + let first_record_relative_offset = 0usize; + bytes[cursor..cursor + 2].copy_from_slice(&SAVE_REGION_RECORD_NAME_TAG.to_le_bytes()); + bytes[cursor + 4] = 8; + bytes[cursor + 5..cursor + 13].copy_from_slice(b"Marker11"); + cursor += 0x10; + bytes[cursor..cursor + 2].copy_from_slice(&SAVE_REGION_RECORD_POLICY_TAG.to_le_bytes()); + bytes[cursor + 4..cursor + 8].copy_from_slice(&100.0f32.to_bits().to_le_bytes()); + bytes[cursor + 8..cursor + 12].copy_from_slice(&2.0f32.to_bits().to_le_bytes()); + bytes[cursor + 12..cursor + 16].copy_from_slice(&50.0f32.to_bits().to_le_bytes()); + bytes[cursor + 28..cursor + 30].copy_from_slice(&1u16.to_le_bytes()); + cursor += 0x1e; + bytes[cursor..cursor + 2].copy_from_slice(&SAVE_REGION_RECORD_PROFILE_TAG.to_le_bytes()); + cursor += 0x20; + let second_record_relative_offset = cursor - (records_tag_offset + 4); + bytes[cursor..cursor + 4].copy_from_slice(&0x11223344u32.to_le_bytes()); + bytes[cursor + 4..cursor + 8].copy_from_slice(&0x55667788u32.to_le_bytes()); + cursor += 8; + bytes[cursor..cursor + 2].copy_from_slice(&SAVE_REGION_RECORD_NAME_TAG.to_le_bytes()); + bytes[cursor + 4] = 8; + bytes[cursor + 5..cursor + 13].copy_from_slice(b"Marker12"); + cursor += 0x10; + bytes[cursor..cursor + 2].copy_from_slice(&SAVE_REGION_RECORD_POLICY_TAG.to_le_bytes()); + bytes[cursor + 4..cursor + 8].copy_from_slice(&120.0f32.to_bits().to_le_bytes()); + bytes[cursor + 8..cursor + 12].copy_from_slice(&3.0f32.to_bits().to_le_bytes()); + bytes[cursor + 12..cursor + 16].copy_from_slice(&60.0f32.to_bits().to_le_bytes()); + bytes[cursor + 28..cursor + 30].copy_from_slice(&1u16.to_le_bytes()); + cursor += 0x1e; + bytes[cursor..cursor + 2].copy_from_slice(&SAVE_REGION_RECORD_PROFILE_TAG.to_le_bytes()); + let directory_root_byte_offset = SAVE_REGION_COLLECTION_DIRECTORY_ROOT_DWORD_INDEX * 4; + let first_payload_relative_offset = records_tag_offset - metadata_tag_offset; + let second_payload_relative_offset = + first_payload_relative_offset + second_record_relative_offset; + bytes[metadata_tag_offset + 4 + directory_root_byte_offset + ..metadata_tag_offset + 8 + directory_root_byte_offset] + .copy_from_slice(&(first_payload_relative_offset as u32).to_le_bytes()); + bytes[metadata_tag_offset + 16 + directory_root_byte_offset + ..metadata_tag_offset + 20 + directory_root_byte_offset] + .copy_from_slice(&(second_payload_relative_offset as u32).to_le_bytes()); + + let header_probe = SmpSaveTaggedCollectionHeaderProbe { + profile_family: "rt3-105-save-container-v1".to_string(), + source_kind: "save-region-tagged-header-counts".to_string(), + semantic_family: "scenario-save-region-header-counts".to_string(), + metadata_tag_offset, + records_tag_offset, + close_tag_offset, + direct_collection_flag: 0, + direct_collection_flag_hex: "0x00000000".to_string(), + direct_record_stride: 0x06, + direct_record_stride_hex: "0x00000006".to_string(), + live_id_bound: 0x96, + live_id_bound_hex: "0x00000096".to_string(), + live_record_count: 2, + live_record_count_hex: "0x00000002".to_string(), + header_words: vec![0, 6, 0x0a, 0x14, 0x96, 0x02], + header_hex_words: vec![], + evidence: vec![], + }; + let triplet_probe = parse_save_region_record_triplet_probe(&bytes, Some(&header_probe)) + .expect("region triplet probe should parse"); + + assert_eq!(triplet_probe.entries.len(), 2); + assert_eq!( + triplet_probe.entries[0].record_payload_relative_offset, + first_record_relative_offset + ); + assert_eq!(triplet_probe.entries[0].pre_name_prefix_len, 0); + assert_eq!( + triplet_probe.entries[1].record_payload_relative_offset, + second_record_relative_offset + ); + assert_eq!(triplet_probe.entries[1].pre_name_prefix_len, 8); + assert_eq!( + triplet_probe.entries[1] + .pre_name_prefix_dword_candidates + .len(), + 2 + ); + assert_eq!( + triplet_probe.entries[1].pre_name_prefix_dword_candidates[0].raw_u32_hex, + "0x11223344" + ); + assert_eq!( + triplet_probe.entries[1].pre_name_prefix_dword_candidates[1].relative_offset_hex, + "0x52" + ); } #[test] @@ -25906,6 +26048,7 @@ mod tests { profile_tag_relative_offset: 0, pre_name_prefix_len: 0, pre_name_prefix_hex_bytes: Vec::new(), + pre_name_prefix_dword_candidates: Vec::new(), policy_chunk_len: 0, profile_chunk_len: 0, policy_leading_f32_0: 368.0, diff --git a/docs/rehost-queue.md b/docs/rehost-queue.md index b2a7677..1418d46 100644 --- a/docs/rehost-queue.md +++ b/docs/rehost-queue.md @@ -544,6 +544,11 @@ Working rule: `[region+0x2a4]` or `[region+0x310/+0x338/+0x360]`, and they still do not touch `[region+0x276/+0x302/+0x316]`. That means the remaining region restore target is now the later owner that rebuilds those latches or the separate tagged body seam that persists them. +- The save-side region payload probe is wider now too: the checked-in `region_record_triplets` + surface no longer stops at raw pre-name prefix bytes and now also emits structured prefix dword + candidates per record. That gives the next region payload pass a direct way to compare the + opaque pre-`0x55f1` band against the remaining acquisition-side lane shapes instead of redoing + raw hex inspection by hand. - The rest of `0x00455fc0` is ruled down further now too: after the `+0x48` callback it only runs `0x0052ebd0`, which reads two one-byte generic flags through `0x531150` into base object bytes `[this+0x20]`, `[this+0x8d]`, `[this+0x5c..+0x61]`, `[this+0x1ee]`, `[this+0x1fa]`, and