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,
#[serde(default)]
pub pre_name_prefix_hex_bytes: Vec<String>,
#[serde(default)]
pub pre_name_prefix_dword_candidates: Vec<SmpSaveDwordCandidate>,
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<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(
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,

View file

@ -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