Skip to main content

wow_cdbc/
stringblock.rs

1//! String block parsing functionality
2
3use crate::{Error, Result, StringRef};
4use std::collections::HashMap;
5use std::io::{Read, Seek, SeekFrom};
6use std::sync::Arc;
7
8/// Represents a string block in a DBC file
9#[derive(Debug, Clone)]
10pub struct StringBlock {
11    /// The raw bytes of the string block
12    data: Vec<u8>,
13}
14
15impl StringBlock {
16    /// Parse a string block from a reader
17    pub fn parse<R: Read + Seek>(reader: &mut R, offset: u64, size: u32) -> Result<Self> {
18        reader.seek(SeekFrom::Start(offset))?;
19
20        let mut data = vec![0u8; size as usize];
21        reader.read_exact(&mut data)?;
22
23        Ok(Self { data })
24    }
25
26    /// Get a string from the string block using a string reference
27    pub fn get_string(&self, string_ref: StringRef) -> Result<&str> {
28        let offset = string_ref.offset() as usize;
29        if offset >= self.data.len() {
30            return Err(Error::OutOfBounds(format!(
31                "String reference offset out of bounds: {} (max: {})",
32                offset,
33                self.data.len()
34            )));
35        }
36
37        // Find the end of the string (null terminator)
38        let mut end = offset;
39        while end < self.data.len() && self.data[end] != 0 {
40            end += 1;
41        }
42
43        // Convert the bytes to a string
44        std::str::from_utf8(&self.data[offset..end])
45            .map_err(|e| Error::TypeConversion(format!("Invalid UTF-8 string: {e}")))
46    }
47
48    /// Get the raw data of the string block
49    pub fn data(&self) -> &[u8] {
50        &self.data
51    }
52
53    /// Create a string block from raw bytes.
54    ///
55    /// Used when constructing a `RecordSet` during import, where the string block
56    /// is built in memory rather than read from a file.
57    pub fn from_bytes(data: Vec<u8>) -> Self {
58        Self { data }
59    }
60
61    /// Get the size of the string block in bytes
62    pub fn size(&self) -> usize {
63        self.data.len()
64    }
65
66    /// Check if an offset is the start of a string in the block
67    ///
68    /// A valid string start is either at offset 0 (beginning of block)
69    /// or immediately after a NUL terminator (byte at offset-1 is 0).
70    pub fn is_string_start(&self, offset: u32) -> bool {
71        let offset = offset as usize;
72        if offset >= self.data.len() {
73            return false;
74        }
75        // Offset 0 is always a valid string start
76        // Otherwise, the previous byte must be a NUL terminator
77        offset == 0 || self.data[offset - 1] == 0
78    }
79}
80
81/// A cached string block for efficient string lookups
82#[derive(Debug, Clone)]
83pub struct CachedStringBlock {
84    /// The raw bytes of the string block
85    data: Arc<Vec<u8>>,
86    /// Cache of string references to string slices
87    cache: HashMap<u32, (usize, usize)>,
88}
89
90impl CachedStringBlock {
91    /// Create a cached string block from a string block
92    pub fn from_string_block(string_block: &StringBlock) -> Self {
93        let data = Arc::new(string_block.data().to_vec());
94        let mut cache = HashMap::new();
95
96        let mut offset = 0;
97        while offset < data.len() {
98            let start_offset = offset;
99
100            // Find the end of the string (null terminator)
101            while offset < data.len() && data[offset] != 0 {
102                offset += 1;
103            }
104
105            // Cache the string position
106            cache.insert(start_offset as u32, (start_offset, offset));
107
108            // Skip the null terminator
109            offset += 1;
110        }
111
112        Self { data, cache }
113    }
114
115    /// Get a string from the string block using a string reference
116    pub fn get_string(&self, string_ref: StringRef) -> Result<&str> {
117        let offset = string_ref.offset() as usize;
118
119        if let Some((start, end)) = self.cache.get(&string_ref.offset()) {
120            // Convert the bytes to a string
121            std::str::from_utf8(&self.data[*start..*end])
122                .map_err(|e| Error::TypeConversion(format!("Invalid UTF-8 string: {e}")))
123        } else {
124            // If not cached, find the end of the string
125            if offset >= self.data.len() {
126                return Err(Error::OutOfBounds(format!(
127                    "String reference offset out of bounds: {} (max: {})",
128                    offset,
129                    self.data.len()
130                )));
131            }
132
133            let mut end = offset;
134            while end < self.data.len() && self.data[end] != 0 {
135                end += 1;
136            }
137
138            // Convert the bytes to a string
139            std::str::from_utf8(&self.data[offset..end])
140                .map_err(|e| Error::TypeConversion(format!("Invalid UTF-8 string: {e}")))
141        }
142    }
143}