Probe stock building header families
This commit is contained in:
parent
311712b051
commit
6c7ebb75b5
4 changed files with 131 additions and 0 deletions
|
|
@ -21,6 +21,8 @@ pub struct BuildingTypeSourceFile {
|
||||||
pub byte_len: Option<usize>,
|
pub byte_len: Option<usize>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub bca_selector_probe: Option<BuildingTypeBcaSelectorProbe>,
|
pub bca_selector_probe: Option<BuildingTypeBcaSelectorProbe>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub bty_header_probe: Option<BuildingTypeBtyHeaderProbe>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
|
@ -43,6 +45,26 @@ pub struct BuildingTypeBcaSelectorProbe {
|
||||||
pub byte_0xbb_hex: String,
|
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)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct BuildingTypeBcaSelectorPatternSummary {
|
pub struct BuildingTypeBcaSelectorPatternSummary {
|
||||||
pub byte_len: usize,
|
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::Bca => Some(probe_bca_selector_bytes(&bytes)),
|
||||||
BuildingTypeSourceKind::Bty => None,
|
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(
|
fn load_named_binding_comparison(
|
||||||
bindings_path: &Path,
|
bindings_path: &Path,
|
||||||
entries: &[BuildingTypeSourceEntry],
|
entries: &[BuildingTypeSourceEntry],
|
||||||
|
|
@ -416,6 +483,36 @@ mod tests {
|
||||||
assert_eq!(probe.byte_0xbb_hex, "0x78");
|
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]
|
#[test]
|
||||||
fn summarizes_recovered_table_families_from_entries_and_files() {
|
fn summarizes_recovered_table_families_from_entries_and_files() {
|
||||||
let entries = vec![
|
let entries = vec![
|
||||||
|
|
@ -452,6 +549,7 @@ mod tests {
|
||||||
source_kind: BuildingTypeSourceKind::Bty,
|
source_kind: BuildingTypeSourceKind::Bty,
|
||||||
byte_len: None,
|
byte_len: None,
|
||||||
bca_selector_probe: None,
|
bca_selector_probe: None,
|
||||||
|
bty_header_probe: None,
|
||||||
},
|
},
|
||||||
BuildingTypeSourceFile {
|
BuildingTypeSourceFile {
|
||||||
file_name: "Warehouse.bca".to_string(),
|
file_name: "Warehouse.bca".to_string(),
|
||||||
|
|
@ -460,6 +558,7 @@ mod tests {
|
||||||
source_kind: BuildingTypeSourceKind::Bca,
|
source_kind: BuildingTypeSourceKind::Bca,
|
||||||
byte_len: None,
|
byte_len: None,
|
||||||
bca_selector_probe: None,
|
bca_selector_probe: None,
|
||||||
|
bty_header_probe: None,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -75,6 +75,15 @@
|
||||||
`Maintenance.bty` and `ServiceTower.bty`, alongside the style-specific house families. So the
|
`Maintenance.bty` and `ServiceTower.bty`, alongside the style-specific house families. So the
|
||||||
recovered `0x005f3c6c/0x005f3c80` naming strip is now backed by the shipped on-disk
|
recovered `0x005f3c6c/0x005f3c80` naming strip is now backed by the shipped on-disk
|
||||||
`BuildingTypes` filename families, not only by the loader-side disassembly.
|
`BuildingTypes` filename families, not only by the loader-side disassembly.
|
||||||
|
The new `.bty` header probes now tighten that stock source split too: `Port.bty` and
|
||||||
|
`Warehouse.bty` both probe as ordinary `type_id = 0x000003ec` rows with direct bare-name
|
||||||
|
headers and shared `dword_0xbb = 0x000001f4`, while the style-station families such as
|
||||||
|
`VictorianStationSml/Med/Lrg.bty` stay on the same `0x000003ec` family but keep
|
||||||
|
`name_0x7c = VictorianStations` and zero `dword_0xbb`. `Maintenance.bty` and
|
||||||
|
`ServiceTower.bty` likewise stay in the same stock family, but expose display names
|
||||||
|
`Maintenance Facility` and `Service Tower` with zero `dword_0xbb`. So the later numbered
|
||||||
|
`Port%02d` / `Warehouse%02d` clone seam is now bounded above the bare `Port` / `Warehouse`
|
||||||
|
family itself rather than under a hidden station-style alias family in `BuildingTypes`.
|
||||||
The fixed
|
The fixed
|
||||||
tail is explicit now too: `0x00444dd0` writes one direct dword from
|
tail is explicit now too: `0x00444dd0` writes one direct dword from
|
||||||
`[world+0x19]`, one zeroed `0x1f4`-byte slab under `0x32cf`, closes the package, derives the
|
`[world+0x19]`, one zeroed `0x1f4`-byte slab under `0x32cf`, closes the package, derives the
|
||||||
|
|
|
||||||
|
|
@ -1307,6 +1307,17 @@
|
||||||
`0x005f3c6c/0x005f3c80` pair is no longer an anonymous bank-byte helper; it is the stock
|
`0x005f3c6c/0x005f3c80` pair is no longer an anonymous bank-byte helper; it is the stock
|
||||||
style-family-to-combined-name resolver immediately below the remaining Tier-2 source-selection
|
style-family-to-combined-name resolver immediately below the remaining Tier-2 source-selection
|
||||||
frontier.
|
frontier.
|
||||||
|
The recovered `.bty` header probes now sharpen that frontier too. `Port.bty` and
|
||||||
|
`Warehouse.bty` probe as ordinary `type_id = 0x000003ec` rows with direct bare-name headers
|
||||||
|
(`name_0x22` / `name_0x7c` = `Port` or `Warehouse`) and shared `dword_0xbb = 0x000001f4`,
|
||||||
|
while `VictorianStationSml/Med/Lrg.bty` stay on the same `0x000003ec` family but keep
|
||||||
|
`name_0x7c = VictorianStations` and `dword_0xbb = 0x00000000`. The standalone
|
||||||
|
`Maintenance.bty` / `ServiceTower.bty` rows also stay in that stock family, but their display
|
||||||
|
names are `Maintenance Facility` and `Service Tower` and their `dword_0xbb` lane remains zero.
|
||||||
|
So the numbered `Port%02d` / `Warehouse%02d` seam is no longer plausibly a hidden station-style
|
||||||
|
alias family under the stock assets; the remaining open question is why the later clone chooser
|
||||||
|
favors the bare `Port` / `Warehouse` family over those zero-valued station and maintenance /
|
||||||
|
service rows.
|
||||||
The direct `+0xba/+0xbb` writer census now rules out a broad false lead too. The obvious new
|
The direct `+0xba/+0xbb` writer census now rules out a broad false lead too. The obvious new
|
||||||
stores at `0x004ecd42/0x004ecdaa` and `0x004ed5d5/0x004ed625` are only shell-side
|
stores at `0x004ecd42/0x004ecdaa` and `0x004ed5d5/0x004ed625` are only shell-side
|
||||||
portrait/string refresh helpers over a different id-keyed collection rooted through
|
portrait/string refresh helpers over a different id-keyed collection rooted through
|
||||||
|
|
|
||||||
|
|
@ -843,6 +843,18 @@ Working rule:
|
||||||
`0x005f3c6c/0x005f3c80` pair is no longer an anonymous bank byte table; it is the stock
|
`0x005f3c6c/0x005f3c80` pair is no longer an anonymous bank byte table; it is the stock
|
||||||
style-family-to-combined-name resolver that sits immediately below the remaining Tier-2 source
|
style-family-to-combined-name resolver that sits immediately below the remaining Tier-2 source
|
||||||
selection frontier.
|
selection frontier.
|
||||||
|
The recovered `.bty` header probes tighten that source split further too. The checked-in
|
||||||
|
`inspect-building-type-sources` report now shows `Port.bty` and `Warehouse.bty` are ordinary
|
||||||
|
`type_id = 0x000003ec` rows with direct bare-name headers (`name_0x22` / `name_0x7c` =
|
||||||
|
`Port` or `Warehouse`) and shared `dword_0xbb = 0x000001f4`, while the style-station rows such
|
||||||
|
as `VictorianStationSml/Med/Lrg.bty` stay on the same `0x000003ec` family but keep
|
||||||
|
`name_0x7c = VictorianStations` and `dword_0xbb = 0x00000000`. The standalone
|
||||||
|
`Maintenance.bty` / `ServiceTower.bty` rows also stay in the same stock family, but expose
|
||||||
|
display names `Maintenance Facility` and `Service Tower` with zero `dword_0xbb`. So the
|
||||||
|
remaining Tier-2 source question is no longer whether the numbered `Port%02d` /
|
||||||
|
`Warehouse%02d` banks are hidden station-style aliases; it is why the later clone path prefers
|
||||||
|
the bare `Port` / `Warehouse` family over the zero-valued station and maintenance/service
|
||||||
|
families when it seeds those numbered banks.
|
||||||
The direct `+0xba/+0xbb` writer census is narrower now too. The obvious newly surfaced stores
|
The direct `+0xba/+0xbb` writer census is narrower now too. The obvious newly surfaced stores
|
||||||
at `0x004ecd42/0x004ecdaa` and `0x004ed5d5/0x004ed625` are only shell-side portrait/string
|
at `0x004ecd42/0x004ecdaa` and `0x004ed5d5/0x004ed625` are only shell-side portrait/string
|
||||||
refresh helpers: they walk a separate id-keyed collection through `0x0053f830`, free and
|
refresh helpers: they walk a separate id-keyed collection through `0x0053f830`, free and
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue