Decode save-side placed-structure footers
This commit is contained in:
parent
5928815465
commit
f13d710556
2 changed files with 109 additions and 6 deletions
|
|
@ -1719,6 +1719,15 @@ pub struct SmpSavePlacedStructureRecordTripletEntryProbe {
|
||||||
pub policy_reserved_dword: u32,
|
pub policy_reserved_dword: u32,
|
||||||
pub policy_trailing_word: u16,
|
pub policy_trailing_word: u16,
|
||||||
pub policy_trailing_word_hex: String,
|
pub policy_trailing_word_hex: String,
|
||||||
|
pub profile_open_marker: u32,
|
||||||
|
pub profile_open_marker_hex: String,
|
||||||
|
pub profile_repeated_primary_name: String,
|
||||||
|
pub profile_repeated_secondary_name: String,
|
||||||
|
pub profile_payload_dword: u32,
|
||||||
|
pub profile_payload_dword_hex: String,
|
||||||
|
pub profile_sentinel_i32: i32,
|
||||||
|
pub profile_close_marker: u32,
|
||||||
|
pub profile_close_marker_hex: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
|
|
@ -3278,7 +3287,7 @@ pub fn load_save_slice_from_report(
|
||||||
}
|
}
|
||||||
if let Some(probe) = &report.save_placed_structure_record_triplet_probe {
|
if let Some(probe) = &report.save_placed_structure_record_triplet_probe {
|
||||||
notes.push(format!(
|
notes.push(format!(
|
||||||
"Raw save tagged placed-structure records also expose {} repeated 0x55f1/0x55f2/0x55f3 triplets; first stems={:?}/{:?}, first policy lanes=({:.3}, {:.3}, {:.3}, {:.3}, {:.3}).",
|
"Raw save tagged placed-structure records also expose {} repeated 0x55f1/0x55f2/0x55f3 triplets; first stems={:?}/{:?}, first policy lanes=({:.3}, {:.3}, {:.3}, {:.3}, {:.3}), first footer payload={}.",
|
||||||
probe.record_count,
|
probe.record_count,
|
||||||
probe.entries.first().map(|entry| entry.primary_name.as_str()),
|
probe.entries.first().map(|entry| entry.primary_name.as_str()),
|
||||||
probe.entries.first().map(|entry| entry.secondary_name.as_str()),
|
probe.entries.first().map(|entry| entry.secondary_name.as_str()),
|
||||||
|
|
@ -3286,7 +3295,8 @@ pub fn load_save_slice_from_report(
|
||||||
probe.entries.first().map(|entry| entry.policy_f32_lane_1).unwrap_or_default(),
|
probe.entries.first().map(|entry| entry.policy_f32_lane_1).unwrap_or_default(),
|
||||||
probe.entries.first().map(|entry| entry.policy_f32_lane_2).unwrap_or_default(),
|
probe.entries.first().map(|entry| entry.policy_f32_lane_2).unwrap_or_default(),
|
||||||
probe.entries.first().map(|entry| entry.policy_f32_lane_3).unwrap_or_default(),
|
probe.entries.first().map(|entry| entry.policy_f32_lane_3).unwrap_or_default(),
|
||||||
probe.entries.first().map(|entry| entry.policy_f32_lane_4).unwrap_or_default()
|
probe.entries.first().map(|entry| entry.policy_f32_lane_4).unwrap_or_default(),
|
||||||
|
probe.entries.first().map(|entry| entry.profile_payload_dword_hex.as_str()).unwrap_or("0x00000000")
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
if let Some(roster) = &report.save_company_roster_probe {
|
if let Some(roster) = &report.save_company_roster_probe {
|
||||||
|
|
@ -3699,10 +3709,11 @@ pub fn inspect_save_company_and_chairman_analysis_bytes(
|
||||||
}
|
}
|
||||||
if let Some(triplets) = placed_structure_record_triplets.as_ref() {
|
if let Some(triplets) = placed_structure_record_triplets.as_ref() {
|
||||||
notes.push(format!(
|
notes.push(format!(
|
||||||
"Placed-structure analysis now also exports {} tagged 0x55f1/0x55f2/0x55f3 record triplets; first stems={:?}/{:?}.",
|
"Placed-structure analysis now also exports {} tagged 0x55f1/0x55f2/0x55f3 record triplets; first stems={:?}/{:?}, first footer payload={}.",
|
||||||
triplets.record_count,
|
triplets.record_count,
|
||||||
triplets.entries.first().map(|entry| entry.primary_name.as_str()),
|
triplets.entries.first().map(|entry| entry.primary_name.as_str()),
|
||||||
triplets.entries.first().map(|entry| entry.secondary_name.as_str())
|
triplets.entries.first().map(|entry| entry.secondary_name.as_str()),
|
||||||
|
triplets.entries.first().map(|entry| entry.profile_payload_dword_hex.as_str()).unwrap_or("0x00000000")
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
if !company_entries.is_empty() {
|
if !company_entries.is_empty() {
|
||||||
|
|
@ -10181,6 +10192,44 @@ fn parse_save_placed_structure_record_triplet_probe(
|
||||||
let policy_trailing_word = read_u16_at(policy_payload, 24)?;
|
let policy_trailing_word = read_u16_at(policy_payload, 24)?;
|
||||||
let profile_chunk_len =
|
let profile_chunk_len =
|
||||||
next_record_relative_offset.checked_sub(profile_tag_relative_offset + 4)?;
|
next_record_relative_offset.checked_sub(profile_tag_relative_offset + 4)?;
|
||||||
|
let profile_payload =
|
||||||
|
records_payload.get(profile_tag_relative_offset + 4..next_record_relative_offset)?;
|
||||||
|
let profile_open_marker = read_u32_at(profile_payload, 0)?;
|
||||||
|
if profile_open_marker != 0x00005dc1 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let (profile_repeated_primary_name, profile_repeated_secondary_name) =
|
||||||
|
parse_save_len_prefixed_ascii_name_pair(profile_payload.get(4..)?)?;
|
||||||
|
let mut trailer_offset = 4usize;
|
||||||
|
let repeated_primary_len = *profile_payload.get(trailer_offset)? as usize;
|
||||||
|
trailer_offset += 1 + repeated_primary_len;
|
||||||
|
while matches!(profile_payload.get(trailer_offset), Some(0)) {
|
||||||
|
trailer_offset += 1;
|
||||||
|
}
|
||||||
|
let repeated_secondary_len = *profile_payload.get(trailer_offset)? as usize;
|
||||||
|
trailer_offset += 1 + repeated_secondary_len;
|
||||||
|
let mut matched_footer = None;
|
||||||
|
for candidate_offset in [trailer_offset, trailer_offset + 1] {
|
||||||
|
if let (
|
||||||
|
Some(profile_payload_dword),
|
||||||
|
Some(profile_sentinel_i32),
|
||||||
|
Some(profile_close_marker),
|
||||||
|
) = (
|
||||||
|
read_u32_at(profile_payload, candidate_offset),
|
||||||
|
read_i32_at(profile_payload, candidate_offset + 4),
|
||||||
|
read_u32_at(profile_payload, candidate_offset + 8),
|
||||||
|
) {
|
||||||
|
if profile_close_marker == 0x00005dc2 {
|
||||||
|
matched_footer = Some((
|
||||||
|
profile_payload_dword,
|
||||||
|
profile_sentinel_i32,
|
||||||
|
profile_close_marker,
|
||||||
|
));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let (profile_payload_dword, profile_sentinel_i32, profile_close_marker) = matched_footer?;
|
||||||
entries.push(SmpSavePlacedStructureRecordTripletEntryProbe {
|
entries.push(SmpSavePlacedStructureRecordTripletEntryProbe {
|
||||||
record_index: index,
|
record_index: index,
|
||||||
primary_name,
|
primary_name,
|
||||||
|
|
@ -10198,6 +10247,15 @@ fn parse_save_placed_structure_record_triplet_probe(
|
||||||
policy_reserved_dword,
|
policy_reserved_dword,
|
||||||
policy_trailing_word,
|
policy_trailing_word,
|
||||||
policy_trailing_word_hex: format!("0x{policy_trailing_word:04x}"),
|
policy_trailing_word_hex: format!("0x{policy_trailing_word:04x}"),
|
||||||
|
profile_open_marker,
|
||||||
|
profile_open_marker_hex: format!("0x{profile_open_marker:08x}"),
|
||||||
|
profile_repeated_primary_name,
|
||||||
|
profile_repeated_secondary_name,
|
||||||
|
profile_payload_dword,
|
||||||
|
profile_payload_dword_hex: format!("0x{profile_payload_dword:08x}"),
|
||||||
|
profile_sentinel_i32,
|
||||||
|
profile_close_marker,
|
||||||
|
profile_close_marker_hex: format!("0x{profile_close_marker:08x}"),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Some(SmpSavePlacedStructureRecordTripletProbe {
|
Some(SmpSavePlacedStructureRecordTripletProbe {
|
||||||
|
|
@ -17855,7 +17913,25 @@ mod tests {
|
||||||
cursor += 0x1e;
|
cursor += 0x1e;
|
||||||
bytes[cursor..cursor + 2]
|
bytes[cursor..cursor + 2]
|
||||||
.copy_from_slice(&SAVE_REGION_RECORD_PROFILE_TAG.to_le_bytes());
|
.copy_from_slice(&SAVE_REGION_RECORD_PROFILE_TAG.to_le_bytes());
|
||||||
cursor += 0x10;
|
bytes[cursor + 4..cursor + 8].copy_from_slice(&0x5dc1u32.to_le_bytes());
|
||||||
|
let mut payload_cursor = cursor + 8;
|
||||||
|
bytes[payload_cursor] = primary.len() as u8;
|
||||||
|
bytes[payload_cursor + 1..payload_cursor + 1 + primary.len()]
|
||||||
|
.copy_from_slice(primary.as_bytes());
|
||||||
|
payload_cursor += 1 + primary.len();
|
||||||
|
bytes[payload_cursor] = 0;
|
||||||
|
payload_cursor += 1;
|
||||||
|
bytes[payload_cursor] = secondary.len() as u8;
|
||||||
|
bytes[payload_cursor + 1..payload_cursor + 1 + secondary.len()]
|
||||||
|
.copy_from_slice(secondary.as_bytes());
|
||||||
|
payload_cursor += 1 + secondary.len();
|
||||||
|
bytes[payload_cursor] = 0;
|
||||||
|
payload_cursor += 1;
|
||||||
|
bytes[payload_cursor..payload_cursor + 4].copy_from_slice(&0x0e373500u32.to_le_bytes());
|
||||||
|
bytes[payload_cursor + 4..payload_cursor + 8].copy_from_slice(&(-1i32).to_le_bytes());
|
||||||
|
bytes[payload_cursor + 8..payload_cursor + 12]
|
||||||
|
.copy_from_slice(&0x5dc2u32.to_le_bytes());
|
||||||
|
cursor += 0x18 + primary.len() + secondary.len();
|
||||||
}
|
}
|
||||||
|
|
||||||
let header_probe = SmpSaveTaggedCollectionHeaderProbe {
|
let header_probe = SmpSaveTaggedCollectionHeaderProbe {
|
||||||
|
|
@ -17887,6 +17963,27 @@ mod tests {
|
||||||
assert_eq!(triplet_probe.entries[0].policy_chunk_len, 0x1a);
|
assert_eq!(triplet_probe.entries[0].policy_chunk_len, 0x1a);
|
||||||
assert_eq!(triplet_probe.entries[0].policy_f32_lane_4, 5.9760494);
|
assert_eq!(triplet_probe.entries[0].policy_f32_lane_4, 5.9760494);
|
||||||
assert_eq!(triplet_probe.entries[0].policy_trailing_word, 0x0101);
|
assert_eq!(triplet_probe.entries[0].policy_trailing_word, 0x0101);
|
||||||
|
assert_eq!(
|
||||||
|
triplet_probe.entries[0].profile_open_marker_hex,
|
||||||
|
"0x00005dc1"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
triplet_probe.entries[0].profile_repeated_primary_name,
|
||||||
|
"StationA"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
triplet_probe.entries[0].profile_repeated_secondary_name,
|
||||||
|
"StationSetA"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
triplet_probe.entries[0].profile_payload_dword_hex,
|
||||||
|
"0x0e373500"
|
||||||
|
);
|
||||||
|
assert_eq!(triplet_probe.entries[0].profile_sentinel_i32, -1);
|
||||||
|
assert_eq!(
|
||||||
|
triplet_probe.entries[0].profile_close_marker_hex,
|
||||||
|
"0x00005dc2"
|
||||||
|
);
|
||||||
assert_eq!(triplet_probe.entries[1].primary_name, "StationB");
|
assert_eq!(triplet_probe.entries[1].primary_name, "StationB");
|
||||||
assert_eq!(triplet_probe.entries[1].secondary_name, "StationSetB");
|
assert_eq!(triplet_probe.entries[1].secondary_name, "StationSetB");
|
||||||
assert_eq!(triplet_probe.entries[1].policy_f32_lane_1, 1200.0);
|
assert_eq!(triplet_probe.entries[1].policy_f32_lane_1, 1200.0);
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,9 @@ Working rule:
|
||||||
or class discriminator that can drive shellless city-connection service.
|
or class discriminator that can drive shellless city-connection service.
|
||||||
- Reconstruct the save-side placed-structure collection body on top of the newly grounded
|
- Reconstruct the save-side placed-structure collection body on top of the newly grounded
|
||||||
`0x36b1/0x36b2/0x36b3` header seam so the blocked city-connection / linked-transit branch can
|
`0x36b1/0x36b2/0x36b3` header seam so the blocked city-connection / linked-transit branch can
|
||||||
stop depending on atlas-only placed-structure and local-runtime refresh notes.
|
stop depending on atlas-only placed-structure and local-runtime refresh notes, especially the
|
||||||
|
semantics of the now-grounded compact `0x55f3` footer dword/status lane and any deeper side
|
||||||
|
buffers beyond the repeated `0x55f1/0x55f2/0x55f3` triplet envelope.
|
||||||
- Extend shellless clock advancement so more periodic-company service branches consume owned
|
- Extend shellless clock advancement so more periodic-company service branches consume owned
|
||||||
runtime time state directly instead of only the explicit periodic service command.
|
runtime time state directly instead of only the explicit periodic service command.
|
||||||
- Keep widening selected-year world-owner state only when a full owning reader/rebuild family is
|
- Keep widening selected-year world-owner state only when a full owning reader/rebuild family is
|
||||||
|
|
@ -69,6 +71,10 @@ Working rule:
|
||||||
live-id/count headers, fixed `0x22`-byte rows, profile names, and trailing weight scalars, so
|
live-id/count headers, fixed `0x22`-byte rows, profile names, and trailing weight scalars, so
|
||||||
the remaining region work is on the unresolved payload fields above that collection rather than
|
the remaining region work is on the unresolved payload fields above that collection rather than
|
||||||
on the profile subcollection itself.
|
on the profile subcollection itself.
|
||||||
|
- The placed-structure tagged save stream now also exposes repeated `0x55f1/0x55f2/0x55f3`
|
||||||
|
triplets with dual name stems, a fixed five-`f32` policy row, and a compact `0x5dc1...0x5dc2`
|
||||||
|
footer carrying one raw `u32` payload lane plus one live `i32` status lane, so the remaining
|
||||||
|
placed-structure work is semantic closure of those owned fields rather than envelope discovery.
|
||||||
- Stepped calendar progression now also refreshes save-world owner time fields, including packed
|
- Stepped calendar progression now also refreshes save-world owner time fields, including packed
|
||||||
year, packed tuple words, absolute counter, and the derived selected-year gap scalar.
|
year, packed tuple words, absolute counter, and the derived selected-year gap scalar.
|
||||||
- Automatic year-rollover calendar stepping now invokes periodic-boundary service.
|
- Automatic year-rollover calendar stepping now invokes periodic-boundary service.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue