boon/entity/
string_tables.rs1use std::collections::HashMap;
2
3use crate::error::{Error, Result};
4use crate::io::BitReader;
5
6use super::class_info::ClassInfo;
7
8use boon_proto::proto::{CDemoStringTables, CsvcMsgCreateStringTable, CsvcMsgUpdateStringTable};
9
10const HISTORY_SIZE: usize = 32;
14const HISTORY_BITMASK: usize = HISTORY_SIZE - 1;
15
16const MAX_STRING_BITS: usize = 5;
18const MAX_STRING_SIZE: usize = 1 << MAX_STRING_BITS;
19
20const MAX_USERDATA_BITS: usize = 17;
22const MAX_USERDATA_SIZE: usize = 1 << MAX_USERDATA_BITS;
23
24pub const INSTANCE_BASELINE_TABLE_NAME: &str = "instancebaseline";
26
27#[derive(Debug, Clone)]
29pub struct StringTableEntry {
30 pub string: Option<String>,
31 pub user_data: Option<Vec<u8>>,
32}
33
34#[derive(Debug)]
36pub struct StringTable {
37 pub name: String,
38 user_data_fixed_size: bool,
39 user_data_size: i32,
40 user_data_size_bits: i32,
41 flags: i32,
42 using_varint_bitcounts: bool,
43 pub entries: Vec<StringTableEntry>,
44}
45
46impl StringTable {
47 fn new(
48 name: &str,
49 user_data_fixed_size: bool,
50 user_data_size: i32,
51 user_data_size_bits: i32,
52 flags: i32,
53 using_varint_bitcounts: bool,
54 ) -> Self {
55 Self {
56 name: name.to_string(),
57 user_data_fixed_size,
58 user_data_size,
59 user_data_size_bits,
60 flags,
61 using_varint_bitcounts,
62 entries: Vec::new(),
63 }
64 }
65
66 pub fn parse_update(&mut self, br: &mut BitReader, num_entries: i32) -> Result<()> {
68 let mut entry_index: i32 = -1;
69 let mut history: Vec<[u8; MAX_STRING_SIZE]> = vec![[0u8; MAX_STRING_SIZE]; HISTORY_SIZE];
70 let mut history_delta_index: usize = 0;
71 let mut string_buf = vec![0u8; 1024];
72 let mut user_data_buf = vec![0u8; MAX_USERDATA_SIZE];
73 let mut user_data_uncompressed_buf = vec![0u8; MAX_USERDATA_SIZE];
74
75 for _ in 0..num_entries as usize {
76 entry_index = if br.read_bool()? {
78 entry_index + 1
79 } else {
80 br.read_uvarint32()? as i32 + 1
81 };
82
83 let has_string = br.read_bool()?;
85 let string = if has_string {
86 let mut size: usize = 0;
87
88 if br.read_bool()? {
89 let mut history_delta_zero = 0;
91 if history_delta_index > HISTORY_SIZE {
92 history_delta_zero = history_delta_index & HISTORY_BITMASK;
93 }
94
95 let index = (history_delta_zero + br.read_bits(5)? as usize) & HISTORY_BITMASK;
96 let bytes_to_copy = br.read_bits(MAX_STRING_BITS)? as usize;
97 size += bytes_to_copy;
98
99 string_buf[..bytes_to_copy].copy_from_slice(&history[index][..bytes_to_copy]);
100 size += br.read_string_into(&mut string_buf[bytes_to_copy..])?;
101 } else {
102 size += br.read_string_into(&mut string_buf)?;
103 }
104
105 let mut she = [0u8; MAX_STRING_SIZE];
107 let copy_len = size.min(MAX_STRING_SIZE);
108 she[..copy_len].copy_from_slice(&string_buf[..copy_len]);
109 history[history_delta_index & HISTORY_BITMASK] = she;
110 history_delta_index += 1;
111
112 Some(String::from_utf8_lossy(&string_buf[..size]).into_owned())
113 } else {
114 None
115 };
116
117 let has_user_data = br.read_bool()?;
119 let user_data = if has_user_data {
120 if self.user_data_fixed_size {
121 br.read_bits_to_bytes(&mut user_data_buf, self.user_data_size_bits as usize)?;
122 Some(user_data_buf[..self.user_data_size as usize].to_vec())
123 } else {
124 let mut is_compressed = false;
125 if (self.flags & 0x1) != 0 {
126 is_compressed = br.read_bool()?;
127 }
128
129 let size = if self.using_varint_bitcounts {
130 br.read_ubitvar()? as usize
131 } else {
132 br.read_bits(MAX_USERDATA_BITS)? as usize
133 };
134
135 br.read_bytes(&mut user_data_buf[..size])?;
136
137 if is_compressed {
138 let decomp_len = snap::raw::decompress_len(&user_data_buf[..size])
139 .map_err(|e| Error::Decompress(e.to_string()))?;
140 user_data_uncompressed_buf.resize(decomp_len, 0);
141 snap::raw::Decoder::new()
142 .decompress(&user_data_buf[..size], &mut user_data_uncompressed_buf)
143 .map_err(|e| Error::Decompress(e.to_string()))?;
144 Some(user_data_uncompressed_buf[..decomp_len].to_vec())
145 } else {
146 Some(user_data_buf[..size].to_vec())
147 }
148 }
149 } else {
150 None
151 };
152
153 let idx = entry_index as usize;
155 if idx < self.entries.len() {
156 if let Some(ud) = user_data {
157 self.entries[idx].user_data = Some(ud);
158 }
159 if let Some(s) = string {
160 self.entries[idx].string = Some(s);
161 }
162 } else {
163 while self.entries.len() < idx {
165 self.entries.push(StringTableEntry {
166 string: None,
167 user_data: None,
168 });
169 }
170 self.entries.push(StringTableEntry { string, user_data });
171 }
172 }
173
174 Ok(())
175 }
176}
177
178#[derive(Default)]
180pub struct StringTableContainer {
181 tables: Vec<StringTable>,
182 pub instance_baselines: HashMap<i32, Vec<u8>>,
184}
185
186impl StringTableContainer {
187 pub fn new() -> Self {
188 Self::default()
189 }
190
191 pub fn handle_create(&mut self, msg: CsvcMsgCreateStringTable) -> Result<bool> {
194 let name = msg.name.as_deref().unwrap_or("");
195 let is_baseline = name == INSTANCE_BASELINE_TABLE_NAME;
196 let mut table = StringTable::new(
197 name,
198 msg.user_data_fixed_size.unwrap_or(false),
199 msg.user_data_size.unwrap_or(0),
200 msg.user_data_size_bits.unwrap_or(0),
201 msg.flags.unwrap_or(0),
202 msg.using_varint_bitcounts.unwrap_or(false),
203 );
204
205 let string_data = if msg.data_compressed.unwrap_or(false) {
206 let sd = msg.string_data.as_deref().unwrap_or(&[]);
207 let decomp_len =
208 snap::raw::decompress_len(sd).map_err(|e| Error::Decompress(e.to_string()))?;
209 let mut buf = vec![0u8; decomp_len];
210 snap::raw::Decoder::new()
211 .decompress(sd, &mut buf)
212 .map_err(|e| Error::Decompress(e.to_string()))?;
213 buf
214 } else {
215 msg.string_data.unwrap_or_default()
216 };
217
218 let mut br = BitReader::new(&string_data);
219 table.parse_update(&mut br, msg.num_entries.unwrap_or(0))?;
220
221 self.tables.push(table);
222 Ok(is_baseline)
223 }
224
225 pub fn handle_update(&mut self, msg: CsvcMsgUpdateStringTable) -> Result<bool> {
228 let table_id = msg.table_id.unwrap_or(0) as usize;
229 if table_id >= self.tables.len() {
230 return Err(Error::Parse {
231 context: format!("string table update for non-existent table {}", table_id),
232 });
233 }
234
235 let is_baseline = self.tables[table_id].name == INSTANCE_BASELINE_TABLE_NAME;
236
237 let string_data = msg.string_data.unwrap_or_default();
238 let mut br = BitReader::new(&string_data);
239 self.tables[table_id].parse_update(&mut br, msg.num_changed_entries.unwrap_or(0))?;
240
241 Ok(is_baseline)
242 }
243
244 pub fn do_full_update(&mut self, cmd: CDemoStringTables) {
246 for incoming in &cmd.tables {
247 let table_name = incoming.table_name.as_deref().unwrap_or("");
248 if let Some(table) = self.tables.iter_mut().find(|t| t.name == table_name) {
249 for (i, item) in incoming.items.iter().enumerate() {
250 let entry = StringTableEntry {
251 string: item.str.clone(),
252 user_data: item.data.clone(),
253 };
254 if i < table.entries.len() {
255 if entry.user_data.is_some() {
256 table.entries[i].user_data = entry.user_data;
257 }
258 } else {
259 while table.entries.len() < i {
260 table.entries.push(StringTableEntry {
261 string: None,
262 user_data: None,
263 });
264 }
265 table.entries.push(entry);
266 }
267 }
268 }
269 }
270 }
271
272 pub fn update_instance_baselines(&mut self, _class_info: &ClassInfo) {
274 if let Some(table) = self
275 .tables
276 .iter()
277 .find(|t| t.name == INSTANCE_BASELINE_TABLE_NAME)
278 {
279 for entry in &table.entries {
280 if let (Some(s), Some(data)) = (&entry.string, &entry.user_data)
281 && let Ok(class_id) = s.parse::<i32>()
282 {
283 if self.instance_baselines.get(&class_id) != Some(data) {
285 self.instance_baselines.insert(class_id, data.clone());
286 }
287 }
288 }
289 }
290 }
291
292 pub fn find_table(&self, name: &str) -> Option<&StringTable> {
294 self.tables.iter().find(|t| t.name == name)
295 }
296
297 pub fn tables(&self) -> &[StringTable] {
299 &self.tables
300 }
301}
302
303#[cfg(test)]
304mod tests {
305 use super::*;
306
307 #[test]
308 fn container_new_is_empty() {
309 let c = StringTableContainer::new();
310 assert!(c.tables().is_empty());
311 assert!(c.instance_baselines.is_empty());
312 }
313
314 #[test]
315 fn find_table_missing() {
316 let c = StringTableContainer::new();
317 assert!(c.find_table("nonexistent").is_none());
318 }
319
320 #[test]
321 fn update_instance_baselines_on_empty_is_noop() {
322 let mut c = StringTableContainer::new();
323 let ci = ClassInfo::empty();
324 c.update_instance_baselines(&ci);
325 assert!(c.instance_baselines.is_empty());
326 }
327
328 #[test]
329 fn handle_update_invalid_table_id() {
330 let mut c = StringTableContainer::new();
331 let msg = CsvcMsgUpdateStringTable {
332 table_id: Some(99),
333 num_changed_entries: Some(0),
334 string_data: None,
335 };
336 let result = c.handle_update(msg);
337 assert!(result.is_err());
338 }
339
340 #[test]
341 fn handle_create_empty_uncompressed() {
342 let mut c = StringTableContainer::new();
343 let msg = CsvcMsgCreateStringTable {
344 name: Some("test".to_string()),
345 num_entries: Some(0),
346 user_data_fixed_size: Some(false),
347 user_data_size: Some(0),
348 user_data_size_bits: Some(0),
349 flags: Some(0),
350 string_data: Some(vec![]),
351 data_compressed: Some(false),
352 using_varint_bitcounts: Some(false),
353 ..Default::default()
354 };
355 c.handle_create(msg).unwrap();
356 assert_eq!(c.tables().len(), 1);
357 assert!(c.find_table("test").is_some());
358 }
359}