Probe stock building header families

This commit is contained in:
Jan Petykiewicz 2026-04-19 15:05:48 -07:00
commit 6c7ebb75b5
4 changed files with 131 additions and 0 deletions

View file

@ -21,6 +21,8 @@ pub struct BuildingTypeSourceFile {
pub byte_len: Option<usize>,
#[serde(default)]
pub bca_selector_probe: Option<BuildingTypeBcaSelectorProbe>,
#[serde(default)]
pub bty_header_probe: Option<BuildingTypeBtyHeaderProbe>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
@ -43,6 +45,26 @@ pub struct BuildingTypeBcaSelectorProbe {
pub byte_0xbb_hex: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct BuildingTypeBtyHeaderProbe {
pub type_id: u32,
pub type_id_hex: String,
pub name_0x04: String,
pub name_0x22: String,
pub name_0x40: String,
pub name_0x5e: String,
pub name_0x7c: String,
pub name_0x9a: String,
pub byte_0xb8: u8,
pub byte_0xb8_hex: String,
pub byte_0xb9: u8,
pub byte_0xb9_hex: String,
pub byte_0xba: u8,
pub byte_0xba_hex: String,
pub dword_0xbb: u32,
pub dword_0xbb_hex: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct BuildingTypeBcaSelectorPatternSummary {
pub byte_len: usize,
@ -136,6 +158,10 @@ pub fn inspect_building_types_dir_with_bindings(
BuildingTypeSourceKind::Bca => Some(probe_bca_selector_bytes(&bytes)),
BuildingTypeSourceKind::Bty => None,
},
bty_header_probe: match source_kind {
BuildingTypeSourceKind::Bca => None,
BuildingTypeSourceKind::Bty => Some(probe_bty_header(&bytes)),
},
});
}
@ -267,6 +293,47 @@ fn probe_bca_selector_bytes(bytes: &[u8]) -> BuildingTypeBcaSelectorProbe {
}
}
fn probe_bty_header(bytes: &[u8]) -> BuildingTypeBtyHeaderProbe {
let type_id = read_u32_le(bytes, 0x00);
let byte_0xb8 = bytes.get(0xb8).copied().unwrap_or(0);
let byte_0xb9 = bytes.get(0xb9).copied().unwrap_or(0);
let byte_0xba = bytes.get(0xba).copied().unwrap_or(0);
let dword_0xbb = read_u32_le(bytes, 0xbb);
BuildingTypeBtyHeaderProbe {
type_id,
type_id_hex: format!("0x{type_id:08x}"),
name_0x04: read_c_string(bytes, 0x04, 0x1e),
name_0x22: read_c_string(bytes, 0x22, 0x1e),
name_0x40: read_c_string(bytes, 0x40, 0x1e),
name_0x5e: read_c_string(bytes, 0x5e, 0x1e),
name_0x7c: read_c_string(bytes, 0x7c, 0x1e),
name_0x9a: read_c_string(bytes, 0x9a, 0x1e),
byte_0xb8,
byte_0xb8_hex: format!("0x{byte_0xb8:02x}"),
byte_0xb9,
byte_0xb9_hex: format!("0x{byte_0xb9:02x}"),
byte_0xba,
byte_0xba_hex: format!("0x{byte_0xba:02x}"),
dword_0xbb,
dword_0xbb_hex: format!("0x{dword_0xbb:08x}"),
}
}
fn read_u32_le(bytes: &[u8], offset: usize) -> u32 {
bytes.get(offset..offset + 4)
.and_then(|slice| <[u8; 4]>::try_from(slice).ok())
.map(u32::from_le_bytes)
.unwrap_or(0)
}
fn read_c_string(bytes: &[u8], offset: usize, max_len: usize) -> String {
let Some(slice) = bytes.get(offset..offset.saturating_add(max_len)) else {
return String::new();
};
let end = slice.iter().position(|byte| *byte == 0).unwrap_or(slice.len());
String::from_utf8_lossy(&slice[..end]).into_owned()
}
fn load_named_binding_comparison(
bindings_path: &Path,
entries: &[BuildingTypeSourceEntry],
@ -416,6 +483,36 @@ mod tests {
assert_eq!(probe.byte_0xbb_hex, "0x78");
}
#[test]
fn probes_bty_header_from_fixed_offsets() {
let mut bytes = vec![0u8; 0xc0];
bytes[0x00..0x04].copy_from_slice(&0x03ebu32.to_le_bytes());
bytes[0x04..0x04 + 5].copy_from_slice(b"Port\0");
bytes[0x22..0x22 + 7].copy_from_slice(b"Cargo\0\0");
bytes[0x40..0x40 + 6].copy_from_slice(b"Dock\0\0");
bytes[0x5e..0x5e + 5].copy_from_slice(b"Sea\0\0");
bytes[0x7c..0x7c + 6].copy_from_slice(b"Coast\0");
bytes[0x9a..0x9a + 5].copy_from_slice(b"Port\0");
bytes[0xb8] = 0x12;
bytes[0xb9] = 0x34;
bytes[0xba] = 0x56;
bytes[0xbb..0xbf].copy_from_slice(&0x89abcdefu32.to_le_bytes());
let probe = probe_bty_header(&bytes);
assert_eq!(probe.type_id, 0x03eb);
assert_eq!(probe.type_id_hex, "0x000003eb");
assert_eq!(probe.name_0x04, "Port");
assert_eq!(probe.name_0x22, "Cargo");
assert_eq!(probe.name_0x40, "Dock");
assert_eq!(probe.name_0x5e, "Sea");
assert_eq!(probe.name_0x7c, "Coast");
assert_eq!(probe.name_0x9a, "Port");
assert_eq!(probe.byte_0xb8_hex, "0x12");
assert_eq!(probe.byte_0xb9_hex, "0x34");
assert_eq!(probe.byte_0xba_hex, "0x56");
assert_eq!(probe.dword_0xbb_hex, "0x89abcdef");
}
#[test]
fn summarizes_recovered_table_families_from_entries_and_files() {
let entries = vec![
@ -452,6 +549,7 @@ mod tests {
source_kind: BuildingTypeSourceKind::Bty,
byte_len: None,
bca_selector_probe: None,
bty_header_probe: None,
},
BuildingTypeSourceFile {
file_name: "Warehouse.bca".to_string(),
@ -460,6 +558,7 @@ mod tests {
source_kind: BuildingTypeSourceKind::Bca,
byte_len: None,
bca_selector_probe: None,
bty_header_probe: None,
},
];