banished_formats/
names.rs1use crate::crc::type_id;
10use crate::crs::{self, Reader};
11use crate::error::{format_err, Error, Result};
12
13#[derive(Debug, Clone, Copy)]
15pub struct NameTransition {
16 pub prefix: [u8; 4],
18 pub boundary: u8,
20 pub next_characters: u32,
22}
23
24#[derive(Debug, Clone)]
26pub struct NameDatabase {
27 pub transitions: Vec<NameTransition>,
29 pub starting_transitions: Vec<u16>,
31 pub string_pool: Vec<u8>,
33 pub name_offsets: Vec<u32>,
35 pub names: Vec<String>,
37}
38
39impl NameDatabase {
40 pub const CLASS: &'static str = "NameDatabase";
42
43 pub fn read(data: &[u8]) -> Result<Self> {
45 let (_, value) = crs::parse(data, |r: &mut Reader, header| {
46 if header.type_id != type_id(Self::CLASS) {
47 return format_err(format!(
48 "expected NameDatabase, found {:#010x}",
49 header.type_id
50 ));
51 }
52 r.block(|r, _| {
53 let transition_count = r.count()?;
54 let mut transitions = Vec::with_capacity(transition_count);
55 for _ in 0..transition_count {
56 let prefix = r.bytes(4)?.try_into().unwrap();
57 let boundary = r.u8()?;
58 let next_characters = r.u32()?;
59 transitions.push(NameTransition {
60 prefix,
61 boundary,
62 next_characters,
63 });
64 }
65
66 let starting_count = r.count()?;
67 let mut starting_transitions = Vec::with_capacity(starting_count);
68 for _ in 0..starting_count {
69 starting_transitions.push(u16::from_le_bytes(r.bytes(2)?.try_into().unwrap()));
70 }
71
72 let pool_len = r.count()?;
73 let string_pool = r.bytes(pool_len)?.to_vec();
74 let name_count = r.count()?;
75 let mut name_offsets = Vec::with_capacity(name_count);
76 let mut names = Vec::with_capacity(name_count);
77 for _ in 0..name_count {
78 let offset = r.u32()?;
79 let start = offset as usize;
80 let Some(rest) = string_pool.get(start..) else {
81 return Err(Error::Format(format!(
82 "name offset {offset:#x} exceeds string pool size {}",
83 string_pool.len()
84 )));
85 };
86 let Some(end) = rest.iter().position(|&b| b == 0) else {
87 return Err(Error::Format(format!(
88 "name at offset {offset:#x} is not NUL terminated"
89 )));
90 };
91 let name = std::str::from_utf8(&rest[..end])
92 .map_err(|_| Error::Format(format!("name at {offset:#x} is not UTF-8")))?;
93 names.push(name.to_owned());
94 name_offsets.push(offset);
95 }
96
97 if transitions
98 .iter()
99 .any(|entry| !entry.prefix.is_ascii() || entry.next_characters >> 27 != 0)
100 {
101 return format_err("NameDatabase contains an invalid transition entry");
102 }
103 if starting_transitions
104 .iter()
105 .any(|&index| index as usize >= transitions.len())
106 {
107 return format_err("NameDatabase starting index exceeds transition table");
108 }
109 if names.windows(2).any(|pair| pair[0] > pair[1]) {
110 return format_err("NameDatabase name offsets are not sorted by name");
111 }
112
113 Ok(Self {
114 transitions,
115 starting_transitions,
116 string_pool,
117 name_offsets,
118 names,
119 })
120 })
121 })?;
122 Ok(value)
123 }
124}