Expose region payload prefix dword candidates

This commit is contained in:
Jan Petykiewicz 2026-04-18 19:25:36 -07:00
commit ad7576b65b
2 changed files with 148 additions and 0 deletions

View file

@ -1711,6 +1711,8 @@ pub struct SmpSaveRegionRecordTripletEntryProbe {
pub pre_name_prefix_len: usize, pub pre_name_prefix_len: usize,
#[serde(default)] #[serde(default)]
pub pre_name_prefix_hex_bytes: Vec<String>, pub pre_name_prefix_hex_bytes: Vec<String>,
#[serde(default)]
pub pre_name_prefix_dword_candidates: Vec<SmpSaveDwordCandidate>,
pub policy_chunk_len: usize, pub policy_chunk_len: usize,
pub profile_chunk_len: usize, pub profile_chunk_len: usize,
pub policy_leading_f32_0: f32, pub policy_leading_f32_0: f32,
@ -13195,6 +13197,10 @@ fn parse_save_region_record_triplet_probe(
} }
let pre_name_prefix = let pre_name_prefix =
records_payload.get(record_payload_relative_offset..name_tag_relative_offset)?; 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 = let name_payload =
records_payload.get(name_tag_relative_offset + 4..policy_tag_relative_offset)?; records_payload.get(name_tag_relative_offset + 4..policy_tag_relative_offset)?;
let name = parse_save_len_prefixed_ascii_name(name_payload)?; let name = parse_save_len_prefixed_ascii_name(name_payload)?;
@ -13231,6 +13237,7 @@ fn parse_save_region_record_triplet_probe(
.iter() .iter()
.map(|byte| format!("0x{byte:02x}")) .map(|byte| format!("0x{byte:02x}"))
.collect(), .collect(),
pre_name_prefix_dword_candidates,
policy_chunk_len, policy_chunk_len,
profile_chunk_len, profile_chunk_len,
policy_leading_f32_0, 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<SmpSaveDwordCandidate> {
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( fn parse_save_region_queued_notice_record_probe(
bytes: &[u8], bytes: &[u8],
file_extension_hint: Option<&str>, file_extension_hint: Option<&str>,
@ -23333,6 +23363,7 @@ mod tests {
profile_tag_relative_offset: 0x2e, profile_tag_relative_offset: 0x2e,
pre_name_prefix_len: 0, pre_name_prefix_len: 0,
pre_name_prefix_hex_bytes: Vec::new(), pre_name_prefix_hex_bytes: Vec::new(),
pre_name_prefix_dword_candidates: Vec::new(),
policy_chunk_len: 0x1a, policy_chunk_len: 0x1a,
profile_chunk_len: 0x40, profile_chunk_len: 0x40,
policy_leading_f32_0: 368.0, policy_leading_f32_0: 368.0,
@ -23374,6 +23405,7 @@ mod tests {
profile_tag_relative_offset: 0x9c, profile_tag_relative_offset: 0x9c,
pre_name_prefix_len: 0, pre_name_prefix_len: 0,
pre_name_prefix_hex_bytes: Vec::new(), pre_name_prefix_hex_bytes: Vec::new(),
pre_name_prefix_dword_candidates: Vec::new(),
policy_chunk_len: 0x1a, policy_chunk_len: 0x1a,
profile_chunk_len: 0x20, profile_chunk_len: 0x20,
policy_leading_f32_0: 552.0, 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_0, 368.0);
assert_eq!(triplet_probe.entries[0].policy_leading_f32_1, 0.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_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!( assert_eq!(
triplet_probe.entries[0].policy_reserved_dwords, triplet_probe.entries[0].policy_reserved_dwords,
vec![0, 0, 0] 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_0, 552.0);
assert_eq!(triplet_probe.entries[1].policy_leading_f32_1, 1.5); 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_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] #[test]
@ -25906,6 +26048,7 @@ mod tests {
profile_tag_relative_offset: 0, profile_tag_relative_offset: 0,
pre_name_prefix_len: 0, pre_name_prefix_len: 0,
pre_name_prefix_hex_bytes: Vec::new(), pre_name_prefix_hex_bytes: Vec::new(),
pre_name_prefix_dword_candidates: Vec::new(),
policy_chunk_len: 0, policy_chunk_len: 0,
profile_chunk_len: 0, profile_chunk_len: 0,
policy_leading_f32_0: 368.0, policy_leading_f32_0: 368.0,

View file

@ -544,6 +544,11 @@ Working rule:
`[region+0x2a4]` or `[region+0x310/+0x338/+0x360]`, and they still do not touch `[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 `[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. 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 - 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 `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 `[this+0x20]`, `[this+0x8d]`, `[this+0x5c..+0x61]`, `[this+0x1ee]`, `[this+0x1fa]`, and