1use crate::error::{Error, Result};
4use std::io::{Read, Write};
5
6pub mod maid;
7pub mod mphd;
8
9pub use maid::MaidChunk;
11pub use mphd::{MphdChunk, MphdFlags};
12
13pub const WDT_VERSION: u32 = 18;
15
16pub const WDT_MAP_SIZE: usize = 64;
18
19pub const WDT_TILE_COUNT: usize = WDT_MAP_SIZE * WDT_MAP_SIZE;
21
22pub const CHUNK_HEADER_SIZE: usize = 8;
24
25pub trait Chunk: Sized {
27 fn magic() -> &'static [u8; 4];
29
30 fn expected_size() -> Option<usize> {
32 None
33 }
34
35 fn read(reader: &mut impl Read, size: usize) -> Result<Self>;
37
38 fn write(&self, writer: &mut impl Write) -> Result<()>;
40
41 fn size(&self) -> usize;
43
44 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#[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" }
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#[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 pub fn has_adt(&self) -> bool {
128 (self.flags & 0x0001) != 0
129 }
130
131 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#[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 pub fn get(&self, x: usize, y: usize) -> Option<&MainEntry> {
168 self.entries.get(y).and_then(|row| row.get(x))
169 }
170
171 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 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" }
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#[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 pub fn add_filename(&mut self, filename: String) {
258 self.filenames.push(filename);
259 }
260
261 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" }
277
278 fn read(reader: &mut impl Read, size: usize) -> Result<Self> {
279 if size == 0 {
280 return Ok(Self::new());
281 }
282
283 let mut data = vec![0u8; size];
285 reader.read_exact(&mut data)?;
286
287 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 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])?; }
324 Ok(())
325 }
326
327 fn size(&self) -> usize {
328 self.filenames
329 .iter()
330 .map(|f| f.len() + 1) .sum()
332 }
333}
334
335#[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, 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, }
364 }
365}
366
367impl Default for ModfEntry {
368 fn default() -> Self {
369 Self::new()
370 }
371}
372
373#[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 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" }
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}