wow_cdbc/
versions.rs

1//! Support for different DBC file versions
2
3use crate::{DbcHeader, Error, Result};
4use std::io::{Read, Seek, SeekFrom};
5
6/// DBC file format version
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum DbcVersion {
9    /// Original WDBC format (World of Warcraft Classic)
10    WDBC,
11    /// World of Warcraft: The Burning Crusade
12    WDB2,
13    /// World of Warcraft: Wrath of the Lich King
14    WDB3,
15    /// World of Warcraft: Cataclysm
16    WDB4,
17    /// World of Warcraft: Mists of Pandaria
18    WDB5,
19}
20
21impl DbcVersion {
22    /// Detect the DBC version from a reader
23    pub fn detect<R: Read + Seek>(reader: &mut R) -> Result<Self> {
24        reader.seek(SeekFrom::Start(0))?;
25
26        let mut magic = [0u8; 4];
27        reader.read_exact(&mut magic)?;
28
29        match &magic {
30            b"WDBC" => Ok(DbcVersion::WDBC),
31            b"WDB2" => Ok(DbcVersion::WDB2),
32            b"WDB3" => Ok(DbcVersion::WDB3),
33            b"WDB4" => Ok(DbcVersion::WDB4),
34            b"WDB5" => Ok(DbcVersion::WDB5),
35            _ => Err(Error::InvalidHeader(format!(
36                "Unknown DBC version: {:?}",
37                std::str::from_utf8(&magic).unwrap_or("Invalid UTF-8")
38            ))),
39        }
40    }
41
42    /// Get the magic signature for this DBC version
43    pub fn magic(&self) -> [u8; 4] {
44        match self {
45            DbcVersion::WDBC => *b"WDBC",
46            DbcVersion::WDB2 => *b"WDB2",
47            DbcVersion::WDB3 => *b"WDB3",
48            DbcVersion::WDB4 => *b"WDB4",
49            DbcVersion::WDB5 => *b"WDB5",
50        }
51    }
52}
53
54/// WDB2 header (The Burning Crusade)
55#[derive(Debug, Clone, Copy, PartialEq, Eq)]
56pub struct Wdb2Header {
57    /// The magic signature, should be "WDB2"
58    pub magic: [u8; 4],
59    /// Number of records in the file
60    pub record_count: u32,
61    /// Number of fields in each record
62    pub field_count: u32,
63    /// Size of each record in bytes
64    pub record_size: u32,
65    /// Size of the string block in bytes
66    pub string_block_size: u32,
67    /// Table hash
68    pub table_hash: u32,
69    /// Build number
70    pub build: u32,
71}
72
73impl Wdb2Header {
74    /// The size of a WDB2 header in bytes
75    pub const SIZE: usize = 28;
76
77    /// Parse a WDB2 header from a reader
78    pub fn parse<R: Read + Seek>(reader: &mut R) -> Result<Self> {
79        // Ensure we're at the beginning of the file
80        reader.seek(SeekFrom::Start(0))?;
81
82        // Read the magic signature
83        let mut magic = [0u8; 4];
84        reader.read_exact(&mut magic)?;
85
86        // Validate the magic signature
87        if magic != *b"WDB2" {
88            return Err(Error::InvalidHeader(format!(
89                "Invalid magic signature: {:?}, expected: {:?}",
90                magic, b"WDB2"
91            )));
92        }
93
94        // Read the rest of the header
95        let mut buf = [0u8; 4];
96
97        reader.read_exact(&mut buf)?;
98        let record_count = u32::from_le_bytes(buf);
99
100        reader.read_exact(&mut buf)?;
101        let field_count = u32::from_le_bytes(buf);
102
103        reader.read_exact(&mut buf)?;
104        let record_size = u32::from_le_bytes(buf);
105
106        reader.read_exact(&mut buf)?;
107        let string_block_size = u32::from_le_bytes(buf);
108
109        reader.read_exact(&mut buf)?;
110        let table_hash = u32::from_le_bytes(buf);
111
112        reader.read_exact(&mut buf)?;
113        let build = u32::from_le_bytes(buf);
114
115        Ok(Self {
116            magic,
117            record_count,
118            field_count,
119            record_size,
120            string_block_size,
121            table_hash,
122            build,
123        })
124    }
125
126    /// Convert to a standard DBC header
127    pub fn to_dbc_header(&self) -> DbcHeader {
128        DbcHeader {
129            magic: *b"WDBC", // Convert to WDBC format
130            record_count: self.record_count,
131            field_count: self.field_count,
132            record_size: self.record_size,
133            string_block_size: self.string_block_size,
134        }
135    }
136
137    /// Calculates the offset to the string block
138    pub fn string_block_offset(&self) -> u64 {
139        Self::SIZE as u64 + (self.record_count as u64 * self.record_size as u64)
140    }
141
142    /// Calculates the total size of the WDB2 file
143    pub fn total_size(&self) -> u64 {
144        self.string_block_offset() + self.string_block_size as u64
145    }
146}
147
148/// WDB5 header (Mists of Pandaria)
149#[derive(Debug, Clone, Copy, PartialEq, Eq)]
150pub struct Wdb5Header {
151    /// The magic signature, should be "WDB5"
152    pub magic: [u8; 4],
153    /// Number of records in the file
154    pub record_count: u32,
155    /// Number of fields in each record
156    pub field_count: u32,
157    /// Size of each record in bytes
158    pub record_size: u32,
159    /// Size of the string block in bytes
160    pub string_block_size: u32,
161    /// Table hash
162    pub table_hash: u32,
163    /// Layout hash
164    pub layout_hash: u32,
165    /// Min ID
166    pub min_id: u32,
167    /// Max ID
168    pub max_id: u32,
169    /// Locale
170    pub locale: u32,
171    /// Flags
172    pub flags: u16,
173    /// ID index
174    pub id_index: u16,
175}
176
177impl Wdb5Header {
178    /// The size of a WDB5 header in bytes
179    pub const SIZE: usize = 48;
180
181    /// Parse a WDB5 header from a reader
182    pub fn parse<R: Read + Seek>(reader: &mut R) -> Result<Self> {
183        // Ensure we're at the beginning of the file
184        reader.seek(SeekFrom::Start(0))?;
185
186        // Read the magic signature
187        let mut magic = [0u8; 4];
188        reader.read_exact(&mut magic)?;
189
190        // Validate the magic signature
191        if magic != *b"WDB5" {
192            return Err(Error::InvalidHeader(format!(
193                "Invalid magic signature: {:?}, expected: {:?}",
194                magic, b"WDB5"
195            )));
196        }
197
198        // Read the rest of the header
199        let mut buf4 = [0u8; 4];
200        let mut buf2 = [0u8; 2];
201
202        reader.read_exact(&mut buf4)?;
203        let record_count = u32::from_le_bytes(buf4);
204
205        reader.read_exact(&mut buf4)?;
206        let field_count = u32::from_le_bytes(buf4);
207
208        reader.read_exact(&mut buf4)?;
209        let record_size = u32::from_le_bytes(buf4);
210
211        reader.read_exact(&mut buf4)?;
212        let string_block_size = u32::from_le_bytes(buf4);
213
214        reader.read_exact(&mut buf4)?;
215        let table_hash = u32::from_le_bytes(buf4);
216
217        reader.read_exact(&mut buf4)?;
218        let layout_hash = u32::from_le_bytes(buf4);
219
220        reader.read_exact(&mut buf4)?;
221        let min_id = u32::from_le_bytes(buf4);
222
223        reader.read_exact(&mut buf4)?;
224        let max_id = u32::from_le_bytes(buf4);
225
226        reader.read_exact(&mut buf4)?;
227        let locale = u32::from_le_bytes(buf4);
228
229        reader.read_exact(&mut buf2)?;
230        let flags = u16::from_le_bytes(buf2);
231
232        reader.read_exact(&mut buf2)?;
233        let id_index = u16::from_le_bytes(buf2);
234
235        Ok(Self {
236            magic,
237            record_count,
238            field_count,
239            record_size,
240            string_block_size,
241            table_hash,
242            layout_hash,
243            min_id,
244            max_id,
245            locale,
246            flags,
247            id_index,
248        })
249    }
250
251    /// Convert to a standard DBC header
252    pub fn to_dbc_header(&self) -> DbcHeader {
253        DbcHeader {
254            magic: *b"WDBC", // Convert to WDBC format
255            record_count: self.record_count,
256            field_count: self.field_count,
257            record_size: self.record_size,
258            string_block_size: self.string_block_size,
259        }
260    }
261
262    /// Calculates the offset to the string block
263    pub fn string_block_offset(&self) -> u64 {
264        Self::SIZE as u64 + (self.record_count as u64 * self.record_size as u64)
265    }
266
267    /// Calculates the total size of the WDB5 file
268    pub fn total_size(&self) -> u64 {
269        self.string_block_offset() + self.string_block_size as u64
270    }
271}