Probe infrastructure payload 0x55f1 triplets
This commit is contained in:
parent
68a2a3401e
commit
ae67719ac6
3 changed files with 104 additions and 11 deletions
|
|
@ -1838,6 +1838,7 @@ pub struct SmpSavePlacedStructureDynamicSideBufferProbe {
|
|||
pub first_embedded_name_tag_relative_offset: usize,
|
||||
pub embedded_name_tag_count: usize,
|
||||
pub decoded_embedded_name_row_count: usize,
|
||||
pub decoded_embedded_name_row_with_tertiary_name_count: usize,
|
||||
pub unique_compact_prefix_pattern_count: usize,
|
||||
pub prefix_leading_dword_matching_embedded_profile_tag_count: usize,
|
||||
pub unique_embedded_name_pair_count: usize,
|
||||
|
|
@ -1846,6 +1847,8 @@ pub struct SmpSavePlacedStructureDynamicSideBufferProbe {
|
|||
#[serde(default)]
|
||||
pub first_embedded_secondary_name: Option<String>,
|
||||
#[serde(default)]
|
||||
pub first_embedded_tertiary_name: Option<String>,
|
||||
#[serde(default)]
|
||||
pub embedded_name_row_samples: Vec<SmpSavePlacedStructureDynamicSideBufferSampleEntry>,
|
||||
#[serde(default)]
|
||||
pub compact_prefix_pattern_summaries:
|
||||
|
|
@ -1869,6 +1872,8 @@ pub struct SmpSavePlacedStructureDynamicSideBufferSampleEntry {
|
|||
pub primary_name: Option<String>,
|
||||
#[serde(default)]
|
||||
pub secondary_name: Option<String>,
|
||||
#[serde(default)]
|
||||
pub tertiary_name: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
|
|
@ -3960,6 +3965,13 @@ fn build_infrastructure_asset_trace_report(
|
|||
"direct disassembly now also shows 0x005181f0/0x00518260 treating the same 12-byte rows as a live-entry directory: dword +0 is the payload pointer, dword +4 is previous live id, and dword +8 is next live id, with collection head/tail caches alongside them".to_string(),
|
||||
"direct disassembly now also shows 0x00493be0 iterating live-entry ordinals through 0x00518380(ordinal, 0), converting each ordinal to a live id, then resolving that live id through 0x00518140 before handing the resulting payload pointer to 0x0048dcf0".to_string(),
|
||||
"direct disassembly now shows 0x00518680 loading the non-direct collection header, tombstone bitset, and live-id-bound-scaled 12-byte tables for the non-direct path before 0x00493be0 starts iterating".to_string(),
|
||||
"direct disassembly now also shows the shared child payload callback 0x00455fc0 opening 0x55f1, parsing three len-prefixed strings through 0x531380, opening 0x55f2, seeding the child through 0x455b70, dispatching slot +0x48, and then opening 0x55f3".to_string(),
|
||||
format!(
|
||||
"current save-side probe reports {} embedded 0x55f1 rows with a third decoded string",
|
||||
side_buffer
|
||||
.map(|probe| probe.decoded_embedded_name_row_with_tertiary_name_count)
|
||||
.unwrap_or_default()
|
||||
),
|
||||
"local .rdata at 0x005cfd00 now also proves the infrastructure child table uses the shared tagged callback strip directly: slot +0x40 = 0x455fc0, slot +0x48 = 0x455870, and slot +0x4c = 0x455930".to_string(),
|
||||
"direct disassembly now shows 0x0048a1e0 cloning the first child triplet bands through 0x52e880/0x52e720, destroying the prior child, seeding a new literal Infrastructure child through 0x455b70 with payload seed 0x5c87a8, attaching through 0x5395d0 or 0x53a5d0, and republishing the two bands through 0x52e8b0/0x530720".to_string(),
|
||||
"direct disassembly now also shows the outer owner at 0x0048dcf0 reading a child count plus optional primary-child ordinal from the tagged stream through 0x531150, zeroing [this+0x08], dispatching each fresh child through 0x455a50 -> vtable slot +0x40, culling ordinals above 5, and restoring cached primary-child slot [this+0x248] from the saved ordinal".to_string(),
|
||||
|
|
@ -3968,6 +3980,7 @@ fn build_infrastructure_asset_trace_report(
|
|||
blockers: vec![
|
||||
"how the payload streams reached through 0x00518380 -> 0x00518140 align with the embedded 0x55f1 name-pair groups and compact-prefix regimes surfaced by the save-side probe".to_string(),
|
||||
"which tagged values inside each payload stream correspond to the child count, optional primary-child ordinal, and the per-child shared tagged callback sequence consumed by 0x0048dcf0".to_string(),
|
||||
"whether the third 0x55f1 string parsed by 0x00455fc0 is absent on grounded saves, stored under a different framing than the current probe, or only populated on a narrower infrastructure subset".to_string(),
|
||||
"which restored child fields or grouped rows retain the 0x38a5 embedded name-pair semantics before route/local-runtime follow-ons take over".to_string(),
|
||||
],
|
||||
},
|
||||
|
|
@ -4436,7 +4449,7 @@ pub fn load_save_slice_from_report(
|
|||
if let Some(probe) = &placed_structure_dynamic_side_buffer_probe {
|
||||
let dominant_pattern = probe.compact_prefix_pattern_summaries.first();
|
||||
notes.push(format!(
|
||||
"Raw save also exposes the separate placed-structure dynamic-side-buffer candidate 0x38a5/0x38a6/0x38a7: live_record_count={}, owner-shared 0x38a6 dword={} at relative offset 0x{:x}, first compact prefix=({},{},{}), first embedded names={:?}/{:?}, embedded 0x55f1 row count={}, unique compact prefix patterns={}, 0x55f3-leading rows={}, dominant compact pattern={}/{}/{} x{}.",
|
||||
"Raw save also exposes the separate placed-structure dynamic-side-buffer candidate 0x38a5/0x38a6/0x38a7: live_record_count={}, owner-shared 0x38a6 dword={} at relative offset 0x{:x}, first compact prefix=({},{},{}), first embedded names={:?}/{:?}/{:?}, embedded 0x55f1 row count={}, rows with tertiary 0x55f1 string={}, unique compact prefix patterns={}, 0x55f3-leading rows={}, dominant compact pattern={}/{}/{} x{}.",
|
||||
probe.live_record_count,
|
||||
probe.owner_shared_dword_hex,
|
||||
probe.owner_shared_dword_relative_offset,
|
||||
|
|
@ -4445,7 +4458,9 @@ pub fn load_save_slice_from_report(
|
|||
probe.prefix_separator_byte_hex,
|
||||
probe.first_embedded_primary_name.as_deref(),
|
||||
probe.first_embedded_secondary_name.as_deref(),
|
||||
probe.first_embedded_tertiary_name.as_deref(),
|
||||
probe.embedded_name_tag_count,
|
||||
probe.decoded_embedded_name_row_with_tertiary_name_count,
|
||||
probe.unique_compact_prefix_pattern_count,
|
||||
probe.prefix_leading_dword_matching_embedded_profile_tag_count,
|
||||
dominant_pattern
|
||||
|
|
@ -4932,10 +4947,11 @@ pub fn inspect_save_company_and_chairman_analysis_bytes(
|
|||
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, {} decoded rows across {} unique name pairs, {} unique compact prefix patterns, {} rows whose leading dword matches 0x55f3, and dominant compact pattern={}/{}/{} x{}.",
|
||||
"Placed-structure analysis now also exports the separate 0x38a5 dynamic side-buffer owner seam with {} embedded name rows, {} decoded rows across {} unique name pairs, {} rows with a tertiary 0x55f1 string, {} unique compact prefix patterns, {} rows whose leading dword matches 0x55f3, and dominant compact pattern={}/{}/{} x{}.",
|
||||
side_buffer.embedded_name_tag_count,
|
||||
side_buffer.decoded_embedded_name_row_count,
|
||||
side_buffer.unique_embedded_name_pair_count,
|
||||
side_buffer.decoded_embedded_name_row_with_tertiary_name_count,
|
||||
side_buffer.unique_compact_prefix_pattern_count,
|
||||
side_buffer.prefix_leading_dword_matching_embedded_profile_tag_count,
|
||||
dominant_pattern
|
||||
|
|
@ -11725,6 +11741,7 @@ fn parse_save_placed_structure_dynamic_side_buffer_probe(
|
|||
prefix_separator_byte: u8,
|
||||
primary_name: Option<String>,
|
||||
secondary_name: Option<String>,
|
||||
tertiary_name: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
|
|
@ -11851,13 +11868,16 @@ fn parse_save_placed_structure_dynamic_side_buffer_probe(
|
|||
else {
|
||||
continue;
|
||||
};
|
||||
if let Some(names) = parse_save_len_prefixed_ascii_name_pair(name_payload) {
|
||||
if let Some(names) = parse_save_len_prefixed_ascii_name_triplet(name_payload) {
|
||||
parsed_embedded_names = Some(names);
|
||||
break;
|
||||
}
|
||||
}
|
||||
let Some((first_embedded_primary_name, first_embedded_secondary_name)) =
|
||||
parsed_embedded_names
|
||||
let Some((
|
||||
first_embedded_primary_name,
|
||||
first_embedded_secondary_name,
|
||||
first_embedded_tertiary_name,
|
||||
)) = parsed_embedded_names
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
|
@ -11879,12 +11899,13 @@ fn parse_save_placed_structure_dynamic_side_buffer_probe(
|
|||
else {
|
||||
continue;
|
||||
};
|
||||
if let Some(names) = parse_save_len_prefixed_ascii_name_pair(name_payload) {
|
||||
if let Some(names) = parse_save_len_prefixed_ascii_name_triplet(name_payload) {
|
||||
parsed_names = Some(names);
|
||||
break;
|
||||
}
|
||||
}
|
||||
let (primary_name, secondary_name) = parsed_names.unwrap_or_default();
|
||||
let (primary_name, secondary_name, tertiary_name) =
|
||||
parsed_names.unwrap_or_default();
|
||||
Some(EmbeddedNameRow {
|
||||
name_tag_relative_offset,
|
||||
prefix_leading_dword,
|
||||
|
|
@ -11892,6 +11913,7 @@ fn parse_save_placed_structure_dynamic_side_buffer_probe(
|
|||
prefix_separator_byte,
|
||||
primary_name: (!primary_name.is_empty()).then_some(primary_name),
|
||||
secondary_name: (!secondary_name.is_empty()).then_some(secondary_name),
|
||||
tertiary_name,
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
|
@ -11911,6 +11933,7 @@ fn parse_save_placed_structure_dynamic_side_buffer_probe(
|
|||
prefix_separator_byte_hex: format!("0x{:02x}", row.prefix_separator_byte),
|
||||
primary_name: row.primary_name.clone(),
|
||||
secondary_name: row.secondary_name.clone(),
|
||||
tertiary_name: row.tertiary_name.clone(),
|
||||
},
|
||||
)
|
||||
.collect::<Vec<_>>();
|
||||
|
|
@ -12061,6 +12084,14 @@ fn parse_save_placed_structure_dynamic_side_buffer_probe(
|
|||
.iter()
|
||||
.filter(|row| row.primary_name.is_some() && row.secondary_name.is_some())
|
||||
.count();
|
||||
let decoded_embedded_name_row_with_tertiary_name_count = embedded_name_rows
|
||||
.iter()
|
||||
.filter(|row| {
|
||||
row.primary_name.is_some()
|
||||
&& row.secondary_name.is_some()
|
||||
&& row.tertiary_name.is_some()
|
||||
})
|
||||
.count();
|
||||
return Some(SmpSavePlacedStructureDynamicSideBufferProbe {
|
||||
profile_family: profile.profile_family.clone(),
|
||||
source_kind: "save-placed-structure-dynamic-side-buffer-records".to_string(),
|
||||
|
|
@ -12089,11 +12120,13 @@ fn parse_save_placed_structure_dynamic_side_buffer_probe(
|
|||
first_embedded_name_tag_relative_offset,
|
||||
embedded_name_tag_count: embedded_name_tag_offsets.len(),
|
||||
decoded_embedded_name_row_count,
|
||||
decoded_embedded_name_row_with_tertiary_name_count,
|
||||
unique_compact_prefix_pattern_count: compact_prefix_pattern_summaries.len(),
|
||||
prefix_leading_dword_matching_embedded_profile_tag_count,
|
||||
unique_embedded_name_pair_count,
|
||||
first_embedded_primary_name: Some(first_embedded_primary_name.clone()),
|
||||
first_embedded_secondary_name: Some(first_embedded_secondary_name.clone()),
|
||||
first_embedded_tertiary_name: first_embedded_tertiary_name.clone(),
|
||||
embedded_name_row_samples,
|
||||
compact_prefix_pattern_summaries,
|
||||
name_pair_summaries,
|
||||
|
|
@ -12105,9 +12138,10 @@ fn parse_save_placed_structure_dynamic_side_buffer_probe(
|
|||
"records payload begins with a compact 6-byte prefix plus one separator byte before the first embedded 0x55f1 name row".to_string(),
|
||||
"first embedded 0x55f1 row decodes with placed-structure-style dual names, which makes this the strongest current candidate for the separate placed-structure dynamic side-buffer owner seam".to_string(),
|
||||
format!(
|
||||
"grounded first embedded names are {:?}/{:?} with {} embedded 0x55f1 name rows in the tagged records span",
|
||||
"grounded first embedded names are {:?}/{:?}/{:?} with {} embedded 0x55f1 name rows in the tagged records span",
|
||||
Some(first_embedded_primary_name),
|
||||
Some(first_embedded_secondary_name),
|
||||
first_embedded_tertiary_name,
|
||||
embedded_name_tag_offsets.len()
|
||||
),
|
||||
format!(
|
||||
|
|
@ -12117,8 +12151,10 @@ fn parse_save_placed_structure_dynamic_side_buffer_probe(
|
|||
u32::from(SAVE_REGION_RECORD_PROFILE_TAG)
|
||||
),
|
||||
format!(
|
||||
"{decoded_embedded_name_row_count} decoded embedded name rows collapse into {} unique placed-structure name pairs",
|
||||
"{decoded_embedded_name_row_count} decoded embedded name rows collapse into {} unique placed-structure name pairs; {} rows also expose a third embedded 0x55f1 string",
|
||||
unique_embedded_name_pair_count
|
||||
,
|
||||
decoded_embedded_name_row_with_tertiary_name_count
|
||||
),
|
||||
dominant_compact_prefix_pattern
|
||||
.map(|pattern| {
|
||||
|
|
@ -12511,6 +12547,13 @@ fn parse_save_len_prefixed_ascii_name(bytes: &[u8]) -> Option<String> {
|
|||
}
|
||||
|
||||
fn parse_save_len_prefixed_ascii_name_pair(bytes: &[u8]) -> Option<(String, String)> {
|
||||
let (first, second, _) = parse_save_len_prefixed_ascii_name_triplet(bytes)?;
|
||||
Some((first, second))
|
||||
}
|
||||
|
||||
fn parse_save_len_prefixed_ascii_name_triplet(
|
||||
bytes: &[u8],
|
||||
) -> Option<(String, String, Option<String>)> {
|
||||
let first_len = *bytes.first()? as usize;
|
||||
let first_end = 1 + first_len;
|
||||
let first = std::str::from_utf8(bytes.get(1..first_end)?)
|
||||
|
|
@ -12530,7 +12573,24 @@ fn parse_save_len_prefixed_ascii_name_pair(bytes: &[u8]) -> Option<(String, Stri
|
|||
if first.is_empty() || second.is_empty() {
|
||||
return None;
|
||||
}
|
||||
Some((first, second))
|
||||
let mut third_len_offset = second_start + second_len;
|
||||
while matches!(bytes.get(third_len_offset), Some(0)) {
|
||||
third_len_offset += 1;
|
||||
}
|
||||
let third = bytes
|
||||
.get(third_len_offset)
|
||||
.copied()
|
||||
.filter(|len| *len != 0)
|
||||
.and_then(|third_len| {
|
||||
let third_len = third_len as usize;
|
||||
let third_start = third_len_offset + 1;
|
||||
let text = std::str::from_utf8(bytes.get(third_start..third_start + third_len)?)
|
||||
.ok()?
|
||||
.trim_end_matches('\0')
|
||||
.to_string();
|
||||
(!text.is_empty()).then_some(text)
|
||||
});
|
||||
Some((first, second, third))
|
||||
}
|
||||
|
||||
fn parse_save_fixed_ascii_name(bytes: &[u8]) -> Option<String> {
|
||||
|
|
@ -20205,6 +20265,7 @@ mod tests {
|
|||
assert_eq!(probe.first_embedded_name_tag_relative_offset, 7);
|
||||
assert_eq!(probe.embedded_name_tag_count, 1);
|
||||
assert_eq!(probe.decoded_embedded_name_row_count, 1);
|
||||
assert_eq!(probe.decoded_embedded_name_row_with_tertiary_name_count, 0);
|
||||
assert_eq!(probe.unique_compact_prefix_pattern_count, 1);
|
||||
assert_eq!(
|
||||
probe.prefix_leading_dword_matching_embedded_profile_tag_count,
|
||||
|
|
@ -20219,6 +20280,7 @@ mod tests {
|
|||
probe.first_embedded_secondary_name.as_deref(),
|
||||
Some("Infrastructure")
|
||||
);
|
||||
assert_eq!(probe.first_embedded_tertiary_name.as_deref(), None);
|
||||
assert_eq!(probe.compact_prefix_pattern_summaries.len(), 1);
|
||||
assert_eq!(
|
||||
probe.compact_prefix_pattern_summaries[0].prefix_leading_dword_hex,
|
||||
|
|
@ -20295,6 +20357,7 @@ mod tests {
|
|||
|
||||
assert_eq!(probe.embedded_name_tag_count, 3);
|
||||
assert_eq!(probe.decoded_embedded_name_row_count, 3);
|
||||
assert_eq!(probe.decoded_embedded_name_row_with_tertiary_name_count, 0);
|
||||
assert_eq!(probe.unique_compact_prefix_pattern_count, 2);
|
||||
assert_eq!(
|
||||
probe.prefix_leading_dword_matching_embedded_profile_tag_count,
|
||||
|
|
@ -20333,6 +20396,19 @@ mod tests {
|
|||
assert_eq!(probe.name_pair_summaries[1].count, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parses_save_len_prefixed_ascii_name_triplet_with_optional_third_name() {
|
||||
let bytes = [
|
||||
5u8, b'F', b'i', b'r', b's', b't', 0, 6, b'S', b'e', b'c', b'o', b'n', b'd', 0, 5,
|
||||
b'T', b'h', b'i', b'r', b'd',
|
||||
];
|
||||
let parsed = parse_save_len_prefixed_ascii_name_triplet(&bytes)
|
||||
.expect("triplet parser should decode three len-prefixed ascii names");
|
||||
assert_eq!(parsed.0, "First");
|
||||
assert_eq!(parsed.1, "Second");
|
||||
assert_eq!(parsed.2.as_deref(), Some("Third"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn aligns_placed_structure_dynamic_side_buffer_name_pairs_with_triplets() {
|
||||
let side_buffer = SmpSavePlacedStructureDynamicSideBufferProbe {
|
||||
|
|
@ -20363,11 +20439,13 @@ mod tests {
|
|||
first_embedded_name_tag_relative_offset: 7,
|
||||
embedded_name_tag_count: 3,
|
||||
decoded_embedded_name_row_count: 3,
|
||||
decoded_embedded_name_row_with_tertiary_name_count: 0,
|
||||
unique_compact_prefix_pattern_count: 2,
|
||||
prefix_leading_dword_matching_embedded_profile_tag_count: 2,
|
||||
unique_embedded_name_pair_count: 2,
|
||||
first_embedded_primary_name: Some("TunnelSTBrick_Section.3dp".to_string()),
|
||||
first_embedded_secondary_name: Some("Infrastructure".to_string()),
|
||||
first_embedded_tertiary_name: None,
|
||||
embedded_name_row_samples: vec![],
|
||||
compact_prefix_pattern_summaries: vec![],
|
||||
name_pair_summaries: vec![
|
||||
|
|
@ -22015,11 +22093,13 @@ mod tests {
|
|||
first_embedded_name_tag_relative_offset: 0x20,
|
||||
embedded_name_tag_count: 138,
|
||||
decoded_embedded_name_row_count: 138,
|
||||
decoded_embedded_name_row_with_tertiary_name_count: 0,
|
||||
unique_compact_prefix_pattern_count: 7,
|
||||
prefix_leading_dword_matching_embedded_profile_tag_count: 17,
|
||||
unique_embedded_name_pair_count: 5,
|
||||
first_embedded_primary_name: Some("TrackCapST_Cap.3dp".to_string()),
|
||||
first_embedded_secondary_name: Some("Infrastructure".to_string()),
|
||||
first_embedded_tertiary_name: None,
|
||||
embedded_name_row_samples: Vec::new(),
|
||||
compact_prefix_pattern_summaries: Vec::new(),
|
||||
name_pair_summaries: vec![SmpSavePlacedStructureDynamicSideBufferNamePairSummary {
|
||||
|
|
|
|||
|
|
@ -2933,6 +2933,13 @@ The low helper strip beneath that shared family is tighter now too: `0x0052ecd0`
|
|||
next unknowns are no longer inside that row layout itself; they are in the payload streams those
|
||||
rows point at, and how those streams align with the save-side `0x55f1` name-pair groups and
|
||||
compact prefix regimes.
|
||||
The shared child payload callback is tighter now too: `0x00455fc0` zeroes the route-entry and
|
||||
cached bridge bands, opens `0x55f1`, parses three len-prefixed strings through `0x00531380`,
|
||||
opens `0x55f2`, seeds the child through `0x00455b70`, dispatches slot `+0x48`, runs the local
|
||||
follow-on `0x0052ebd0`, and then opens `0x55f3`. The widened save-side probe currently still
|
||||
sees only two embedded `0x55f1` strings on grounded `q.gms`, so the remaining payload question is
|
||||
whether that third parsed string is absent on ordinary saves, hidden behind different framing, or
|
||||
only populated on a narrower infrastructure subset.
|
||||
The child loader family is explicit now too: local `.rdata` at `0x005cfd00` proves the
|
||||
`Infrastructure` child vtable uses the shared tagged callback strip directly, with
|
||||
`+0x40 = 0x00455fc0`, `+0x48 = 0x00455870`, and `+0x4c = 0x00455930`. So the remaining
|
||||
|
|
|
|||
|
|
@ -65,7 +65,13 @@ Working rule:
|
|||
`(payload pointer, previous live id, next live id)`, so the next infrastructure question is only
|
||||
how those payload streams align with the embedded `0x55f1` name-pair groups and compact-prefix
|
||||
regimes, and which tagged values inside each payload stream become the child count, optional
|
||||
primary-child ordinal, and per-child callback sequence that `0x0048dcf0` consumes.
|
||||
primary-child ordinal, and per-child callback sequence that `0x0048dcf0` consumes. Direct
|
||||
disassembly now also shows the shared child payload callback `0x00455fc0` opening
|
||||
`0x55f1 -> 0x55f2 -> 0x55f3`, parsing three `0x55f1` strings through `0x00531380`, seeding the
|
||||
child through `0x00455b70`, and then dispatching slot `+0x48`; the widened save-side probe
|
||||
currently sees `0` third `0x55f1` strings on grounded `q.gms`, so the next pass should ask
|
||||
whether that third string is genuinely absent on ordinary saves or stored under different
|
||||
framing than the current embedded-row scan.
|
||||
- The child loader identity is closed now too: local `.rdata` at `0x005cfd00` proves the
|
||||
`Infrastructure` child vtable uses the shared tagged callback strip directly, with
|
||||
`+0x40 = 0x00455fc0`, `+0x48 = 0x00455870`, and `+0x4c = 0x00455930`. So the remaining
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue