Probe save-side region queued notice nodes
This commit is contained in:
parent
564b8b6641
commit
dc7215f720
4 changed files with 275 additions and 6 deletions
|
|
@ -28,7 +28,8 @@ use rrt_runtime::{
|
||||||
extract_pk4_entry_file, inspect_campaign_exe_file, inspect_cargo_economy_sources_with_bindings,
|
extract_pk4_entry_file, inspect_campaign_exe_file, inspect_cargo_economy_sources_with_bindings,
|
||||||
inspect_cargo_skin_pk4, inspect_cargo_types_dir, inspect_pk4_file,
|
inspect_cargo_skin_pk4, inspect_cargo_types_dir, inspect_pk4_file,
|
||||||
inspect_save_company_and_chairman_analysis_file,
|
inspect_save_company_and_chairman_analysis_file,
|
||||||
inspect_save_placed_structure_dynamic_side_buffer_file, inspect_smp_file,
|
inspect_save_placed_structure_dynamic_side_buffer_file,
|
||||||
|
inspect_save_region_queued_notice_records_file, inspect_smp_file,
|
||||||
inspect_unclassified_save_collection_headers_file, inspect_win_file,
|
inspect_unclassified_save_collection_headers_file, inspect_win_file,
|
||||||
load_runtime_snapshot_document, load_runtime_state_import, load_save_slice_file,
|
load_runtime_snapshot_document, load_runtime_state_import, load_save_slice_file,
|
||||||
project_save_slice_to_runtime_state_import, save_runtime_overlay_import_document,
|
project_save_slice_to_runtime_state_import, save_runtime_overlay_import_document,
|
||||||
|
|
@ -132,6 +133,9 @@ enum Command {
|
||||||
RuntimeInspectSaveCompanyChairman {
|
RuntimeInspectSaveCompanyChairman {
|
||||||
smp_path: PathBuf,
|
smp_path: PathBuf,
|
||||||
},
|
},
|
||||||
|
RuntimeInspectSaveRegionQueuedNoticeRecords {
|
||||||
|
smp_path: PathBuf,
|
||||||
|
},
|
||||||
RuntimeInspectPlacedStructureDynamicSideBuffer {
|
RuntimeInspectPlacedStructureDynamicSideBuffer {
|
||||||
smp_path: PathBuf,
|
smp_path: PathBuf,
|
||||||
},
|
},
|
||||||
|
|
@ -861,6 +865,9 @@ fn real_main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
Command::RuntimeInspectSaveCompanyChairman { smp_path } => {
|
Command::RuntimeInspectSaveCompanyChairman { smp_path } => {
|
||||||
run_runtime_inspect_save_company_chairman(&smp_path)?;
|
run_runtime_inspect_save_company_chairman(&smp_path)?;
|
||||||
}
|
}
|
||||||
|
Command::RuntimeInspectSaveRegionQueuedNoticeRecords { smp_path } => {
|
||||||
|
run_runtime_inspect_save_region_queued_notice_records(&smp_path)?;
|
||||||
|
}
|
||||||
Command::RuntimeInspectPlacedStructureDynamicSideBuffer { smp_path } => {
|
Command::RuntimeInspectPlacedStructureDynamicSideBuffer { smp_path } => {
|
||||||
run_runtime_inspect_placed_structure_dynamic_side_buffer(&smp_path)?;
|
run_runtime_inspect_placed_structure_dynamic_side_buffer(&smp_path)?;
|
||||||
}
|
}
|
||||||
|
|
@ -1070,6 +1077,14 @@ fn parse_command() -> Result<Command, Box<dyn std::error::Error>> {
|
||||||
smp_path: PathBuf::from(path),
|
smp_path: PathBuf::from(path),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
[command, subcommand, path]
|
||||||
|
if command == "runtime"
|
||||||
|
&& subcommand == "inspect-save-region-queued-notice-records" =>
|
||||||
|
{
|
||||||
|
Ok(Command::RuntimeInspectSaveRegionQueuedNoticeRecords {
|
||||||
|
smp_path: PathBuf::from(path),
|
||||||
|
})
|
||||||
|
}
|
||||||
[command, subcommand, path]
|
[command, subcommand, path]
|
||||||
if command == "runtime"
|
if command == "runtime"
|
||||||
&& subcommand == "inspect-placed-structure-dynamic-side-buffer" =>
|
&& subcommand == "inspect-placed-structure-dynamic-side-buffer" =>
|
||||||
|
|
@ -1288,7 +1303,7 @@ fn parse_command() -> Result<Command, Box<dyn std::error::Error>> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
_ => Err(
|
_ => Err(
|
||||||
"usage: rrt-cli [validate [repo-root] | finance eval <snapshot.json> | finance diff <left.json> <right.json> | runtime validate-fixture <fixture.json> | runtime summarize-fixture <fixture.json> | runtime export-fixture-state <fixture.json> <snapshot.json> | runtime diff-state <left.json> <right.json> | runtime summarize-state <snapshot.json> | runtime import-state <input.json> <snapshot.json> | runtime inspect-smp <file.smp> | runtime summarize-save-load <file.smp> | runtime load-save-slice <file.smp> | runtime inspect-save-company-chairman <file.smp> | runtime inspect-placed-structure-dynamic-side-buffer <file.smp> | runtime inspect-unclassified-save-collections <file.smp> | runtime import-save-state <file.smp> <snapshot.json> | runtime export-save-slice <file.smp> <save-slice.json> | runtime export-overlay-import <snapshot.json> <save-slice.json> <overlay-import.json> | runtime inspect-pk4 <file.pk4> | runtime inspect-cargo-types <CargoTypes-dir> | runtime inspect-cargo-skins <Cargo106.PK4> | runtime inspect-cargo-economy-sources <CargoTypes-dir> <Cargo106.PK4> | runtime inspect-cargo-production-selector <CargoTypes-dir> <Cargo106.PK4> | runtime inspect-cargo-price-selector <CargoTypes-dir> <Cargo106.PK4> | runtime inspect-win <file.win> | runtime extract-pk4-entry <file.pk4> <entry-name> <output-path> | runtime inspect-campaign-exe <RT3.exe> | runtime compare-classic-profile <save1.gms> <save2.gms> [saveN.gms...] | runtime compare-105-profile <save1.gms> <save2.gms> [saveN.gms...] | runtime compare-candidate-table <file1> <file2> [fileN...] | runtime compare-recipe-book-lines <file1> <file2> [fileN...] | runtime compare-setup-payload-core <file1> <file2> [fileN...] | runtime compare-setup-launch-payload <file1> <file2> [fileN...] | runtime compare-post-special-conditions-scalars <file1> <file2> [fileN...] | runtime scan-candidate-table-headers <root-dir> | runtime scan-special-conditions <root-dir> | runtime scan-aligned-runtime-rule-band <root-dir> | runtime scan-post-special-conditions-scalars <root-dir> | runtime scan-post-special-conditions-tail <root-dir> | runtime scan-recipe-book-lines <root-dir> | runtime export-profile-block <save.gms> <profile.json>]"
|
"usage: rrt-cli [validate [repo-root] | finance eval <snapshot.json> | finance diff <left.json> <right.json> | runtime validate-fixture <fixture.json> | runtime summarize-fixture <fixture.json> | runtime export-fixture-state <fixture.json> <snapshot.json> | runtime diff-state <left.json> <right.json> | runtime summarize-state <snapshot.json> | runtime import-state <input.json> <snapshot.json> | runtime inspect-smp <file.smp> | runtime summarize-save-load <file.smp> | runtime load-save-slice <file.smp> | runtime inspect-save-company-chairman <file.smp> | runtime inspect-save-region-queued-notice-records <file.smp> | runtime inspect-placed-structure-dynamic-side-buffer <file.smp> | runtime inspect-unclassified-save-collections <file.smp> | runtime import-save-state <file.smp> <snapshot.json> | runtime export-save-slice <file.smp> <save-slice.json> | runtime export-overlay-import <snapshot.json> <save-slice.json> <overlay-import.json> | runtime inspect-pk4 <file.pk4> | runtime inspect-cargo-types <CargoTypes-dir> | runtime inspect-cargo-skins <Cargo106.PK4> | runtime inspect-cargo-economy-sources <CargoTypes-dir> <Cargo106.PK4> | runtime inspect-cargo-production-selector <CargoTypes-dir> <Cargo106.PK4> | runtime inspect-cargo-price-selector <CargoTypes-dir> <Cargo106.PK4> | runtime inspect-win <file.win> | runtime extract-pk4-entry <file.pk4> <entry-name> <output-path> | runtime inspect-campaign-exe <RT3.exe> | runtime compare-classic-profile <save1.gms> <save2.gms> [saveN.gms...] | runtime compare-105-profile <save1.gms> <save2.gms> [saveN.gms...] | runtime compare-candidate-table <file1> <file2> [fileN...] | runtime compare-recipe-book-lines <file1> <file2> [fileN...] | runtime compare-setup-payload-core <file1> <file2> [fileN...] | runtime compare-setup-launch-payload <file1> <file2> [fileN...] | runtime compare-post-special-conditions-scalars <file1> <file2> [fileN...] | runtime scan-candidate-table-headers <root-dir> | runtime scan-special-conditions <root-dir> | runtime scan-aligned-runtime-rule-band <root-dir> | runtime scan-post-special-conditions-scalars <root-dir> | runtime scan-post-special-conditions-tail <root-dir> | runtime scan-recipe-book-lines <root-dir> | runtime export-profile-block <save.gms> <profile.json>]"
|
||||||
.into(),
|
.into(),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
@ -1529,6 +1544,16 @@ fn run_runtime_inspect_save_company_chairman(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn run_runtime_inspect_save_region_queued_notice_records(
|
||||||
|
smp_path: &Path,
|
||||||
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
println!(
|
||||||
|
"{}",
|
||||||
|
serde_json::to_string_pretty(&inspect_save_region_queued_notice_records_file(smp_path)?)?
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn run_runtime_inspect_placed_structure_dynamic_side_buffer(
|
fn run_runtime_inspect_placed_structure_dynamic_side_buffer(
|
||||||
smp_path: &Path,
|
smp_path: &Path,
|
||||||
) -> Result<(), Box<dyn std::error::Error>> {
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
|
|
||||||
|
|
@ -122,14 +122,15 @@ pub use smp::{
|
||||||
SmpSavePlacedStructureDynamicSideBufferAlignmentProbe,
|
SmpSavePlacedStructureDynamicSideBufferAlignmentProbe,
|
||||||
SmpSavePlacedStructureDynamicSideBufferNamePairSummary,
|
SmpSavePlacedStructureDynamicSideBufferNamePairSummary,
|
||||||
SmpSavePlacedStructureDynamicSideBufferPrefixPatternSummary,
|
SmpSavePlacedStructureDynamicSideBufferPrefixPatternSummary,
|
||||||
SmpSavePlacedStructureDynamicSideBufferProbe, SmpSaveScalarCandidate,
|
SmpSavePlacedStructureDynamicSideBufferProbe, SmpSaveRegionQueuedNoticeRecordProbe,
|
||||||
SmpSaveTaggedCollectionHeaderProbe, SmpSaveWorldEconomicTuningProbe,
|
SmpSaveScalarCandidate, SmpSaveTaggedCollectionHeaderProbe, SmpSaveWorldEconomicTuningProbe,
|
||||||
SmpSaveWorldFinanceNeighborhoodProbe, SmpSaveWorldIssue37Probe,
|
SmpSaveWorldFinanceNeighborhoodProbe, SmpSaveWorldIssue37Probe,
|
||||||
SmpSaveWorldSelectionRoleAnalysis, SmpSaveWorldSelectionRoleAnalysisEntry,
|
SmpSaveWorldSelectionRoleAnalysis, SmpSaveWorldSelectionRoleAnalysisEntry,
|
||||||
SmpSecondaryVariantProbe, SmpSharedHeader, SmpSpecialConditionEntry, SmpSpecialConditionsProbe,
|
SmpSecondaryVariantProbe, SmpSharedHeader, SmpSpecialConditionEntry, SmpSpecialConditionsProbe,
|
||||||
inspect_save_company_and_chairman_analysis_bytes,
|
inspect_save_company_and_chairman_analysis_bytes,
|
||||||
inspect_save_company_and_chairman_analysis_file,
|
inspect_save_company_and_chairman_analysis_file,
|
||||||
inspect_save_placed_structure_dynamic_side_buffer_file, inspect_smp_bytes, inspect_smp_file,
|
inspect_save_placed_structure_dynamic_side_buffer_file,
|
||||||
|
inspect_save_region_queued_notice_records_file, inspect_smp_bytes, inspect_smp_file,
|
||||||
inspect_unclassified_save_collection_headers_file, load_save_slice_file,
|
inspect_unclassified_save_collection_headers_file, load_save_slice_file,
|
||||||
load_save_slice_from_report,
|
load_save_slice_from_report,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -155,6 +155,9 @@ const SAVE_REGION_COLLECTION_DIRECTORY_ENTRY_DWORD_COUNT: usize = 3;
|
||||||
const SAVE_REGION_RECORD_NAME_TAG: u16 = 0x55f1;
|
const SAVE_REGION_RECORD_NAME_TAG: u16 = 0x55f1;
|
||||||
const SAVE_REGION_RECORD_POLICY_TAG: u16 = 0x55f2;
|
const SAVE_REGION_RECORD_POLICY_TAG: u16 = 0x55f2;
|
||||||
const SAVE_REGION_RECORD_PROFILE_TAG: u16 = 0x55f3;
|
const SAVE_REGION_RECORD_PROFILE_TAG: u16 = 0x55f3;
|
||||||
|
const SAVE_REGION_QUEUED_NOTICE_PAYLOAD_SEED: u32 = 0x005c87a8;
|
||||||
|
const SAVE_REGION_QUEUED_NOTICE_NODE_KIND: u32 = 7;
|
||||||
|
const SAVE_REGION_QUEUED_NOTICE_NODE_LEN: usize = 0x20;
|
||||||
const PACKED_EVENT_RECORDS_SYNTHETIC_MAGIC: &[u8; 8] = b"RPEVT001";
|
const PACKED_EVENT_RECORDS_SYNTHETIC_MAGIC: &[u8; 8] = b"RPEVT001";
|
||||||
const PACKED_EVENT_RECORD_SYNTHETIC_MAGIC: &[u8; 4] = b"RPE1";
|
const PACKED_EVENT_RECORD_SYNTHETIC_MAGIC: &[u8; 4] = b"RPE1";
|
||||||
const PACKED_EVENT_RECORD_TEMPLATE_SYNTHETIC_MAGIC: &[u8; 4] = b"RPT1";
|
const PACKED_EVENT_RECORD_TEMPLATE_SYNTHETIC_MAGIC: &[u8; 4] = b"RPT1";
|
||||||
|
|
@ -1729,6 +1732,40 @@ pub struct SmpSaveRegionRecordTripletProbe {
|
||||||
pub evidence: Vec<String>,
|
pub evidence: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
pub struct SmpSaveRegionQueuedNoticeRecordEntryProbe {
|
||||||
|
pub node_base_offset: usize,
|
||||||
|
pub payload_seed_offset: usize,
|
||||||
|
pub next_link_raw: u32,
|
||||||
|
pub next_link_raw_hex: String,
|
||||||
|
pub payload_seed_dword: u32,
|
||||||
|
pub payload_seed_dword_hex: String,
|
||||||
|
pub kind: u32,
|
||||||
|
pub kind_hex: String,
|
||||||
|
pub promotion_latch_dword: u32,
|
||||||
|
pub promotion_latch_dword_hex: String,
|
||||||
|
pub region_id: u32,
|
||||||
|
pub region_id_hex: String,
|
||||||
|
pub amount: u32,
|
||||||
|
pub amount_hex: String,
|
||||||
|
pub trailing_sentinel_i32_0: i32,
|
||||||
|
pub trailing_sentinel_i32_0_hex: String,
|
||||||
|
pub trailing_sentinel_i32_1: i32,
|
||||||
|
pub trailing_sentinel_i32_1_hex: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
pub struct SmpSaveRegionQueuedNoticeRecordProbe {
|
||||||
|
pub profile_family: String,
|
||||||
|
pub source_kind: String,
|
||||||
|
pub semantic_family: String,
|
||||||
|
pub payload_seed_dword: u32,
|
||||||
|
pub payload_seed_dword_hex: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub entries: Vec<SmpSaveRegionQueuedNoticeRecordEntryProbe>,
|
||||||
|
pub evidence: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
pub struct SmpSavePlacedStructureRecordTripletEntryProbe {
|
pub struct SmpSavePlacedStructureRecordTripletEntryProbe {
|
||||||
pub record_index: usize,
|
pub record_index: usize,
|
||||||
|
|
@ -2825,6 +2862,8 @@ pub struct SmpSaveCompanyChairmanAnalysisReport {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub region_record_triplets: Option<SmpSaveRegionRecordTripletProbe>,
|
pub region_record_triplets: Option<SmpSaveRegionRecordTripletProbe>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
pub region_queued_notice_records: Option<SmpSaveRegionQueuedNoticeRecordProbe>,
|
||||||
|
#[serde(default)]
|
||||||
pub placed_structure_collection_header: Option<SmpSaveTaggedCollectionHeaderProbe>,
|
pub placed_structure_collection_header: Option<SmpSaveTaggedCollectionHeaderProbe>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub placed_structure_record_triplets: Option<SmpSavePlacedStructureRecordTripletProbe>,
|
pub placed_structure_record_triplets: Option<SmpSavePlacedStructureRecordTripletProbe>,
|
||||||
|
|
@ -3098,6 +3137,8 @@ pub struct SmpInspectionReport {
|
||||||
pub save_train_collection_directory_probe: Option<SmpSaveTrainCollectionDirectoryProbe>,
|
pub save_train_collection_directory_probe: Option<SmpSaveTrainCollectionDirectoryProbe>,
|
||||||
pub save_region_collection_header_probe: Option<SmpSaveTaggedCollectionHeaderProbe>,
|
pub save_region_collection_header_probe: Option<SmpSaveTaggedCollectionHeaderProbe>,
|
||||||
pub save_region_record_triplet_probe: Option<SmpSaveRegionRecordTripletProbe>,
|
pub save_region_record_triplet_probe: Option<SmpSaveRegionRecordTripletProbe>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub save_region_queued_notice_record_probe: Option<SmpSaveRegionQueuedNoticeRecordProbe>,
|
||||||
pub save_placed_structure_collection_header_probe: Option<SmpSaveTaggedCollectionHeaderProbe>,
|
pub save_placed_structure_collection_header_probe: Option<SmpSaveTaggedCollectionHeaderProbe>,
|
||||||
pub save_placed_structure_record_triplet_probe:
|
pub save_placed_structure_record_triplet_probe:
|
||||||
Option<SmpSavePlacedStructureRecordTripletProbe>,
|
Option<SmpSavePlacedStructureRecordTripletProbe>,
|
||||||
|
|
@ -3239,6 +3280,41 @@ pub fn inspect_save_placed_structure_dynamic_side_buffer_file(
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn inspect_save_region_queued_notice_records_file(
|
||||||
|
path: &Path,
|
||||||
|
) -> Result<Option<SmpSaveRegionQueuedNoticeRecordProbe>, Box<dyn std::error::Error>> {
|
||||||
|
let bytes = fs::read(path)?;
|
||||||
|
let file_extension_hint = path
|
||||||
|
.extension()
|
||||||
|
.and_then(|extension| extension.to_str())
|
||||||
|
.map(|extension| extension.to_ascii_lowercase());
|
||||||
|
let shared_header = parse_shared_header(&bytes);
|
||||||
|
let header_variant_probe = shared_header.as_ref().map(classify_header_variant_probe);
|
||||||
|
let first_ascii_run = find_first_ascii_run(&bytes);
|
||||||
|
let early_content_probe = first_ascii_run
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|ascii_run| probe_early_content_layout(&bytes, ascii_run));
|
||||||
|
let secondary_variant_probe = early_content_probe
|
||||||
|
.as_ref()
|
||||||
|
.and_then(classify_secondary_variant_probe);
|
||||||
|
let container_profile = classify_container_profile(
|
||||||
|
file_extension_hint.as_deref(),
|
||||||
|
header_variant_probe.as_ref(),
|
||||||
|
secondary_variant_probe.as_ref(),
|
||||||
|
);
|
||||||
|
let save_region_collection_header_probe = parse_save_region_collection_header_probe(
|
||||||
|
&bytes,
|
||||||
|
file_extension_hint.as_deref(),
|
||||||
|
container_profile.as_ref(),
|
||||||
|
);
|
||||||
|
Ok(parse_save_region_queued_notice_record_probe(
|
||||||
|
&bytes,
|
||||||
|
file_extension_hint.as_deref(),
|
||||||
|
container_profile.as_ref(),
|
||||||
|
save_region_collection_header_probe.as_ref(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn inspect_smp_bytes(bytes: &[u8]) -> SmpInspectionReport {
|
pub fn inspect_smp_bytes(bytes: &[u8]) -> SmpInspectionReport {
|
||||||
inspect_bundle_bytes(bytes, None)
|
inspect_bundle_bytes(bytes, None)
|
||||||
}
|
}
|
||||||
|
|
@ -3644,6 +3720,17 @@ pub fn inspect_save_company_and_chairman_analysis_bytes(
|
||||||
let world_finance_neighborhood = report.save_world_finance_neighborhood_probe.clone();
|
let world_finance_neighborhood = report.save_world_finance_neighborhood_probe.clone();
|
||||||
let train_collection_directory = report.save_train_collection_directory_probe.clone();
|
let train_collection_directory = report.save_train_collection_directory_probe.clone();
|
||||||
let region_record_triplets = report.save_region_record_triplet_probe.clone();
|
let region_record_triplets = report.save_region_record_triplet_probe.clone();
|
||||||
|
let region_queued_notice_records = report
|
||||||
|
.save_region_queued_notice_record_probe
|
||||||
|
.clone()
|
||||||
|
.or_else(|| {
|
||||||
|
parse_save_region_queued_notice_record_probe(
|
||||||
|
bytes,
|
||||||
|
report.file_extension_hint.as_deref(),
|
||||||
|
report.container_profile.as_ref(),
|
||||||
|
report.save_region_collection_header_probe.as_ref(),
|
||||||
|
)
|
||||||
|
});
|
||||||
let placed_structure_record_triplets =
|
let placed_structure_record_triplets =
|
||||||
report.save_placed_structure_record_triplet_probe.clone();
|
report.save_placed_structure_record_triplet_probe.clone();
|
||||||
let placed_structure_dynamic_side_buffer = report
|
let placed_structure_dynamic_side_buffer = report
|
||||||
|
|
@ -4003,6 +4090,18 @@ pub fn inspect_save_company_and_chairman_analysis_bytes(
|
||||||
})
|
})
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
if let Some(queue_probe) = region_queued_notice_records.as_ref() {
|
||||||
|
notes.push(format!(
|
||||||
|
"Region analysis now also exports {} queued kind-7 notice nodes with payload seed {}: first region id={} amount={} promotion={} tails={}/{}.",
|
||||||
|
queue_probe.entries.len(),
|
||||||
|
queue_probe.payload_seed_dword_hex,
|
||||||
|
queue_probe.entries[0].region_id,
|
||||||
|
queue_probe.entries[0].amount,
|
||||||
|
queue_probe.entries[0].promotion_latch_dword_hex,
|
||||||
|
queue_probe.entries[0].trailing_sentinel_i32_0_hex,
|
||||||
|
queue_probe.entries[0].trailing_sentinel_i32_1_hex
|
||||||
|
));
|
||||||
|
}
|
||||||
if let Some(header) = report
|
if let Some(header) = report
|
||||||
.save_placed_structure_collection_header_probe
|
.save_placed_structure_collection_header_probe
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
|
@ -4110,6 +4209,7 @@ pub fn inspect_save_company_and_chairman_analysis_bytes(
|
||||||
train_collection_directory,
|
train_collection_directory,
|
||||||
region_collection_header: report.save_region_collection_header_probe.clone(),
|
region_collection_header: report.save_region_collection_header_probe.clone(),
|
||||||
region_record_triplets,
|
region_record_triplets,
|
||||||
|
region_queued_notice_records,
|
||||||
placed_structure_collection_header: report
|
placed_structure_collection_header: report
|
||||||
.save_placed_structure_collection_header_probe
|
.save_placed_structure_collection_header_probe
|
||||||
.clone(),
|
.clone(),
|
||||||
|
|
@ -8131,6 +8231,12 @@ fn inspect_bundle_bytes(bytes: &[u8], file_extension_hint: Option<String>) -> Sm
|
||||||
);
|
);
|
||||||
let save_region_record_triplet_probe =
|
let save_region_record_triplet_probe =
|
||||||
parse_save_region_record_triplet_probe(bytes, save_region_collection_header_probe.as_ref());
|
parse_save_region_record_triplet_probe(bytes, save_region_collection_header_probe.as_ref());
|
||||||
|
let save_region_queued_notice_record_probe = parse_save_region_queued_notice_record_probe(
|
||||||
|
bytes,
|
||||||
|
file_extension_hint.as_deref(),
|
||||||
|
container_profile.as_ref(),
|
||||||
|
save_region_collection_header_probe.as_ref(),
|
||||||
|
);
|
||||||
let save_placed_structure_collection_header_probe =
|
let save_placed_structure_collection_header_probe =
|
||||||
parse_save_placed_structure_collection_header_probe(
|
parse_save_placed_structure_collection_header_probe(
|
||||||
bytes,
|
bytes,
|
||||||
|
|
@ -8331,6 +8437,7 @@ fn inspect_bundle_bytes(bytes: &[u8], file_extension_hint: Option<String>) -> Sm
|
||||||
save_train_collection_directory_probe,
|
save_train_collection_directory_probe,
|
||||||
save_region_collection_header_probe,
|
save_region_collection_header_probe,
|
||||||
save_region_record_triplet_probe,
|
save_region_record_triplet_probe,
|
||||||
|
save_region_queued_notice_record_probe,
|
||||||
save_placed_structure_collection_header_probe,
|
save_placed_structure_collection_header_probe,
|
||||||
save_placed_structure_record_triplet_probe,
|
save_placed_structure_record_triplet_probe,
|
||||||
save_placed_structure_dynamic_side_buffer_probe,
|
save_placed_structure_dynamic_side_buffer_probe,
|
||||||
|
|
@ -10527,6 +10634,81 @@ fn parse_save_region_record_triplet_probe(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn parse_save_region_queued_notice_record_probe(
|
||||||
|
bytes: &[u8],
|
||||||
|
file_extension_hint: Option<&str>,
|
||||||
|
container_profile: Option<&SmpContainerProfile>,
|
||||||
|
region_header_probe: Option<&SmpSaveTaggedCollectionHeaderProbe>,
|
||||||
|
) -> Option<SmpSaveRegionQueuedNoticeRecordProbe> {
|
||||||
|
if file_extension_hint != Some("gms") {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let profile = container_profile?;
|
||||||
|
let max_region_id = region_header_probe
|
||||||
|
.map(|probe| probe.live_id_bound)
|
||||||
|
.unwrap_or(0x1000);
|
||||||
|
let entries = find_u32_le_offsets(bytes, SAVE_REGION_QUEUED_NOTICE_PAYLOAD_SEED)
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|payload_seed_offset| {
|
||||||
|
let node_base_offset = payload_seed_offset.checked_sub(4)?;
|
||||||
|
let _node_bytes = bytes
|
||||||
|
.get(node_base_offset..node_base_offset + SAVE_REGION_QUEUED_NOTICE_NODE_LEN)?;
|
||||||
|
let next_link_raw = read_u32_at(bytes, node_base_offset)?;
|
||||||
|
let kind = read_u32_at(bytes, node_base_offset + 8)?;
|
||||||
|
let promotion_latch_dword = read_u32_at(bytes, node_base_offset + 12)?;
|
||||||
|
let region_id = read_u32_at(bytes, node_base_offset + 16)?;
|
||||||
|
let amount = read_u32_at(bytes, node_base_offset + 20)?;
|
||||||
|
let trailing_sentinel_i32_0 = read_i32_at(bytes, node_base_offset + 24)?;
|
||||||
|
let trailing_sentinel_i32_1 = read_i32_at(bytes, node_base_offset + 28)?;
|
||||||
|
if !(kind == SAVE_REGION_QUEUED_NOTICE_NODE_KIND
|
||||||
|
&& promotion_latch_dword == 0
|
||||||
|
&& region_id >= 1
|
||||||
|
&& region_id <= max_region_id
|
||||||
|
&& amount > 0
|
||||||
|
&& trailing_sentinel_i32_0 == -1
|
||||||
|
&& trailing_sentinel_i32_1 == -1)
|
||||||
|
{
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Some(SmpSaveRegionQueuedNoticeRecordEntryProbe {
|
||||||
|
node_base_offset,
|
||||||
|
payload_seed_offset,
|
||||||
|
next_link_raw,
|
||||||
|
next_link_raw_hex: format!("0x{next_link_raw:08x}"),
|
||||||
|
payload_seed_dword: SAVE_REGION_QUEUED_NOTICE_PAYLOAD_SEED,
|
||||||
|
payload_seed_dword_hex: format!("0x{SAVE_REGION_QUEUED_NOTICE_PAYLOAD_SEED:08x}"),
|
||||||
|
kind,
|
||||||
|
kind_hex: format!("0x{kind:08x}"),
|
||||||
|
promotion_latch_dword,
|
||||||
|
promotion_latch_dword_hex: format!("0x{promotion_latch_dword:08x}"),
|
||||||
|
region_id,
|
||||||
|
region_id_hex: format!("0x{region_id:08x}"),
|
||||||
|
amount,
|
||||||
|
amount_hex: format!("0x{amount:08x}"),
|
||||||
|
trailing_sentinel_i32_0,
|
||||||
|
trailing_sentinel_i32_0_hex: format!("0x{:08x}", trailing_sentinel_i32_0 as u32),
|
||||||
|
trailing_sentinel_i32_1,
|
||||||
|
trailing_sentinel_i32_1_hex: format!("0x{:08x}", trailing_sentinel_i32_1 as u32),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
if entries.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Some(SmpSaveRegionQueuedNoticeRecordProbe {
|
||||||
|
profile_family: profile.profile_family.clone(),
|
||||||
|
source_kind: "save-region-queued-notice-records".to_string(),
|
||||||
|
semantic_family: "scenario-save-region-queued-notice-records".to_string(),
|
||||||
|
payload_seed_dword: SAVE_REGION_QUEUED_NOTICE_PAYLOAD_SEED,
|
||||||
|
payload_seed_dword_hex: format!("0x{SAVE_REGION_QUEUED_NOTICE_PAYLOAD_SEED:08x}"),
|
||||||
|
entries,
|
||||||
|
evidence: vec![
|
||||||
|
"save-side scan searches for the grounded region queued-notice payload seed 0x005c87a8 and validates the full 0x20-byte node shape from the atlas-backed queue owner".to_string(),
|
||||||
|
"accepted nodes require kind=7, promotion-latch dword=0, a bounded live region id, a positive amount, and trailing sentinel dwords -1/-1".to_string(),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn parse_save_placed_structure_record_triplet_probe(
|
fn parse_save_placed_structure_record_triplet_probe(
|
||||||
bytes: &[u8],
|
bytes: &[u8],
|
||||||
header_probe: Option<&SmpSaveTaggedCollectionHeaderProbe>,
|
header_probe: Option<&SmpSaveTaggedCollectionHeaderProbe>,
|
||||||
|
|
@ -18949,6 +19131,60 @@ mod tests {
|
||||||
assert_eq!(profile_probe.entries[1].trailing_weight_f32, 0.45);
|
assert_eq!(profile_probe.entries[1].trailing_weight_f32, 0.45);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parses_region_queued_notice_record_probe_from_seeded_node() {
|
||||||
|
let mut bytes = vec![0u8; 0x200];
|
||||||
|
let node_base_offset = 0x80usize;
|
||||||
|
bytes[node_base_offset + 4..node_base_offset + 8]
|
||||||
|
.copy_from_slice(&SAVE_REGION_QUEUED_NOTICE_PAYLOAD_SEED.to_le_bytes());
|
||||||
|
bytes[node_base_offset + 8..node_base_offset + 12]
|
||||||
|
.copy_from_slice(&SAVE_REGION_QUEUED_NOTICE_NODE_KIND.to_le_bytes());
|
||||||
|
bytes[node_base_offset + 12..node_base_offset + 16].copy_from_slice(&0u32.to_le_bytes());
|
||||||
|
bytes[node_base_offset + 16..node_base_offset + 20].copy_from_slice(&5u32.to_le_bytes());
|
||||||
|
bytes[node_base_offset + 20..node_base_offset + 24].copy_from_slice(&1200u32.to_le_bytes());
|
||||||
|
bytes[node_base_offset + 24..node_base_offset + 28].copy_from_slice(&(-1i32).to_le_bytes());
|
||||||
|
bytes[node_base_offset + 28..node_base_offset + 32].copy_from_slice(&(-1i32).to_le_bytes());
|
||||||
|
|
||||||
|
let probe = parse_save_region_queued_notice_record_probe(
|
||||||
|
&bytes,
|
||||||
|
Some("gms"),
|
||||||
|
Some(&SmpContainerProfile {
|
||||||
|
profile_family: "rt3-105-save-container-v1".to_string(),
|
||||||
|
profile_evidence: vec![],
|
||||||
|
is_known_profile: true,
|
||||||
|
}),
|
||||||
|
Some(&SmpSaveTaggedCollectionHeaderProbe {
|
||||||
|
profile_family: "rt3-105-save-container-v1".to_string(),
|
||||||
|
source_kind: "save-region-tagged-header-counts".to_string(),
|
||||||
|
semantic_family: "scenario-save-region-header-counts".to_string(),
|
||||||
|
metadata_tag_offset: 0,
|
||||||
|
records_tag_offset: 0,
|
||||||
|
close_tag_offset: 0,
|
||||||
|
direct_collection_flag: 0,
|
||||||
|
direct_collection_flag_hex: "0x00000000".to_string(),
|
||||||
|
direct_record_stride: 0x06,
|
||||||
|
direct_record_stride_hex: "0x00000006".to_string(),
|
||||||
|
live_id_bound: 0x96,
|
||||||
|
live_id_bound_hex: "0x00000096".to_string(),
|
||||||
|
live_record_count: 0x91,
|
||||||
|
live_record_count_hex: "0x00000091".to_string(),
|
||||||
|
header_words: vec![],
|
||||||
|
header_hex_words: vec![],
|
||||||
|
evidence: vec![],
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.expect("region queued notice record probe should parse");
|
||||||
|
|
||||||
|
assert_eq!(probe.entries.len(), 1);
|
||||||
|
assert_eq!(probe.entries[0].node_base_offset, node_base_offset);
|
||||||
|
assert_eq!(probe.entries[0].payload_seed_dword_hex, "0x005c87a8");
|
||||||
|
assert_eq!(probe.entries[0].kind, SAVE_REGION_QUEUED_NOTICE_NODE_KIND);
|
||||||
|
assert_eq!(probe.entries[0].region_id, 5);
|
||||||
|
assert_eq!(probe.entries[0].amount, 1200);
|
||||||
|
assert_eq!(probe.entries[0].trailing_sentinel_i32_0, -1);
|
||||||
|
assert_eq!(probe.entries[0].trailing_sentinel_i32_1, -1);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parses_placed_structure_record_triplet_probe_from_dual_name_rows() {
|
fn parses_placed_structure_record_triplet_probe_from_dual_name_rows() {
|
||||||
let mut bytes = vec![0u8; 0x400];
|
let mut bytes = vec![0u8; 0x400];
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,9 @@ Working rule:
|
||||||
separate save-owner seam for the pending bonus lane `[region+0x276]`, completion latch
|
separate save-owner seam for the pending bonus lane `[region+0x276]`, completion latch
|
||||||
`[region+0x302]`, one-shot notice latch `[region+0x316]`, severity/source lane `[region+0x25e]`,
|
`[region+0x302]`, one-shot notice latch `[region+0x316]`, severity/source lane `[region+0x25e]`,
|
||||||
and any stable region-id or class discriminator that can drive shellless city-connection
|
and any stable region-id or class discriminator that can drive shellless city-connection
|
||||||
service.
|
service. The newly grounded queue-node probe for the atlas-backed kind-`7` notice records is a
|
||||||
|
negative result on `q.gms`, `p.gms`, and `Autosave.gms`, so the next region pass should not
|
||||||
|
assume that the transient `[world+0x66a6]` queue family is persisted in ordinary saves.
|
||||||
- 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, especially the
|
stop depending on atlas-only placed-structure and local-runtime refresh notes, especially the
|
||||||
|
|
@ -117,6 +119,11 @@ Working rule:
|
||||||
name pairs match the 56-triplet corpus), which shifts the remaining placed-structure work away
|
name pairs match the 56-triplet corpus), which shifts the remaining placed-structure work away
|
||||||
from “prove these are aliases” toward “find how the separate infrastructure-asset owner seam is
|
from “prove these are aliases” toward “find how the separate infrastructure-asset owner seam is
|
||||||
consumed by city-connection / linked-transit service.”
|
consumed by city-connection / linked-transit service.”
|
||||||
|
- Save inspection now also has a dedicated probe for the atlas-backed region queued-notice node
|
||||||
|
shape (`payload seed 0x005c87a8`, kind `7`, zero promotion latch, region id, amount, `-1/-1`
|
||||||
|
tails), plus a matching lightweight CLI inspector. Grounded `q.gms`, `p.gms`, and `Autosave.gms`
|
||||||
|
all currently return `null`, which is useful negative evidence: the transient region notice
|
||||||
|
queue is not obviously persisted in these ordinary saves.
|
||||||
- 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