Skip to main content

wow_wdt/chunks/
mod.rs

1//! WDT chunk implementations
2
3use crate::error::{Error, Result};
4use std::io::{Read, Write};
5
6pub mod maid;
7pub mod mphd;
8
9// Re-export chunk types
10pub use maid::MaidChunk;
11pub use mphd::{MphdChunk, MphdFlags};
12
13/// WDT file version (always 18)
14pub const WDT_VERSION: u32 = 18;
15
16/// Map dimensions (64x64 grid)
17pub const WDT_MAP_SIZE: usize = 64;
18
19/// Total number of tiles
20pub const WDT_TILE_COUNT: usize = WDT_MAP_SIZE * WDT_MAP_SIZE;
21
22/// Chunk header size (magic + size)
23pub const CHUNK_HEADER_SIZE: usize = 8;
24
25/// Common trait for all chunks
26pub trait Chunk: Sized {
27    /// Get the chunk magic bytes
28    fn magic() -> &'static [u8; 4];
29
30    /// Get the expected chunk size (None for variable-sized chunks)
31    fn expected_size() -> Option<usize> {
32        None
33    }
34
35    /// Read chunk data from a reader (after magic and size have been read)
36    fn read(reader: &mut impl Read, size: usize) -> Result<Self>;
37
38    /// Write chunk data to a writer
39    fn write(&self, writer: &mut impl Write) -> Result<()>;
40
41    /// Get the size of the chunk data (excluding header)
42    fn size(&self) -> usize;
43
44    /// Write the complete chunk including header
45    fn write_chunk(&self, writer: &mut impl Write) -> Result<()> {
46        writer.write_all(Self::magic())?;
47        writer.write_all(&(self.size() as u32).to_le_bytes())?;
48        self.write(writer)?;
49        Ok(())
50    }
51}
52
53/// MVER chunk - Version information
54#[derive(Debug, Clone, Copy, PartialEq, Eq)]
55pub struct MverChunk {
56    pub version: u32,
57}
58
59impl MverChunk {
60    pub fn new() -> Self {
61        Self {
62            version: WDT_VERSION,
63        }
64    }
65}
66
67impl Default for MverChunk {
68    fn default() -> Self {
69        Self::new()
70    }
71}
72
73impl Chunk for MverChunk {
74    fn magic() -> &'static [u8; 4] {
75        b"REVM" // 'MVER' reversed
76    }
77
78    fn expected_size() -> Option<usize> {
79        Some(4)
80    }
81
82    fn read(reader: &mut impl Read, size: usize) -> Result<Self> {
83        if size != 4 {
84            return Err(Error::InvalidChunkSize {
85                chunk: "MVER".to_string(),
86                expected: 4,
87                found: size,
88            });
89        }
90
91        let mut buf = [0u8; 4];
92        reader.read_exact(&mut buf)?;
93        let version = u32::from_le_bytes(buf);
94        if version != WDT_VERSION {
95            return Err(Error::InvalidVersion(version));
96        }
97
98        Ok(Self { version })
99    }
100
101    fn write(&self, writer: &mut impl Write) -> Result<()> {
102        writer.write_all(&self.version.to_le_bytes())?;
103        Ok(())
104    }
105
106    fn size(&self) -> usize {
107        4
108    }
109}
110
111/// MAIN chunk entry - Tile information
112#[derive(Debug, Clone, Copy, PartialEq, Eq)]
113pub struct MainEntry {
114    pub flags: u32,
115    pub area_id: u32,
116}
117
118impl MainEntry {
119    pub fn new() -> Self {
120        Self {
121            flags: 0,
122            area_id: 0,
123        }
124    }
125
126    /// Check if this tile has ADT data
127    pub fn has_adt(&self) -> bool {
128        (self.flags & 0x0001) != 0
129    }
130
131    /// Set whether this tile has ADT data
132    pub fn set_has_adt(&mut self, has_adt: bool) {
133        if has_adt {
134            self.flags |= 0x0001;
135        } else {
136            self.flags &= !0x0001;
137        }
138    }
139}
140
141impl Default for MainEntry {
142    fn default() -> Self {
143        Self::new()
144    }
145}
146
147/// MAIN chunk - Map tile information
148#[derive(Debug, Clone, PartialEq, Eq)]
149pub struct MainChunk {
150    pub entries: Vec<Vec<MainEntry>>,
151}
152
153impl MainChunk {
154    pub fn new() -> Self {
155        let mut entries = Vec::with_capacity(WDT_MAP_SIZE);
156        for _ in 0..WDT_MAP_SIZE {
157            let mut row = Vec::with_capacity(WDT_MAP_SIZE);
158            for _ in 0..WDT_MAP_SIZE {
159                row.push(MainEntry::new());
160            }
161            entries.push(row);
162        }
163        Self { entries }
164    }
165
166    /// Get entry at coordinates
167    pub fn get(&self, x: usize, y: usize) -> Option<&MainEntry> {
168        self.entries.get(y).and_then(|row| row.get(x))
169    }
170
171    /// Get mutable entry at coordinates
172    pub fn get_mut(&mut self, x: usize, y: usize) -> Option<&mut MainEntry> {
173        self.entries.get_mut(y).and_then(|row| row.get_mut(x))
174    }
175
176    /// Count tiles with ADT data
177    pub fn count_existing_tiles(&self) -> usize {
178        self.entries
179            .iter()
180            .flat_map(|row| row.iter())
181            .filter(|entry| entry.has_adt())
182            .count()
183    }
184}
185
186impl Default for MainChunk {
187    fn default() -> Self {
188        Self::new()
189    }
190}
191
192impl Chunk for MainChunk {
193    fn magic() -> &'static [u8; 4] {
194        b"NIAM" // 'MAIN' reversed
195    }
196
197    fn expected_size() -> Option<usize> {
198        Some(WDT_TILE_COUNT * 8)
199    }
200
201    fn read(reader: &mut impl Read, size: usize) -> Result<Self> {
202        let expected = WDT_TILE_COUNT * 8;
203        if size != expected {
204            return Err(Error::InvalidChunkSize {
205                chunk: "MAIN".to_string(),
206                expected,
207                found: size,
208            });
209        }
210
211        let mut entries = Vec::with_capacity(WDT_MAP_SIZE);
212        for _y in 0..WDT_MAP_SIZE {
213            let mut row = Vec::with_capacity(WDT_MAP_SIZE);
214            for _x in 0..WDT_MAP_SIZE {
215                let mut buf = [0u8; 4];
216                reader.read_exact(&mut buf)?;
217                let flags = u32::from_le_bytes(buf);
218                reader.read_exact(&mut buf)?;
219                let area_id = u32::from_le_bytes(buf);
220                row.push(MainEntry { flags, area_id });
221            }
222            entries.push(row);
223        }
224
225        Ok(Self { entries })
226    }
227
228    fn write(&self, writer: &mut impl Write) -> Result<()> {
229        for row in &self.entries {
230            for entry in row {
231                writer.write_all(&entry.flags.to_le_bytes())?;
232                writer.write_all(&entry.area_id.to_le_bytes())?;
233            }
234        }
235        Ok(())
236    }
237
238    fn size(&self) -> usize {
239        WDT_TILE_COUNT * 8
240    }
241}
242
243/// MWMO chunk - World Map Object filenames
244#[derive(Debug, Clone, PartialEq, Eq)]
245pub struct MwmoChunk {
246    pub filenames: Vec<String>,
247}
248
249impl MwmoChunk {
250    pub fn new() -> Self {
251        Self {
252            filenames: Vec::new(),
253        }
254    }
255
256    /// Add a WMO filename
257    pub fn add_filename(&mut self, filename: String) {
258        self.filenames.push(filename);
259    }
260
261    /// Check if chunk is empty
262    pub fn is_empty(&self) -> bool {
263        self.filenames.is_empty()
264    }
265}
266
267impl Default for MwmoChunk {
268    fn default() -> Self {
269        Self::new()
270    }
271}
272
273impl Chunk for MwmoChunk {
274    fn magic() -> &'static [u8; 4] {
275        b"OMWM" // 'MWMO' reversed
276    }
277
278    fn read(reader: &mut impl Read, size: usize) -> Result<Self> {
279        if size == 0 {
280            return Ok(Self::new());
281        }
282
283        // Read all data
284        let mut data = vec![0u8; size];
285        reader.read_exact(&mut data)?;
286
287        // Split by null terminators
288        let mut filenames = Vec::new();
289        let mut current = Vec::new();
290
291        for &byte in &data {
292            if byte == 0 {
293                if !current.is_empty() {
294                    let filename =
295                        String::from_utf8(current.clone()).map_err(|e| Error::StringError {
296                            context: "MWMO filename".to_string(),
297                            message: e.to_string(),
298                        })?;
299                    filenames.push(filename);
300                    current.clear();
301                }
302            } else {
303                current.push(byte);
304            }
305        }
306
307        // Handle case where data doesn't end with null
308        if !current.is_empty() {
309            let filename = String::from_utf8(current).map_err(|e| Error::StringError {
310                context: "MWMO filename".to_string(),
311                message: e.to_string(),
312            })?;
313            filenames.push(filename);
314        }
315
316        Ok(Self { filenames })
317    }
318
319    fn write(&self, writer: &mut impl Write) -> Result<()> {
320        for filename in &self.filenames {
321            writer.write_all(filename.as_bytes())?;
322            writer.write_all(&[0])?; // Null terminator
323        }
324        Ok(())
325    }
326
327    fn size(&self) -> usize {
328        self.filenames
329            .iter()
330            .map(|f| f.len() + 1) // +1 for null terminator
331            .sum()
332    }
333}
334
335/// MODF entry - Map Object Definition
336#[derive(Debug, Clone, Copy, PartialEq)]
337pub struct ModfEntry {
338    pub id: u32,
339    pub unique_id: u32,
340    pub position: [f32; 3],
341    pub rotation: [f32; 3],
342    pub lower_bounds: [f32; 3],
343    pub upper_bounds: [f32; 3],
344    pub flags: u16,
345    pub doodad_set: u16,
346    pub name_set: u16,
347    pub scale: u16,
348}
349
350impl ModfEntry {
351    pub fn new() -> Self {
352        Self {
353            id: 0,
354            unique_id: 0xFFFFFFFF, // -1, common in early versions
355            position: [0.0; 3],
356            rotation: [0.0; 3],
357            lower_bounds: [0.0; 3],
358            upper_bounds: [0.0; 3],
359            flags: 0,
360            doodad_set: 0,
361            name_set: 0,
362            scale: 0, // 0 in early versions, 1024 (1.0) in later
363        }
364    }
365}
366
367impl Default for ModfEntry {
368    fn default() -> Self {
369        Self::new()
370    }
371}
372
373/// MODF chunk - Map Object Definitions
374#[derive(Debug, Clone, PartialEq)]
375pub struct ModfChunk {
376    pub entries: Vec<ModfEntry>,
377}
378
379impl ModfChunk {
380    pub fn new() -> Self {
381        Self {
382            entries: Vec::new(),
383        }
384    }
385
386    /// Add an entry
387    pub fn add_entry(&mut self, entry: ModfEntry) {
388        self.entries.push(entry);
389    }
390}
391
392impl Default for ModfChunk {
393    fn default() -> Self {
394        Self::new()
395    }
396}
397
398impl Chunk for ModfChunk {
399    fn magic() -> &'static [u8; 4] {
400        b"FDOM" // 'MODF' reversed
401    }
402
403    fn read(reader: &mut impl Read, size: usize) -> Result<Self> {
404        if !size.is_multiple_of(64) {
405            return Err(Error::InvalidChunkData {
406                chunk: "MODF".to_string(),
407                message: format!("Size {size} is not a multiple of 64"),
408            });
409        }
410
411        let count = size / 64;
412        let mut entries = Vec::with_capacity(count);
413
414        for _ in 0..count {
415            let mut buf = [0u8; 4];
416            reader.read_exact(&mut buf)?;
417            let id = u32::from_le_bytes(buf);
418            reader.read_exact(&mut buf)?;
419            let unique_id = u32::from_le_bytes(buf);
420
421            let mut position = [0.0f32; 3];
422            for item in &mut position {
423                let mut buf = [0u8; 4];
424                reader.read_exact(&mut buf)?;
425                *item = f32::from_le_bytes(buf);
426            }
427
428            let mut rotation = [0.0f32; 3];
429            for item in &mut rotation {
430                let mut buf = [0u8; 4];
431                reader.read_exact(&mut buf)?;
432                *item = f32::from_le_bytes(buf);
433            }
434
435            let mut lower_bounds = [0.0f32; 3];
436            for item in &mut lower_bounds {
437                let mut buf = [0u8; 4];
438                reader.read_exact(&mut buf)?;
439                *item = f32::from_le_bytes(buf);
440            }
441
442            let mut upper_bounds = [0.0f32; 3];
443            for item in &mut upper_bounds {
444                let mut buf = [0u8; 4];
445                reader.read_exact(&mut buf)?;
446                *item = f32::from_le_bytes(buf);
447            }
448
449            let mut buf2 = [0u8; 2];
450            reader.read_exact(&mut buf2)?;
451            let flags = u16::from_le_bytes(buf2);
452            reader.read_exact(&mut buf2)?;
453            let doodad_set = u16::from_le_bytes(buf2);
454            reader.read_exact(&mut buf2)?;
455            let name_set = u16::from_le_bytes(buf2);
456            reader.read_exact(&mut buf2)?;
457            let scale = u16::from_le_bytes(buf2);
458
459            entries.push(ModfEntry {
460                id,
461                unique_id,
462                position,
463                rotation,
464                lower_bounds,
465                upper_bounds,
466                flags,
467                doodad_set,
468                name_set,
469                scale,
470            });
471        }
472
473        Ok(Self { entries })
474    }
475
476    fn write(&self, writer: &mut impl Write) -> Result<()> {
477        for entry in &self.entries {
478            writer.write_all(&entry.id.to_le_bytes())?;
479            writer.write_all(&entry.unique_id.to_le_bytes())?;
480
481            for &v in &entry.position {
482                writer.write_all(&v.to_le_bytes())?;
483            }
484
485            for &v in &entry.rotation {
486                writer.write_all(&v.to_le_bytes())?;
487            }
488
489            for &v in &entry.lower_bounds {
490                writer.write_all(&v.to_le_bytes())?;
491            }
492
493            for &v in &entry.upper_bounds {
494                writer.write_all(&v.to_le_bytes())?;
495            }
496
497            writer.write_all(&entry.flags.to_le_bytes())?;
498            writer.write_all(&entry.doodad_set.to_le_bytes())?;
499            writer.write_all(&entry.name_set.to_le_bytes())?;
500            writer.write_all(&entry.scale.to_le_bytes())?;
501        }
502        Ok(())
503    }
504
505    fn size(&self) -> usize {
506        self.entries.len() * 64
507    }
508}