Probe raw save company and chairman header counts
This commit is contained in:
parent
09ccc77a45
commit
0fbe03e470
5 changed files with 412 additions and 19 deletions
|
|
@ -82,9 +82,9 @@ pub use smp::{
|
|||
SmpRt3105SaveBridgePayloadProbe, SmpRt3105SaveNameTableEntry, SmpRt3105SaveNameTableProbe,
|
||||
SmpRuntimeAnchorCycleBlock, SmpRuntimePostSpanHeaderCandidate, SmpRuntimePostSpanProbe,
|
||||
SmpRuntimeTrailerBlock, SmpSaveAnchorRunBlock, SmpSaveBootstrapBlock,
|
||||
SmpSaveLoadCandidateTableSummary, SmpSaveLoadSummary, SmpSecondaryVariantProbe,
|
||||
SmpSharedHeader, SmpSpecialConditionEntry, SmpSpecialConditionsProbe, inspect_smp_bytes,
|
||||
inspect_smp_file, load_save_slice_file, load_save_slice_from_report,
|
||||
SmpSaveLoadCandidateTableSummary, SmpSaveLoadSummary, SmpSaveTaggedCollectionHeaderProbe,
|
||||
SmpSecondaryVariantProbe, SmpSharedHeader, SmpSpecialConditionEntry, SmpSpecialConditionsProbe,
|
||||
inspect_smp_bytes, inspect_smp_file, load_save_slice_file, load_save_slice_from_report,
|
||||
};
|
||||
pub use step::{BoundaryEvent, ServiceEvent, StepCommand, StepResult, execute_step_command};
|
||||
pub use summary::RuntimeSummary;
|
||||
|
|
|
|||
|
|
@ -1479,6 +1479,27 @@ pub struct SmpSaveWorldSelectionContextProbe {
|
|||
pub evidence: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct SmpSaveTaggedCollectionHeaderProbe {
|
||||
pub profile_family: String,
|
||||
pub source_kind: String,
|
||||
pub semantic_family: String,
|
||||
pub metadata_tag_offset: usize,
|
||||
pub records_tag_offset: usize,
|
||||
pub close_tag_offset: usize,
|
||||
pub direct_collection_flag: u32,
|
||||
pub direct_collection_flag_hex: String,
|
||||
pub direct_record_stride: u32,
|
||||
pub direct_record_stride_hex: String,
|
||||
pub live_id_bound: u32,
|
||||
pub live_id_bound_hex: String,
|
||||
pub live_record_count: u32,
|
||||
pub live_record_count_hex: String,
|
||||
pub header_words: Vec<u32>,
|
||||
pub header_hex_words: Vec<String>,
|
||||
pub evidence: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct SmpRt3105SaveNameTableProbe {
|
||||
pub profile_family: String,
|
||||
|
|
@ -2398,6 +2419,8 @@ pub struct SmpInspectionReport {
|
|||
pub rt3_105_post_span_bridge_probe: Option<SmpRt3105PostSpanBridgeProbe>,
|
||||
pub rt3_105_save_bridge_payload_probe: Option<SmpRt3105SaveBridgePayloadProbe>,
|
||||
pub save_world_selection_context_probe: Option<SmpSaveWorldSelectionContextProbe>,
|
||||
pub save_company_collection_header_probe: Option<SmpSaveTaggedCollectionHeaderProbe>,
|
||||
pub save_chairman_profile_collection_header_probe: Option<SmpSaveTaggedCollectionHeaderProbe>,
|
||||
pub rt3_105_save_name_table_probe: Option<SmpRt3105SaveNameTableProbe>,
|
||||
pub rt3_105_save_named_locomotive_availability_probe:
|
||||
Option<SmpRt3105SaveNamedLocomotiveAvailabilityProbe>,
|
||||
|
|
@ -2536,11 +2559,24 @@ pub fn load_save_slice_from_report(
|
|||
let company_roster = report
|
||||
.save_world_selection_context_probe
|
||||
.as_ref()
|
||||
.and_then(derive_selection_only_company_roster_from_save_world_probe);
|
||||
let chairman_profile_table = report
|
||||
.save_world_selection_context_probe
|
||||
.as_ref()
|
||||
.and_then(derive_selection_only_chairman_profile_table_from_save_world_probe);
|
||||
.and_then(|probe| {
|
||||
derive_selection_only_company_roster_from_save_world_probe(
|
||||
probe,
|
||||
report.save_company_collection_header_probe.as_ref(),
|
||||
)
|
||||
});
|
||||
let chairman_profile_table =
|
||||
report
|
||||
.save_world_selection_context_probe
|
||||
.as_ref()
|
||||
.and_then(|probe| {
|
||||
derive_selection_only_chairman_profile_table_from_save_world_probe(
|
||||
probe,
|
||||
report
|
||||
.save_chairman_profile_collection_header_probe
|
||||
.as_ref(),
|
||||
)
|
||||
});
|
||||
let special_conditions_table =
|
||||
report
|
||||
.special_conditions_probe
|
||||
|
|
@ -2575,6 +2611,26 @@ pub fn load_save_slice_from_report(
|
|||
.to_string(),
|
||||
);
|
||||
}
|
||||
if let Some(probe) = &report.save_company_collection_header_probe {
|
||||
notes.push(format!(
|
||||
"Raw save tagged company header reports live_record_count={} and live_id_bound={} at file offsets 0x{:x}/0x{:x}/0x{:x}.",
|
||||
probe.live_record_count,
|
||||
probe.live_id_bound,
|
||||
probe.metadata_tag_offset,
|
||||
probe.records_tag_offset,
|
||||
probe.close_tag_offset
|
||||
));
|
||||
}
|
||||
if let Some(probe) = &report.save_chairman_profile_collection_header_probe {
|
||||
notes.push(format!(
|
||||
"Raw save tagged chairman/profile header reports live_record_count={} and live_id_bound={} at file offsets 0x{:x}/0x{:x}/0x{:x}.",
|
||||
probe.live_record_count,
|
||||
probe.live_id_bound,
|
||||
probe.metadata_tag_offset,
|
||||
probe.records_tag_offset,
|
||||
probe.close_tag_offset
|
||||
));
|
||||
}
|
||||
|
||||
Ok(SmpLoadedSaveSlice {
|
||||
file_extension_hint: summary.file_extension_hint.clone(),
|
||||
|
|
@ -2677,11 +2733,14 @@ fn derive_cargo_catalog_from_recipe_book_probe(
|
|||
|
||||
fn derive_selection_only_company_roster_from_save_world_probe(
|
||||
probe: &SmpSaveWorldSelectionContextProbe,
|
||||
header_probe: Option<&SmpSaveTaggedCollectionHeaderProbe>,
|
||||
) -> Option<SmpLoadedCompanyRoster> {
|
||||
Some(SmpLoadedCompanyRoster {
|
||||
source_kind: format!("{}-company-selection-only", probe.source_kind),
|
||||
semantic_family: "scenario-selected-company-context".to_string(),
|
||||
observed_entry_count: 0,
|
||||
observed_entry_count: header_probe
|
||||
.map(|probe| probe.live_record_count as usize)
|
||||
.unwrap_or(0),
|
||||
selected_company_id: Some(probe.selected_company_id),
|
||||
entries: Vec::new(),
|
||||
})
|
||||
|
|
@ -2689,11 +2748,14 @@ fn derive_selection_only_company_roster_from_save_world_probe(
|
|||
|
||||
fn derive_selection_only_chairman_profile_table_from_save_world_probe(
|
||||
probe: &SmpSaveWorldSelectionContextProbe,
|
||||
header_probe: Option<&SmpSaveTaggedCollectionHeaderProbe>,
|
||||
) -> Option<SmpLoadedChairmanProfileTable> {
|
||||
Some(SmpLoadedChairmanProfileTable {
|
||||
source_kind: format!("{}-chairman-selection-only", probe.source_kind),
|
||||
semantic_family: "scenario-selected-chairman-context".to_string(),
|
||||
observed_entry_count: 0,
|
||||
observed_entry_count: header_probe
|
||||
.map(|probe| probe.live_record_count as usize)
|
||||
.unwrap_or(0),
|
||||
selected_chairman_profile_id: Some(probe.selected_chairman_profile_id),
|
||||
entries: Vec::new(),
|
||||
})
|
||||
|
|
@ -5420,6 +5482,17 @@ fn inspect_bundle_bytes(bytes: &[u8], file_extension_hint: Option<String>) -> Sm
|
|||
file_extension_hint.as_deref(),
|
||||
container_profile.as_ref(),
|
||||
);
|
||||
let save_company_collection_header_probe = parse_save_company_collection_header_probe(
|
||||
bytes,
|
||||
file_extension_hint.as_deref(),
|
||||
container_profile.as_ref(),
|
||||
);
|
||||
let save_chairman_profile_collection_header_probe =
|
||||
parse_save_chairman_profile_collection_header_probe(
|
||||
bytes,
|
||||
file_extension_hint.as_deref(),
|
||||
container_profile.as_ref(),
|
||||
);
|
||||
let rt3_105_save_name_table_probe = parse_rt3_105_save_name_table_probe(
|
||||
bytes,
|
||||
file_extension_hint.as_deref(),
|
||||
|
|
@ -5567,6 +5640,8 @@ fn inspect_bundle_bytes(bytes: &[u8], file_extension_hint: Option<String>) -> Sm
|
|||
rt3_105_post_span_bridge_probe,
|
||||
rt3_105_save_bridge_payload_probe,
|
||||
save_world_selection_context_probe,
|
||||
save_company_collection_header_probe,
|
||||
save_chairman_profile_collection_header_probe,
|
||||
rt3_105_save_name_table_probe,
|
||||
rt3_105_save_named_locomotive_availability_probe,
|
||||
special_conditions_probe,
|
||||
|
|
@ -7094,6 +7169,177 @@ fn parse_save_world_selection_context_probe(
|
|||
None
|
||||
}
|
||||
|
||||
fn parse_save_company_collection_header_probe(
|
||||
bytes: &[u8],
|
||||
file_extension_hint: Option<&str>,
|
||||
container_profile: Option<&SmpContainerProfile>,
|
||||
) -> Option<SmpSaveTaggedCollectionHeaderProbe> {
|
||||
parse_save_tagged_collection_header_probe(
|
||||
bytes,
|
||||
file_extension_hint,
|
||||
container_profile,
|
||||
0x000061a9,
|
||||
0x000061aa,
|
||||
0x000061ab,
|
||||
"save-company-tagged-header-counts",
|
||||
"scenario-save-company-header-counts",
|
||||
|header| {
|
||||
header.direct_collection_flag == 1
|
||||
&& header.live_id_bound >= 1
|
||||
&& header.live_id_bound <= 0x20
|
||||
&& header.live_record_count <= header.live_id_bound
|
||||
&& header.direct_record_stride >= 0x1000
|
||||
},
|
||||
vec![
|
||||
"save-side company collection uses tagged header family 0x61a9/0x61aa/0x61ab".to_string(),
|
||||
"package-save per-company callback is currently grounded as a no-op stub, so this probe only claims header-level collection counts, not per-company payload".to_string(),
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
fn parse_save_chairman_profile_collection_header_probe(
|
||||
bytes: &[u8],
|
||||
file_extension_hint: Option<&str>,
|
||||
container_profile: Option<&SmpContainerProfile>,
|
||||
) -> Option<SmpSaveTaggedCollectionHeaderProbe> {
|
||||
parse_save_tagged_collection_header_probe(
|
||||
bytes,
|
||||
file_extension_hint,
|
||||
container_profile,
|
||||
0x00005209,
|
||||
0x0000520a,
|
||||
0x0000520b,
|
||||
"save-chairman-profile-tagged-header-counts",
|
||||
"scenario-save-chairman-profile-header-counts",
|
||||
|header| {
|
||||
header.direct_collection_flag == 1
|
||||
&& header.live_id_bound >= 1
|
||||
&& header.live_id_bound <= 0x80
|
||||
&& header.live_record_count <= header.live_id_bound
|
||||
&& header.direct_record_stride >= 0x100
|
||||
&& header.direct_record_stride <= 0x400
|
||||
},
|
||||
vec![
|
||||
"save-side chairman/profile collection uses tagged header family 0x5209/0x520a/0x520b".to_string(),
|
||||
"current grounded claim is header-only: active/live record counts are save-native, but per-profile payload is not yet reconstructed from raw save".to_string(),
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct IndexedCollectionHeaderSummary {
|
||||
metadata_tag_offset: usize,
|
||||
records_tag_offset: usize,
|
||||
close_tag_offset: usize,
|
||||
direct_collection_flag: u32,
|
||||
direct_record_stride: u32,
|
||||
live_id_bound: u32,
|
||||
live_record_count: u32,
|
||||
header_words: [u32; INDEXED_COLLECTION_SERIALIZED_HEADER_DWORD_COUNT],
|
||||
}
|
||||
|
||||
fn parse_save_tagged_collection_header_probe(
|
||||
bytes: &[u8],
|
||||
file_extension_hint: Option<&str>,
|
||||
container_profile: Option<&SmpContainerProfile>,
|
||||
metadata_tag: u32,
|
||||
records_tag: u32,
|
||||
close_tag: u32,
|
||||
source_kind: &str,
|
||||
semantic_family: &str,
|
||||
predicate: impl Fn(IndexedCollectionHeaderSummary) -> bool,
|
||||
mut evidence: Vec<String>,
|
||||
) -> Option<SmpSaveTaggedCollectionHeaderProbe> {
|
||||
if file_extension_hint != Some("gms") {
|
||||
return None;
|
||||
}
|
||||
let profile = container_profile?;
|
||||
if !matches!(
|
||||
profile.profile_family.as_str(),
|
||||
"rt3-classic-save-container-v1"
|
||||
| "rt3-105-save-container-v1"
|
||||
| "rt3-105-scenario-save-container-v1"
|
||||
| "rt3-105-alt-save-container-v1"
|
||||
) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let metadata_offsets = find_u32_le_offsets(bytes, metadata_tag);
|
||||
let records_offsets = find_u32_le_offsets(bytes, records_tag);
|
||||
let close_offsets = find_u32_le_offsets(bytes, close_tag);
|
||||
|
||||
let summary = metadata_offsets
|
||||
.into_iter()
|
||||
.filter_map(|metadata_tag_offset| {
|
||||
let records_tag_offset = records_offsets
|
||||
.iter()
|
||||
.copied()
|
||||
.find(|offset| *offset > metadata_tag_offset)?;
|
||||
let close_tag_offset = close_offsets
|
||||
.iter()
|
||||
.copied()
|
||||
.find(|offset| *offset > records_tag_offset)?;
|
||||
let payload = bytes.get(metadata_tag_offset + 4..records_tag_offset)?;
|
||||
if payload.len() < INDEXED_COLLECTION_SERIALIZED_HEADER_LEN {
|
||||
return None;
|
||||
}
|
||||
|
||||
let header_words = (0..INDEXED_COLLECTION_SERIALIZED_HEADER_DWORD_COUNT)
|
||||
.map(|index| read_u32_at(payload, index * 4))
|
||||
.collect::<Option<Vec<_>>>()?;
|
||||
let header_words: [u32; INDEXED_COLLECTION_SERIALIZED_HEADER_DWORD_COUNT] =
|
||||
header_words.try_into().ok()?;
|
||||
let summary = IndexedCollectionHeaderSummary {
|
||||
metadata_tag_offset,
|
||||
records_tag_offset,
|
||||
close_tag_offset,
|
||||
direct_collection_flag: header_words[0],
|
||||
direct_record_stride: header_words[1],
|
||||
live_id_bound: header_words[4],
|
||||
live_record_count: header_words[5],
|
||||
header_words,
|
||||
};
|
||||
predicate(summary).then_some(summary)
|
||||
})
|
||||
.next()?;
|
||||
|
||||
evidence.push(format!(
|
||||
"exact little-endian u32 tag family 0x{metadata_tag:04x}/0x{records_tag:04x}/0x{close_tag:04x} appears at file offsets 0x{:x}/0x{:x}/0x{:x}",
|
||||
summary.metadata_tag_offset, summary.records_tag_offset, summary.close_tag_offset
|
||||
));
|
||||
evidence.push(format!(
|
||||
"header words report direct_collection_flag={}, direct_record_stride=0x{:x}, live_id_bound={}, live_record_count={}",
|
||||
summary.direct_collection_flag,
|
||||
summary.direct_record_stride,
|
||||
summary.live_id_bound,
|
||||
summary.live_record_count
|
||||
));
|
||||
|
||||
Some(SmpSaveTaggedCollectionHeaderProbe {
|
||||
profile_family: profile.profile_family.clone(),
|
||||
source_kind: source_kind.to_string(),
|
||||
semantic_family: semantic_family.to_string(),
|
||||
metadata_tag_offset: summary.metadata_tag_offset,
|
||||
records_tag_offset: summary.records_tag_offset,
|
||||
close_tag_offset: summary.close_tag_offset,
|
||||
direct_collection_flag: summary.direct_collection_flag,
|
||||
direct_collection_flag_hex: format!("0x{:08x}", summary.direct_collection_flag),
|
||||
direct_record_stride: summary.direct_record_stride,
|
||||
direct_record_stride_hex: format!("0x{:08x}", summary.direct_record_stride),
|
||||
live_id_bound: summary.live_id_bound,
|
||||
live_id_bound_hex: format!("0x{:08x}", summary.live_id_bound),
|
||||
live_record_count: summary.live_record_count,
|
||||
live_record_count_hex: format!("0x{:08x}", summary.live_record_count),
|
||||
header_words: summary.header_words.to_vec(),
|
||||
header_hex_words: summary
|
||||
.header_words
|
||||
.iter()
|
||||
.map(|word| format!("0x{word:08x}"))
|
||||
.collect(),
|
||||
evidence,
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_rt3_105_save_name_table_probe(
|
||||
bytes: &[u8],
|
||||
file_extension_hint: Option<&str>,
|
||||
|
|
@ -13453,18 +13699,71 @@ mod tests {
|
|||
chairman_role_gate_bytes: vec![2, 1, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
evidence: vec![],
|
||||
});
|
||||
report.save_company_collection_header_probe = Some(SmpSaveTaggedCollectionHeaderProbe {
|
||||
profile_family: "rt3-105-save-container-v1".to_string(),
|
||||
source_kind: "save-company-tagged-header-counts".to_string(),
|
||||
semantic_family: "scenario-save-company-header-counts".to_string(),
|
||||
metadata_tag_offset: 0x1000,
|
||||
records_tag_offset: 0x1100,
|
||||
close_tag_offset: 0x1200,
|
||||
direct_collection_flag: 1,
|
||||
direct_collection_flag_hex: "0x00000001".to_string(),
|
||||
direct_record_stride: 0x7684,
|
||||
direct_record_stride_hex: "0x00007684".to_string(),
|
||||
live_id_bound: 5,
|
||||
live_id_bound_hex: "0x00000005".to_string(),
|
||||
live_record_count: 1,
|
||||
live_record_count_hex: "0x00000001".to_string(),
|
||||
header_words: vec![1, 0x7684, 5, 5, 5, 1],
|
||||
header_hex_words: vec![
|
||||
"0x00000001".to_string(),
|
||||
"0x00007684".to_string(),
|
||||
"0x00000005".to_string(),
|
||||
"0x00000005".to_string(),
|
||||
"0x00000005".to_string(),
|
||||
"0x00000001".to_string(),
|
||||
],
|
||||
evidence: vec![],
|
||||
});
|
||||
report.save_chairman_profile_collection_header_probe =
|
||||
Some(SmpSaveTaggedCollectionHeaderProbe {
|
||||
profile_family: "rt3-105-save-container-v1".to_string(),
|
||||
source_kind: "save-chairman-profile-tagged-header-counts".to_string(),
|
||||
semantic_family: "scenario-save-chairman-profile-header-counts".to_string(),
|
||||
metadata_tag_offset: 0x2000,
|
||||
records_tag_offset: 0x2100,
|
||||
close_tag_offset: 0x2200,
|
||||
direct_collection_flag: 1,
|
||||
direct_collection_flag_hex: "0x00000001".to_string(),
|
||||
direct_record_stride: 0x1d5,
|
||||
direct_record_stride_hex: "0x000001d5".to_string(),
|
||||
live_id_bound: 0x32,
|
||||
live_id_bound_hex: "0x00000032".to_string(),
|
||||
live_record_count: 2,
|
||||
live_record_count_hex: "0x00000002".to_string(),
|
||||
header_words: vec![1, 0x1d5, 0x32, 0x14, 0x32, 2],
|
||||
header_hex_words: vec![
|
||||
"0x00000001".to_string(),
|
||||
"0x000001d5".to_string(),
|
||||
"0x00000032".to_string(),
|
||||
"0x00000014".to_string(),
|
||||
"0x00000032".to_string(),
|
||||
"0x00000002".to_string(),
|
||||
],
|
||||
evidence: vec![],
|
||||
});
|
||||
|
||||
let slice = load_save_slice_from_report(&report).expect("save slice");
|
||||
|
||||
let company_roster = slice.company_roster.expect("selection-only company roster");
|
||||
assert_eq!(company_roster.observed_entry_count, 0);
|
||||
assert_eq!(company_roster.observed_entry_count, 1);
|
||||
assert_eq!(company_roster.selected_company_id, Some(1));
|
||||
assert!(company_roster.entries.is_empty());
|
||||
|
||||
let chairman_table = slice
|
||||
.chairman_profile_table
|
||||
.expect("selection-only chairman table");
|
||||
assert_eq!(chairman_table.observed_entry_count, 0);
|
||||
assert_eq!(chairman_table.observed_entry_count, 2);
|
||||
assert_eq!(chairman_table.selected_chairman_profile_id, Some(9));
|
||||
assert!(chairman_table.entries.is_empty());
|
||||
|
||||
|
|
@ -13486,6 +13785,91 @@ mod tests {
|
|||
.iter()
|
||||
.any(|note| note.contains("campaign_override_flag=1"))
|
||||
);
|
||||
assert!(
|
||||
slice
|
||||
.notes
|
||||
.iter()
|
||||
.any(|note| note.contains("tagged company header reports live_record_count=1"))
|
||||
);
|
||||
assert!(slice.notes.iter().any(|note| {
|
||||
note.contains("tagged chairman/profile header reports live_record_count=2")
|
||||
}));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parses_company_tagged_collection_header_probe_from_exact_u32_tags() {
|
||||
let mut bytes = vec![0u8; 0x400];
|
||||
let metadata_tag_offset = 0x40usize;
|
||||
let records_tag_offset = 0x140usize;
|
||||
let close_tag_offset = 0x180usize;
|
||||
bytes[metadata_tag_offset..metadata_tag_offset + 4]
|
||||
.copy_from_slice(&0x000061a9u32.to_le_bytes());
|
||||
bytes[records_tag_offset..records_tag_offset + 4]
|
||||
.copy_from_slice(&0x000061aau32.to_le_bytes());
|
||||
bytes[close_tag_offset..close_tag_offset + 4].copy_from_slice(&0x000061abu32.to_le_bytes());
|
||||
let header_words = [
|
||||
1u32, 0x7684, 5, 5, 5, 1, 1, 1, 0, 0, 1, 1, 1, 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 probe = parse_save_company_collection_header_probe(
|
||||
&bytes,
|
||||
Some("gms"),
|
||||
Some(&SmpContainerProfile {
|
||||
profile_family: "rt3-105-save-container-v1".to_string(),
|
||||
profile_evidence: vec![],
|
||||
is_known_profile: true,
|
||||
}),
|
||||
)
|
||||
.expect("company header probe should parse");
|
||||
|
||||
assert_eq!(probe.metadata_tag_offset, metadata_tag_offset);
|
||||
assert_eq!(probe.records_tag_offset, records_tag_offset);
|
||||
assert_eq!(probe.close_tag_offset, close_tag_offset);
|
||||
assert_eq!(probe.direct_record_stride, 0x7684);
|
||||
assert_eq!(probe.live_id_bound, 5);
|
||||
assert_eq!(probe.live_record_count, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parses_chairman_profile_tagged_collection_header_probe_from_exact_u32_tags() {
|
||||
let mut bytes = vec![0u8; 0x400];
|
||||
let metadata_tag_offset = 0x40usize;
|
||||
let records_tag_offset = 0x140usize;
|
||||
let close_tag_offset = 0x180usize;
|
||||
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 header_words = [
|
||||
1u32, 0x1d5, 0x32, 0x14, 0x32, 2, 1, 1, 0, 0, 1, 1, 1, 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 probe = parse_save_chairman_profile_collection_header_probe(
|
||||
&bytes,
|
||||
Some("gms"),
|
||||
Some(&SmpContainerProfile {
|
||||
profile_family: "rt3-105-save-container-v1".to_string(),
|
||||
profile_evidence: vec![],
|
||||
is_known_profile: true,
|
||||
}),
|
||||
)
|
||||
.expect("chairman profile header probe should parse");
|
||||
|
||||
assert_eq!(probe.metadata_tag_offset, metadata_tag_offset);
|
||||
assert_eq!(probe.records_tag_offset, records_tag_offset);
|
||||
assert_eq!(probe.close_tag_offset, close_tag_offset);
|
||||
assert_eq!(probe.direct_record_stride, 0x1d5);
|
||||
assert_eq!(probe.live_id_bound, 0x32);
|
||||
assert_eq!(probe.live_record_count, 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue