Summarize placed-structure side-buffer patterns
This commit is contained in:
parent
e43731c0ef
commit
c173623692
3 changed files with 345 additions and 39 deletions
|
|
@ -118,8 +118,9 @@ pub use smp::{
|
||||||
SmpRuntimePostSpanHeaderCandidate, SmpRuntimePostSpanProbe, SmpRuntimeTrailerBlock,
|
SmpRuntimePostSpanHeaderCandidate, SmpRuntimePostSpanProbe, SmpRuntimeTrailerBlock,
|
||||||
SmpSaveAnchorRunBlock, SmpSaveBootstrapBlock, SmpSaveChairmanRecordAnalysisEntry,
|
SmpSaveAnchorRunBlock, SmpSaveBootstrapBlock, SmpSaveChairmanRecordAnalysisEntry,
|
||||||
SmpSaveCompanyChairmanAnalysisReport, SmpSaveCompanyRecordAnalysisEntry, SmpSaveDwordCandidate,
|
SmpSaveCompanyChairmanAnalysisReport, SmpSaveCompanyRecordAnalysisEntry, SmpSaveDwordCandidate,
|
||||||
SmpSavePlacedStructureDynamicSideBufferProbe,
|
SmpSaveLoadCandidateTableSummary, SmpSaveLoadSummary,
|
||||||
SmpSaveLoadCandidateTableSummary, SmpSaveLoadSummary, SmpSaveScalarCandidate,
|
SmpSavePlacedStructureDynamicSideBufferPrefixPatternSummary,
|
||||||
|
SmpSavePlacedStructureDynamicSideBufferProbe, SmpSaveScalarCandidate,
|
||||||
SmpSaveTaggedCollectionHeaderProbe, SmpSaveWorldEconomicTuningProbe,
|
SmpSaveTaggedCollectionHeaderProbe, SmpSaveWorldEconomicTuningProbe,
|
||||||
SmpSaveWorldFinanceNeighborhoodProbe, SmpSaveWorldIssue37Probe,
|
SmpSaveWorldFinanceNeighborhoodProbe, SmpSaveWorldIssue37Probe,
|
||||||
SmpSaveWorldSelectionRoleAnalysis, SmpSaveWorldSelectionRoleAnalysisEntry,
|
SmpSaveWorldSelectionRoleAnalysis, SmpSaveWorldSelectionRoleAnalysisEntry,
|
||||||
|
|
|
||||||
|
|
@ -1796,12 +1796,17 @@ pub struct SmpSavePlacedStructureDynamicSideBufferProbe {
|
||||||
pub prefix_separator_byte_hex: String,
|
pub prefix_separator_byte_hex: String,
|
||||||
pub first_embedded_name_tag_relative_offset: usize,
|
pub first_embedded_name_tag_relative_offset: usize,
|
||||||
pub embedded_name_tag_count: usize,
|
pub embedded_name_tag_count: usize,
|
||||||
|
pub unique_compact_prefix_pattern_count: usize,
|
||||||
|
pub prefix_leading_dword_matching_embedded_profile_tag_count: usize,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub first_embedded_primary_name: Option<String>,
|
pub first_embedded_primary_name: Option<String>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub first_embedded_secondary_name: Option<String>,
|
pub first_embedded_secondary_name: Option<String>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub embedded_name_row_samples: Vec<SmpSavePlacedStructureDynamicSideBufferSampleEntry>,
|
pub embedded_name_row_samples: Vec<SmpSavePlacedStructureDynamicSideBufferSampleEntry>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub compact_prefix_pattern_summaries:
|
||||||
|
Vec<SmpSavePlacedStructureDynamicSideBufferPrefixPatternSummary>,
|
||||||
pub evidence: Vec<String>,
|
pub evidence: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1821,6 +1826,26 @@ pub struct SmpSavePlacedStructureDynamicSideBufferSampleEntry {
|
||||||
pub secondary_name: Option<String>,
|
pub secondary_name: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
pub struct SmpSavePlacedStructureDynamicSideBufferPrefixPatternSummary {
|
||||||
|
pub prefix_leading_dword: u32,
|
||||||
|
pub prefix_leading_dword_hex: String,
|
||||||
|
pub prefix_trailing_word: u16,
|
||||||
|
pub prefix_trailing_word_hex: String,
|
||||||
|
pub prefix_separator_byte: u8,
|
||||||
|
pub prefix_separator_byte_hex: String,
|
||||||
|
pub count: usize,
|
||||||
|
pub first_name_tag_relative_offset: usize,
|
||||||
|
pub prefix_leading_dword_matches_embedded_profile_tag: bool,
|
||||||
|
pub section_like_primary_name_count: usize,
|
||||||
|
pub cap_like_primary_name_count: usize,
|
||||||
|
pub other_primary_name_count: usize,
|
||||||
|
#[serde(default)]
|
||||||
|
pub first_primary_name: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub first_secondary_name: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct SmpRt3105SaveNameTableProbe {
|
pub struct SmpRt3105SaveNameTableProbe {
|
||||||
pub profile_family: String,
|
pub profile_family: String,
|
||||||
|
|
@ -2767,8 +2792,7 @@ pub struct SmpSaveCompanyChairmanAnalysisReport {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub placed_structure_record_triplets: Option<SmpSavePlacedStructureRecordTripletProbe>,
|
pub placed_structure_record_triplets: Option<SmpSavePlacedStructureRecordTripletProbe>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub placed_structure_dynamic_side_buffer:
|
pub placed_structure_dynamic_side_buffer: Option<SmpSavePlacedStructureDynamicSideBufferProbe>,
|
||||||
Option<SmpSavePlacedStructureDynamicSideBufferProbe>,
|
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub unclassified_tagged_collection_headers: Vec<SmpSaveUnclassifiedTaggedCollectionHeaderProbe>,
|
pub unclassified_tagged_collection_headers: Vec<SmpSaveUnclassifiedTaggedCollectionHeaderProbe>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
|
@ -3138,10 +3162,12 @@ pub fn inspect_unclassified_save_collection_headers_file(
|
||||||
file_extension_hint.as_deref(),
|
file_extension_hint.as_deref(),
|
||||||
container_profile.as_ref(),
|
container_profile.as_ref(),
|
||||||
);
|
);
|
||||||
Ok(filter_unclassified_tagged_collection_header_probes_outside_known_spans(
|
Ok(
|
||||||
probes,
|
filter_unclassified_tagged_collection_header_probes_outside_known_spans(
|
||||||
&known_header_probes,
|
probes,
|
||||||
))
|
&known_header_probes,
|
||||||
|
),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn inspect_save_placed_structure_dynamic_side_buffer_file(
|
pub fn inspect_save_placed_structure_dynamic_side_buffer_file(
|
||||||
|
|
@ -3333,8 +3359,9 @@ pub fn load_save_slice_from_report(
|
||||||
enabled_visible_labels: probe.enabled_visible_labels.clone(),
|
enabled_visible_labels: probe.enabled_visible_labels.clone(),
|
||||||
entries: probe.entries.clone(),
|
entries: probe.entries.clone(),
|
||||||
});
|
});
|
||||||
let placed_structure_dynamic_side_buffer_probe =
|
let placed_structure_dynamic_side_buffer_probe = report
|
||||||
report.save_placed_structure_dynamic_side_buffer_probe.clone();
|
.save_placed_structure_dynamic_side_buffer_probe
|
||||||
|
.clone();
|
||||||
let mut notes = summary.notes.clone();
|
let mut notes = summary.notes.clone();
|
||||||
if let Some(probe) = &report.save_world_selection_context_probe {
|
if let Some(probe) = &report.save_world_selection_context_probe {
|
||||||
notes.push(format!(
|
notes.push(format!(
|
||||||
|
|
@ -3491,15 +3518,28 @@ pub fn load_save_slice_from_report(
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
if let Some(probe) = &placed_structure_dynamic_side_buffer_probe {
|
if let Some(probe) = &placed_structure_dynamic_side_buffer_probe {
|
||||||
|
let dominant_pattern = probe.compact_prefix_pattern_summaries.first();
|
||||||
notes.push(format!(
|
notes.push(format!(
|
||||||
"Raw save also exposes the separate placed-structure dynamic-side-buffer candidate 0x38a5/0x38a6/0x38a7: live_record_count={}, first compact prefix=({},{},{}), first embedded names={:?}/{:?}, embedded 0x55f1 row count={}.",
|
"Raw save also exposes the separate placed-structure dynamic-side-buffer candidate 0x38a5/0x38a6/0x38a7: live_record_count={}, first compact prefix=({},{},{}), first embedded names={:?}/{:?}, embedded 0x55f1 row count={}, unique compact prefix patterns={}, 0x55f3-leading rows={}, dominant compact pattern={}/{}/{} x{}.",
|
||||||
probe.live_record_count,
|
probe.live_record_count,
|
||||||
probe.prefix_leading_dword_hex,
|
probe.prefix_leading_dword_hex,
|
||||||
probe.prefix_trailing_word_hex,
|
probe.prefix_trailing_word_hex,
|
||||||
probe.prefix_separator_byte_hex,
|
probe.prefix_separator_byte_hex,
|
||||||
probe.first_embedded_primary_name.as_deref(),
|
probe.first_embedded_primary_name.as_deref(),
|
||||||
probe.first_embedded_secondary_name.as_deref(),
|
probe.first_embedded_secondary_name.as_deref(),
|
||||||
probe.embedded_name_tag_count
|
probe.embedded_name_tag_count,
|
||||||
|
probe.unique_compact_prefix_pattern_count,
|
||||||
|
probe.prefix_leading_dword_matching_embedded_profile_tag_count,
|
||||||
|
dominant_pattern
|
||||||
|
.map(|pattern| pattern.prefix_leading_dword_hex.as_str())
|
||||||
|
.unwrap_or("0x00000000"),
|
||||||
|
dominant_pattern
|
||||||
|
.map(|pattern| pattern.prefix_trailing_word_hex.as_str())
|
||||||
|
.unwrap_or("0x0000"),
|
||||||
|
dominant_pattern
|
||||||
|
.map(|pattern| pattern.prefix_separator_byte_hex.as_str())
|
||||||
|
.unwrap_or("0x00"),
|
||||||
|
dominant_pattern.map(|pattern| pattern.count).unwrap_or_default()
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
if let Some(roster) = &report.save_company_roster_probe {
|
if let Some(roster) = &report.save_company_roster_probe {
|
||||||
|
|
@ -3936,6 +3976,25 @@ pub fn inspect_save_company_and_chairman_analysis_bytes(
|
||||||
triplets.entries.first().map(|entry| entry.profile_status_kind.as_str())
|
triplets.entries.first().map(|entry| entry.profile_status_kind.as_str())
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
if let Some(side_buffer) = placed_structure_dynamic_side_buffer.as_ref() {
|
||||||
|
let dominant_pattern = side_buffer.compact_prefix_pattern_summaries.first();
|
||||||
|
notes.push(format!(
|
||||||
|
"Placed-structure analysis now also exports the separate 0x38a5 dynamic side-buffer owner seam with {} embedded name rows, {} unique compact prefix patterns, {} rows whose leading dword matches 0x55f3, and dominant compact pattern={}/{}/{} x{}.",
|
||||||
|
side_buffer.embedded_name_tag_count,
|
||||||
|
side_buffer.unique_compact_prefix_pattern_count,
|
||||||
|
side_buffer.prefix_leading_dword_matching_embedded_profile_tag_count,
|
||||||
|
dominant_pattern
|
||||||
|
.map(|pattern| pattern.prefix_leading_dword_hex.as_str())
|
||||||
|
.unwrap_or("0x00000000"),
|
||||||
|
dominant_pattern
|
||||||
|
.map(|pattern| pattern.prefix_trailing_word_hex.as_str())
|
||||||
|
.unwrap_or("0x0000"),
|
||||||
|
dominant_pattern
|
||||||
|
.map(|pattern| pattern.prefix_separator_byte_hex.as_str())
|
||||||
|
.unwrap_or("0x00"),
|
||||||
|
dominant_pattern.map(|pattern| pattern.count).unwrap_or_default()
|
||||||
|
));
|
||||||
|
}
|
||||||
if let Some(candidate) = unclassified_tagged_collection_headers.first() {
|
if let Some(candidate) = unclassified_tagged_collection_headers.first() {
|
||||||
notes.push(format!(
|
notes.push(format!(
|
||||||
"Generic save-side tagged collection scan also found {} unclassified candidate families; largest current candidate uses tags {}/{}/{} with live_record_count={} stride=0x{:x} records_span_len=0x{:x}.",
|
"Generic save-side tagged collection scan also found {} unclassified candidate families; largest current candidate uses tags {}/{}/{} with live_record_count={} stride=0x{:x} records_span_len=0x{:x}.",
|
||||||
|
|
@ -10609,6 +10668,27 @@ fn parse_save_placed_structure_dynamic_side_buffer_probe(
|
||||||
file_extension_hint: Option<&str>,
|
file_extension_hint: Option<&str>,
|
||||||
container_profile: Option<&SmpContainerProfile>,
|
container_profile: Option<&SmpContainerProfile>,
|
||||||
) -> Option<SmpSavePlacedStructureDynamicSideBufferProbe> {
|
) -> Option<SmpSavePlacedStructureDynamicSideBufferProbe> {
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct EmbeddedNameRow {
|
||||||
|
name_tag_relative_offset: usize,
|
||||||
|
prefix_leading_dword: u32,
|
||||||
|
prefix_trailing_word: u16,
|
||||||
|
prefix_separator_byte: u8,
|
||||||
|
primary_name: Option<String>,
|
||||||
|
secondary_name: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct PrefixPatternAccumulator {
|
||||||
|
count: usize,
|
||||||
|
first_name_tag_relative_offset: usize,
|
||||||
|
first_primary_name: Option<String>,
|
||||||
|
first_secondary_name: Option<String>,
|
||||||
|
section_like_primary_name_count: usize,
|
||||||
|
cap_like_primary_name_count: usize,
|
||||||
|
other_primary_name_count: usize,
|
||||||
|
}
|
||||||
|
|
||||||
if file_extension_hint != Some("gms") {
|
if file_extension_hint != Some("gms") {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
@ -10630,13 +10710,15 @@ fn parse_save_placed_structure_dynamic_side_buffer_probe(
|
||||||
let Some(records_tag_offset) = records_offsets
|
let Some(records_tag_offset) = records_offsets
|
||||||
.iter()
|
.iter()
|
||||||
.copied()
|
.copied()
|
||||||
.find(|offset| *offset > metadata_tag_offset) else {
|
.find(|offset| *offset > metadata_tag_offset)
|
||||||
|
else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
let Some(close_tag_offset) = close_offsets
|
let Some(close_tag_offset) = close_offsets
|
||||||
.iter()
|
.iter()
|
||||||
.copied()
|
.copied()
|
||||||
.find(|offset| *offset > records_tag_offset) else {
|
.find(|offset| *offset > records_tag_offset)
|
||||||
|
else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
let Some(payload) = bytes.get(metadata_tag_offset + 4..records_tag_offset) else {
|
let Some(payload) = bytes.get(metadata_tag_offset + 4..records_tag_offset) else {
|
||||||
|
|
@ -10647,7 +10729,8 @@ fn parse_save_placed_structure_dynamic_side_buffer_probe(
|
||||||
}
|
}
|
||||||
let Some(header_words) = (0..INDEXED_COLLECTION_SERIALIZED_HEADER_DWORD_COUNT)
|
let Some(header_words) = (0..INDEXED_COLLECTION_SERIALIZED_HEADER_DWORD_COUNT)
|
||||||
.map(|index| read_u32_at(payload, index * 4))
|
.map(|index| read_u32_at(payload, index * 4))
|
||||||
.collect::<Option<Vec<_>>>() else {
|
.collect::<Option<Vec<_>>>()
|
||||||
|
else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
let Some(header_words): Option<[u32; INDEXED_COLLECTION_SERIALIZED_HEADER_DWORD_COUNT]> =
|
let Some(header_words): Option<[u32; INDEXED_COLLECTION_SERIALIZED_HEADER_DWORD_COUNT]> =
|
||||||
|
|
@ -10683,10 +10766,12 @@ fn parse_save_placed_structure_dynamic_side_buffer_probe(
|
||||||
};
|
};
|
||||||
let embedded_name_tag_offsets =
|
let embedded_name_tag_offsets =
|
||||||
find_u16_le_offsets(records_payload, SAVE_REGION_RECORD_NAME_TAG);
|
find_u16_le_offsets(records_payload, SAVE_REGION_RECORD_NAME_TAG);
|
||||||
let Some(&first_embedded_name_tag_relative_offset) = embedded_name_tag_offsets.first() else {
|
let Some(&first_embedded_name_tag_relative_offset) = embedded_name_tag_offsets.first()
|
||||||
|
else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
let Some(prefix_payload) = records_payload.get(..first_embedded_name_tag_relative_offset) else {
|
let Some(prefix_payload) = records_payload.get(..first_embedded_name_tag_relative_offset)
|
||||||
|
else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
if prefix_payload.len() < 7 {
|
if prefix_payload.len() < 7 {
|
||||||
|
|
@ -10704,7 +10789,8 @@ fn parse_save_placed_structure_dynamic_side_buffer_probe(
|
||||||
let mut parsed_embedded_names = None;
|
let mut parsed_embedded_names = None;
|
||||||
for relative_name_offset in [4usize, 6usize] {
|
for relative_name_offset in [4usize, 6usize] {
|
||||||
let Some(name_payload) = records_payload
|
let Some(name_payload) = records_payload
|
||||||
.get(first_embedded_name_tag_relative_offset + relative_name_offset..) else {
|
.get(first_embedded_name_tag_relative_offset + relative_name_offset..)
|
||||||
|
else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
if let Some(names) = parse_save_len_prefixed_ascii_name_pair(name_payload) {
|
if let Some(names) = parse_save_len_prefixed_ascii_name_pair(name_payload) {
|
||||||
|
|
@ -10717,11 +10803,10 @@ fn parse_save_placed_structure_dynamic_side_buffer_probe(
|
||||||
else {
|
else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
let embedded_name_row_samples = embedded_name_tag_offsets
|
let embedded_name_rows = embedded_name_tag_offsets
|
||||||
.iter()
|
.iter()
|
||||||
.copied()
|
.copied()
|
||||||
.enumerate()
|
.filter_map(|name_tag_relative_offset| {
|
||||||
.filter_map(|(sample_index, name_tag_relative_offset)| {
|
|
||||||
let prefix_payload = records_payload.get(..name_tag_relative_offset)?;
|
let prefix_payload = records_payload.get(..name_tag_relative_offset)?;
|
||||||
if prefix_payload.len() < 7 {
|
if prefix_payload.len() < 7 {
|
||||||
return None;
|
return None;
|
||||||
|
|
@ -10731,8 +10816,9 @@ fn parse_save_placed_structure_dynamic_side_buffer_probe(
|
||||||
let prefix_separator_byte = *prefix_payload.last()?;
|
let prefix_separator_byte = *prefix_payload.last()?;
|
||||||
let mut parsed_names = None;
|
let mut parsed_names = None;
|
||||||
for relative_name_offset in [4usize, 6usize] {
|
for relative_name_offset in [4usize, 6usize] {
|
||||||
let Some(name_payload) = records_payload
|
let Some(name_payload) =
|
||||||
.get(name_tag_relative_offset + relative_name_offset..) else {
|
records_payload.get(name_tag_relative_offset + relative_name_offset..)
|
||||||
|
else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
if let Some(names) = parse_save_len_prefixed_ascii_name_pair(name_payload) {
|
if let Some(names) = parse_save_len_prefixed_ascii_name_pair(name_payload) {
|
||||||
|
|
@ -10741,21 +10827,108 @@ fn parse_save_placed_structure_dynamic_side_buffer_probe(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let (primary_name, secondary_name) = parsed_names.unwrap_or_default();
|
let (primary_name, secondary_name) = parsed_names.unwrap_or_default();
|
||||||
Some(SmpSavePlacedStructureDynamicSideBufferSampleEntry {
|
Some(EmbeddedNameRow {
|
||||||
sample_index,
|
|
||||||
name_tag_relative_offset,
|
name_tag_relative_offset,
|
||||||
prefix_leading_dword,
|
prefix_leading_dword,
|
||||||
prefix_leading_dword_hex: format!("0x{prefix_leading_dword:08x}"),
|
|
||||||
prefix_trailing_word,
|
prefix_trailing_word,
|
||||||
prefix_trailing_word_hex: format!("0x{prefix_trailing_word:04x}"),
|
|
||||||
prefix_separator_byte,
|
prefix_separator_byte,
|
||||||
prefix_separator_byte_hex: format!("0x{prefix_separator_byte:02x}"),
|
|
||||||
primary_name: (!primary_name.is_empty()).then_some(primary_name),
|
primary_name: (!primary_name.is_empty()).then_some(primary_name),
|
||||||
secondary_name: (!secondary_name.is_empty()).then_some(secondary_name),
|
secondary_name: (!secondary_name.is_empty()).then_some(secondary_name),
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.take(8)
|
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
let embedded_name_row_samples = embedded_name_rows
|
||||||
|
.iter()
|
||||||
|
.take(8)
|
||||||
|
.enumerate()
|
||||||
|
.map(
|
||||||
|
|(sample_index, row)| SmpSavePlacedStructureDynamicSideBufferSampleEntry {
|
||||||
|
sample_index,
|
||||||
|
name_tag_relative_offset: row.name_tag_relative_offset,
|
||||||
|
prefix_leading_dword: row.prefix_leading_dword,
|
||||||
|
prefix_leading_dword_hex: format!("0x{:08x}", row.prefix_leading_dword),
|
||||||
|
prefix_trailing_word: row.prefix_trailing_word,
|
||||||
|
prefix_trailing_word_hex: format!("0x{:04x}", row.prefix_trailing_word),
|
||||||
|
prefix_separator_byte: row.prefix_separator_byte,
|
||||||
|
prefix_separator_byte_hex: format!("0x{:02x}", row.prefix_separator_byte),
|
||||||
|
primary_name: row.primary_name.clone(),
|
||||||
|
secondary_name: row.secondary_name.clone(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let mut compact_prefix_pattern_map =
|
||||||
|
BTreeMap::<(u32, u16, u8), PrefixPatternAccumulator>::new();
|
||||||
|
for row in &embedded_name_rows {
|
||||||
|
let entry = compact_prefix_pattern_map
|
||||||
|
.entry((
|
||||||
|
row.prefix_leading_dword,
|
||||||
|
row.prefix_trailing_word,
|
||||||
|
row.prefix_separator_byte,
|
||||||
|
))
|
||||||
|
.or_insert_with(|| PrefixPatternAccumulator {
|
||||||
|
first_name_tag_relative_offset: row.name_tag_relative_offset,
|
||||||
|
first_primary_name: row.primary_name.clone(),
|
||||||
|
first_secondary_name: row.secondary_name.clone(),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
entry.count += 1;
|
||||||
|
match row.primary_name.as_deref() {
|
||||||
|
Some(name) if name.ends_with("_Section.3dp") => {
|
||||||
|
entry.section_like_primary_name_count += 1;
|
||||||
|
}
|
||||||
|
Some(name) if name.ends_with("_Cap.3dp") => {
|
||||||
|
entry.cap_like_primary_name_count += 1;
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
entry.other_primary_name_count += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let prefix_leading_dword_matching_embedded_profile_tag_count = embedded_name_rows
|
||||||
|
.iter()
|
||||||
|
.filter(|row| row.prefix_leading_dword == u32::from(SAVE_REGION_RECORD_PROFILE_TAG))
|
||||||
|
.count();
|
||||||
|
let mut compact_prefix_pattern_summaries = compact_prefix_pattern_map
|
||||||
|
.into_iter()
|
||||||
|
.map(
|
||||||
|
|(
|
||||||
|
(prefix_leading_dword, prefix_trailing_word, prefix_separator_byte),
|
||||||
|
accumulator,
|
||||||
|
)| {
|
||||||
|
SmpSavePlacedStructureDynamicSideBufferPrefixPatternSummary {
|
||||||
|
prefix_leading_dword,
|
||||||
|
prefix_leading_dword_hex: format!("0x{prefix_leading_dword:08x}"),
|
||||||
|
prefix_trailing_word,
|
||||||
|
prefix_trailing_word_hex: format!("0x{prefix_trailing_word:04x}"),
|
||||||
|
prefix_separator_byte,
|
||||||
|
prefix_separator_byte_hex: format!("0x{prefix_separator_byte:02x}"),
|
||||||
|
count: accumulator.count,
|
||||||
|
first_name_tag_relative_offset: accumulator.first_name_tag_relative_offset,
|
||||||
|
prefix_leading_dword_matches_embedded_profile_tag: prefix_leading_dword
|
||||||
|
== u32::from(SAVE_REGION_RECORD_PROFILE_TAG),
|
||||||
|
section_like_primary_name_count: accumulator
|
||||||
|
.section_like_primary_name_count,
|
||||||
|
cap_like_primary_name_count: accumulator.cap_like_primary_name_count,
|
||||||
|
other_primary_name_count: accumulator.other_primary_name_count,
|
||||||
|
first_primary_name: accumulator.first_primary_name,
|
||||||
|
first_secondary_name: accumulator.first_secondary_name,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
compact_prefix_pattern_summaries.sort_by(|left, right| {
|
||||||
|
right
|
||||||
|
.count
|
||||||
|
.cmp(&left.count)
|
||||||
|
.then_with(|| {
|
||||||
|
left.first_name_tag_relative_offset
|
||||||
|
.cmp(&right.first_name_tag_relative_offset)
|
||||||
|
})
|
||||||
|
.then_with(|| left.prefix_leading_dword.cmp(&right.prefix_leading_dword))
|
||||||
|
.then_with(|| left.prefix_trailing_word.cmp(&right.prefix_trailing_word))
|
||||||
|
.then_with(|| left.prefix_separator_byte.cmp(&right.prefix_separator_byte))
|
||||||
|
});
|
||||||
|
let dominant_compact_prefix_pattern = compact_prefix_pattern_summaries.first().cloned();
|
||||||
return Some(SmpSavePlacedStructureDynamicSideBufferProbe {
|
return Some(SmpSavePlacedStructureDynamicSideBufferProbe {
|
||||||
profile_family: profile.profile_family.clone(),
|
profile_family: profile.profile_family.clone(),
|
||||||
source_kind: "save-placed-structure-dynamic-side-buffer-records".to_string(),
|
source_kind: "save-placed-structure-dynamic-side-buffer-records".to_string(),
|
||||||
|
|
@ -10778,9 +10951,12 @@ fn parse_save_placed_structure_dynamic_side_buffer_probe(
|
||||||
prefix_separator_byte_hex: format!("0x{prefix_separator_byte:02x}"),
|
prefix_separator_byte_hex: format!("0x{prefix_separator_byte:02x}"),
|
||||||
first_embedded_name_tag_relative_offset,
|
first_embedded_name_tag_relative_offset,
|
||||||
embedded_name_tag_count: embedded_name_tag_offsets.len(),
|
embedded_name_tag_count: embedded_name_tag_offsets.len(),
|
||||||
|
unique_compact_prefix_pattern_count: compact_prefix_pattern_summaries.len(),
|
||||||
|
prefix_leading_dword_matching_embedded_profile_tag_count,
|
||||||
first_embedded_primary_name: Some(first_embedded_primary_name.clone()),
|
first_embedded_primary_name: Some(first_embedded_primary_name.clone()),
|
||||||
first_embedded_secondary_name: Some(first_embedded_secondary_name.clone()),
|
first_embedded_secondary_name: Some(first_embedded_secondary_name.clone()),
|
||||||
embedded_name_row_samples,
|
embedded_name_row_samples,
|
||||||
|
compact_prefix_pattern_summaries,
|
||||||
evidence: vec![
|
evidence: vec![
|
||||||
"exact little-endian u32 tag family 0x38a5/0x38a6/0x38a7 appears as a separate save-side tagged collection on grounded saves".to_string(),
|
"exact little-endian u32 tag family 0x38a5/0x38a6/0x38a7 appears as a separate save-side tagged collection on grounded saves".to_string(),
|
||||||
"records payload begins with a compact 6-byte prefix plus one separator byte before the first embedded 0x55f1 name row".to_string(),
|
"records payload begins with a compact 6-byte prefix plus one separator byte before the first embedded 0x55f1 name row".to_string(),
|
||||||
|
|
@ -10791,6 +10967,29 @@ fn parse_save_placed_structure_dynamic_side_buffer_probe(
|
||||||
Some(first_embedded_secondary_name),
|
Some(first_embedded_secondary_name),
|
||||||
embedded_name_tag_offsets.len()
|
embedded_name_tag_offsets.len()
|
||||||
),
|
),
|
||||||
|
format!(
|
||||||
|
"{} of {} embedded name rows use compact leading dword 0x{:08x}, matching the placed-structure embedded profile tag",
|
||||||
|
prefix_leading_dword_matching_embedded_profile_tag_count,
|
||||||
|
embedded_name_rows.len(),
|
||||||
|
u32::from(SAVE_REGION_RECORD_PROFILE_TAG)
|
||||||
|
),
|
||||||
|
dominant_compact_prefix_pattern
|
||||||
|
.map(|pattern| {
|
||||||
|
format!(
|
||||||
|
"dominant compact prefix pattern {} / {} / {} occurs {} times; section-like rows={}, cap-like rows={}, first names={:?}/{:?}",
|
||||||
|
pattern.prefix_leading_dword_hex,
|
||||||
|
pattern.prefix_trailing_word_hex,
|
||||||
|
pattern.prefix_separator_byte_hex,
|
||||||
|
pattern.count,
|
||||||
|
pattern.section_like_primary_name_count,
|
||||||
|
pattern.cap_like_primary_name_count,
|
||||||
|
pattern.first_primary_name,
|
||||||
|
pattern.first_secondary_name
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.unwrap_or_else(|| {
|
||||||
|
"no dominant compact prefix pattern summary was available".to_string()
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -11072,13 +11271,10 @@ fn filter_unclassified_tagged_collection_header_probes_outside_known_spans(
|
||||||
probes
|
probes
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|probe| {
|
.filter(|probe| {
|
||||||
!known_header_probes
|
!known_header_probes.iter().flatten().any(|known| {
|
||||||
.iter()
|
probe.metadata_tag_offset >= known.metadata_tag_offset
|
||||||
.flatten()
|
&& probe.close_tag_offset <= known.close_tag_offset
|
||||||
.any(|known| {
|
})
|
||||||
probe.metadata_tag_offset >= known.metadata_tag_offset
|
|
||||||
&& probe.close_tag_offset <= known.close_tag_offset
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
@ -18683,8 +18879,7 @@ mod tests {
|
||||||
.copy_from_slice(&0x000038a5u32.to_le_bytes());
|
.copy_from_slice(&0x000038a5u32.to_le_bytes());
|
||||||
bytes[records_tag_offset..records_tag_offset + 4]
|
bytes[records_tag_offset..records_tag_offset + 4]
|
||||||
.copy_from_slice(&0x000038a6u32.to_le_bytes());
|
.copy_from_slice(&0x000038a6u32.to_le_bytes());
|
||||||
bytes[close_tag_offset..close_tag_offset + 4]
|
bytes[close_tag_offset..close_tag_offset + 4].copy_from_slice(&0x000038a7u32.to_le_bytes());
|
||||||
.copy_from_slice(&0x000038a7u32.to_le_bytes());
|
|
||||||
let header_words = [
|
let header_words = [
|
||||||
0u32, 0x06, 1000, 500, 1000, 388, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
0u32, 0x06, 1000, 500, 1000, 388, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
];
|
];
|
||||||
|
|
@ -18728,6 +18923,11 @@ mod tests {
|
||||||
assert_eq!(probe.prefix_separator_byte_hex, "0xff");
|
assert_eq!(probe.prefix_separator_byte_hex, "0xff");
|
||||||
assert_eq!(probe.first_embedded_name_tag_relative_offset, 7);
|
assert_eq!(probe.first_embedded_name_tag_relative_offset, 7);
|
||||||
assert_eq!(probe.embedded_name_tag_count, 1);
|
assert_eq!(probe.embedded_name_tag_count, 1);
|
||||||
|
assert_eq!(probe.unique_compact_prefix_pattern_count, 1);
|
||||||
|
assert_eq!(
|
||||||
|
probe.prefix_leading_dword_matching_embedded_profile_tag_count,
|
||||||
|
0
|
||||||
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
probe.first_embedded_primary_name.as_deref(),
|
probe.first_embedded_primary_name.as_deref(),
|
||||||
Some("TrackCapST_Cap.3dp")
|
Some("TrackCapST_Cap.3dp")
|
||||||
|
|
@ -18736,6 +18936,103 @@ mod tests {
|
||||||
probe.first_embedded_secondary_name.as_deref(),
|
probe.first_embedded_secondary_name.as_deref(),
|
||||||
Some("Infrastructure")
|
Some("Infrastructure")
|
||||||
);
|
);
|
||||||
|
assert_eq!(probe.compact_prefix_pattern_summaries.len(), 1);
|
||||||
|
assert_eq!(
|
||||||
|
probe.compact_prefix_pattern_summaries[0].prefix_leading_dword_hex,
|
||||||
|
"0x0005d368"
|
||||||
|
);
|
||||||
|
assert_eq!(probe.compact_prefix_pattern_summaries[0].count, 1);
|
||||||
|
assert_eq!(
|
||||||
|
probe.compact_prefix_pattern_summaries[0].cap_like_primary_name_count,
|
||||||
|
1
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
probe.compact_prefix_pattern_summaries[0].section_like_primary_name_count,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn summarizes_placed_structure_dynamic_side_buffer_compact_prefix_patterns() {
|
||||||
|
let mut bytes = vec![0u8; 0x600];
|
||||||
|
let metadata_tag_offset = 0x40usize;
|
||||||
|
let records_tag_offset = 0x140usize;
|
||||||
|
let close_tag_offset = 0x320usize;
|
||||||
|
bytes[metadata_tag_offset..metadata_tag_offset + 4]
|
||||||
|
.copy_from_slice(&0x000038a5u32.to_le_bytes());
|
||||||
|
bytes[records_tag_offset..records_tag_offset + 4]
|
||||||
|
.copy_from_slice(&0x000038a6u32.to_le_bytes());
|
||||||
|
bytes[close_tag_offset..close_tag_offset + 4].copy_from_slice(&0x000038a7u32.to_le_bytes());
|
||||||
|
let header_words = [
|
||||||
|
0u32, 0x06, 1000, 500, 1000, 388, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
];
|
||||||
|
for (index, word) in header_words.into_iter().enumerate() {
|
||||||
|
let offset = metadata_tag_offset + 4 + index * 4;
|
||||||
|
bytes[offset..offset + 4].copy_from_slice(&word.to_le_bytes());
|
||||||
|
}
|
||||||
|
let mut cursor = records_tag_offset + 4;
|
||||||
|
for (leading_dword, primary_name) in [
|
||||||
|
(0x000055f3u32, "TunnelSTBrick_Section.3dp"),
|
||||||
|
(0x000055f3u32, "TunnelSTBrick_Cap.3dp"),
|
||||||
|
(0xff0000ffu32, "TunnelSTBrick_Cap.3dp"),
|
||||||
|
] {
|
||||||
|
bytes[cursor..cursor + 4].copy_from_slice(&leading_dword.to_le_bytes());
|
||||||
|
bytes[cursor + 4..cursor + 6].copy_from_slice(&0x0001u16.to_le_bytes());
|
||||||
|
bytes[cursor + 6] = 0xff;
|
||||||
|
let name_tag_offset = cursor + 7;
|
||||||
|
bytes[name_tag_offset..name_tag_offset + 2]
|
||||||
|
.copy_from_slice(&SAVE_REGION_RECORD_NAME_TAG.to_le_bytes());
|
||||||
|
let secondary_name = "Infrastructure";
|
||||||
|
bytes[name_tag_offset + 4] = primary_name.len() as u8;
|
||||||
|
bytes[name_tag_offset + 5..name_tag_offset + 5 + primary_name.len()]
|
||||||
|
.copy_from_slice(primary_name.as_bytes());
|
||||||
|
let second_len_offset = name_tag_offset + 5 + primary_name.len();
|
||||||
|
bytes[second_len_offset] = secondary_name.len() as u8;
|
||||||
|
bytes[second_len_offset + 1..second_len_offset + 1 + secondary_name.len()]
|
||||||
|
.copy_from_slice(secondary_name.as_bytes());
|
||||||
|
cursor = second_len_offset + 1 + secondary_name.len();
|
||||||
|
}
|
||||||
|
|
||||||
|
let probe = parse_save_placed_structure_dynamic_side_buffer_probe(
|
||||||
|
&bytes,
|
||||||
|
Some("gms"),
|
||||||
|
Some(&SmpContainerProfile {
|
||||||
|
profile_family: "rt3-105-save-container-v1".to_string(),
|
||||||
|
profile_evidence: vec![],
|
||||||
|
is_known_profile: true,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.expect("placed-structure dynamic side-buffer probe should parse");
|
||||||
|
|
||||||
|
assert_eq!(probe.embedded_name_tag_count, 3);
|
||||||
|
assert_eq!(probe.unique_compact_prefix_pattern_count, 2);
|
||||||
|
assert_eq!(
|
||||||
|
probe.prefix_leading_dword_matching_embedded_profile_tag_count,
|
||||||
|
2
|
||||||
|
);
|
||||||
|
assert_eq!(probe.compact_prefix_pattern_summaries.len(), 2);
|
||||||
|
assert_eq!(
|
||||||
|
probe.compact_prefix_pattern_summaries[0].prefix_leading_dword_hex,
|
||||||
|
"0x000055f3"
|
||||||
|
);
|
||||||
|
assert_eq!(probe.compact_prefix_pattern_summaries[0].count, 2);
|
||||||
|
assert_eq!(
|
||||||
|
probe.compact_prefix_pattern_summaries[0].section_like_primary_name_count,
|
||||||
|
1
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
probe.compact_prefix_pattern_summaries[0].cap_like_primary_name_count,
|
||||||
|
1
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
probe.compact_prefix_pattern_summaries[0]
|
||||||
|
.prefix_leading_dword_matches_embedded_profile_tag
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
probe.compact_prefix_pattern_summaries[1].prefix_leading_dword_hex,
|
||||||
|
"0xff0000ff"
|
||||||
|
);
|
||||||
|
assert_eq!(probe.compact_prefix_pattern_summaries[1].count, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
|
|
@ -24,8 +24,10 @@ Working rule:
|
||||||
separate tagged side-buffer seam candidates, especially the exact `0x38a5/0x38a6/0x38a7`
|
separate tagged side-buffer seam candidates, especially the exact `0x38a5/0x38a6/0x38a7`
|
||||||
family whose compact `6`-byte header pattern and embedded placed-structure-style `0x55f1`
|
family whose compact `6`-byte header pattern and embedded placed-structure-style `0x55f1`
|
||||||
name rows now make it the grounded placed-structure dynamic side-buffer owner; the remaining
|
name rows now make it the grounded placed-structure dynamic side-buffer owner; the remaining
|
||||||
blocker is semantic closure of the 6-byte prefix lane and its relation to the embedded
|
blocker is semantic closure of the compact prefix regimes now summarized in real saves as seven
|
||||||
`0x55f1/0x55f2/0x55f3` row subset.
|
stable patterns on `q.gms` and their relation to the embedded `0x55f1/0x55f2/0x55f3` row
|
||||||
|
subset, especially the dominant bridge-section `0xff000000/0x0001/0xff` group and the smaller
|
||||||
|
`0x000055f3/0x0001/0xff` group that matches the embedded profile tag directly.
|
||||||
- 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
|
||||||
|
|
@ -102,6 +104,12 @@ Working rule:
|
||||||
samples include repeated `TunnelSTBrick_*` names under `Infrastructure` with compact leading
|
samples include repeated `TunnelSTBrick_*` names under `Infrastructure` with compact leading
|
||||||
dwords like `0x000055f3` and `0xff0000ff`, so the next pass can target the semantics of those
|
dwords like `0x000055f3` and `0xff0000ff`, so the next pass can target the semantics of those
|
||||||
compact prefix patterns instead of hunting the owner seam itself.
|
compact prefix patterns instead of hunting the owner seam itself.
|
||||||
|
- The `0x38a5` probe now also summarizes all embedded compact prefix regimes instead of just the
|
||||||
|
first few samples: grounded `q.gms` currently exposes seven stable pattern groups across 138
|
||||||
|
embedded rows, with the dominant `0xff000000/0x0001/0xff` group carrying 62 bridge-section
|
||||||
|
rows, the `0xff0000ff/0x0001/0xff` and `0xf3010100/0x0055/0x00` groups concentrating cap-like
|
||||||
|
rows, and a smaller `0x000055f3/0x0001/0xff` group carrying 17 tunnel-section / cap rows whose
|
||||||
|
leading dword matches the embedded placed-structure profile tag directly.
|
||||||
- The placed-structure tagged save stream now also exposes repeated `0x55f1/0x55f2/0x55f3`
|
- 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`
|
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
|
footer carrying one raw `u32` payload lane plus one live `i32` status lane, so the remaining
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue