use crate::error::{Error, Result};
use std::io::{Read, Write};
pub mod maid;
pub mod mphd;
pub use maid::MaidChunk;
pub use mphd::{MphdChunk, MphdFlags};
pub const WDT_VERSION: u32 = 18;
pub const WDT_MAP_SIZE: usize = 64;
pub const WDT_TILE_COUNT: usize = WDT_MAP_SIZE * WDT_MAP_SIZE;
pub const CHUNK_HEADER_SIZE: usize = 8;
pub trait Chunk: Sized {
fn magic() -> &'static [u8; 4];
fn expected_size() -> Option<usize> {
None
}
fn read(reader: &mut impl Read, size: usize) -> Result<Self>;
fn write(&self, writer: &mut impl Write) -> Result<()>;
fn size(&self) -> usize;
fn write_chunk(&self, writer: &mut impl Write) -> Result<()> {
writer.write_all(Self::magic())?;
writer.write_all(&(self.size() as u32).to_le_bytes())?;
self.write(writer)?;
Ok(())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MverChunk {
pub version: u32,
}
impl MverChunk {
pub fn new() -> Self {
Self {
version: WDT_VERSION,
}
}
}
impl Default for MverChunk {
fn default() -> Self {
Self::new()
}
}
impl Chunk for MverChunk {
fn magic() -> &'static [u8; 4] {
b"REVM" }
fn expected_size() -> Option<usize> {
Some(4)
}
fn read(reader: &mut impl Read, size: usize) -> Result<Self> {
if size != 4 {
return Err(Error::InvalidChunkSize {
chunk: "MVER".to_string(),
expected: 4,
found: size,
});
}
let mut buf = [0u8; 4];
reader.read_exact(&mut buf)?;
let version = u32::from_le_bytes(buf);
if version != WDT_VERSION {
return Err(Error::InvalidVersion(version));
}
Ok(Self { version })
}
fn write(&self, writer: &mut impl Write) -> Result<()> {
writer.write_all(&self.version.to_le_bytes())?;
Ok(())
}
fn size(&self) -> usize {
4
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MainEntry {
pub flags: u32,
pub area_id: u32,
}
impl MainEntry {
pub fn new() -> Self {
Self {
flags: 0,
area_id: 0,
}
}
pub fn has_adt(&self) -> bool {
(self.flags & 0x0001) != 0
}
pub fn set_has_adt(&mut self, has_adt: bool) {
if has_adt {
self.flags |= 0x0001;
} else {
self.flags &= !0x0001;
}
}
}
impl Default for MainEntry {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MainChunk {
pub entries: Vec<Vec<MainEntry>>,
}
impl MainChunk {
pub fn new() -> Self {
let mut entries = Vec::with_capacity(WDT_MAP_SIZE);
for _ in 0..WDT_MAP_SIZE {
let mut row = Vec::with_capacity(WDT_MAP_SIZE);
for _ in 0..WDT_MAP_SIZE {
row.push(MainEntry::new());
}
entries.push(row);
}
Self { entries }
}
pub fn get(&self, x: usize, y: usize) -> Option<&MainEntry> {
self.entries.get(y).and_then(|row| row.get(x))
}
pub fn get_mut(&mut self, x: usize, y: usize) -> Option<&mut MainEntry> {
self.entries.get_mut(y).and_then(|row| row.get_mut(x))
}
pub fn count_existing_tiles(&self) -> usize {
self.entries
.iter()
.flat_map(|row| row.iter())
.filter(|entry| entry.has_adt())
.count()
}
}
impl Default for MainChunk {
fn default() -> Self {
Self::new()
}
}
impl Chunk for MainChunk {
fn magic() -> &'static [u8; 4] {
b"NIAM" }
fn expected_size() -> Option<usize> {
Some(WDT_TILE_COUNT * 8)
}
fn read(reader: &mut impl Read, size: usize) -> Result<Self> {
let expected = WDT_TILE_COUNT * 8;
if size != expected {
return Err(Error::InvalidChunkSize {
chunk: "MAIN".to_string(),
expected,
found: size,
});
}
let mut entries = Vec::with_capacity(WDT_MAP_SIZE);
for _y in 0..WDT_MAP_SIZE {
let mut row = Vec::with_capacity(WDT_MAP_SIZE);
for _x in 0..WDT_MAP_SIZE {
let mut buf = [0u8; 4];
reader.read_exact(&mut buf)?;
let flags = u32::from_le_bytes(buf);
reader.read_exact(&mut buf)?;
let area_id = u32::from_le_bytes(buf);
row.push(MainEntry { flags, area_id });
}
entries.push(row);
}
Ok(Self { entries })
}
fn write(&self, writer: &mut impl Write) -> Result<()> {
for row in &self.entries {
for entry in row {
writer.write_all(&entry.flags.to_le_bytes())?;
writer.write_all(&entry.area_id.to_le_bytes())?;
}
}
Ok(())
}
fn size(&self) -> usize {
WDT_TILE_COUNT * 8
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MwmoChunk {
pub filenames: Vec<String>,
}
impl MwmoChunk {
pub fn new() -> Self {
Self {
filenames: Vec::new(),
}
}
pub fn add_filename(&mut self, filename: String) {
self.filenames.push(filename);
}
pub fn is_empty(&self) -> bool {
self.filenames.is_empty()
}
}
impl Default for MwmoChunk {
fn default() -> Self {
Self::new()
}
}
impl Chunk for MwmoChunk {
fn magic() -> &'static [u8; 4] {
b"OMWM" }
fn read(reader: &mut impl Read, size: usize) -> Result<Self> {
if size == 0 {
return Ok(Self::new());
}
let mut data = vec![0u8; size];
reader.read_exact(&mut data)?;
let mut filenames = Vec::new();
let mut current = Vec::new();
for &byte in &data {
if byte == 0 {
if !current.is_empty() {
let filename =
String::from_utf8(current.clone()).map_err(|e| Error::StringError {
context: "MWMO filename".to_string(),
message: e.to_string(),
})?;
filenames.push(filename);
current.clear();
}
} else {
current.push(byte);
}
}
if !current.is_empty() {
let filename = String::from_utf8(current).map_err(|e| Error::StringError {
context: "MWMO filename".to_string(),
message: e.to_string(),
})?;
filenames.push(filename);
}
Ok(Self { filenames })
}
fn write(&self, writer: &mut impl Write) -> Result<()> {
for filename in &self.filenames {
writer.write_all(filename.as_bytes())?;
writer.write_all(&[0])?; }
Ok(())
}
fn size(&self) -> usize {
self.filenames
.iter()
.map(|f| f.len() + 1) .sum()
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ModfEntry {
pub id: u32,
pub unique_id: u32,
pub position: [f32; 3],
pub rotation: [f32; 3],
pub lower_bounds: [f32; 3],
pub upper_bounds: [f32; 3],
pub flags: u16,
pub doodad_set: u16,
pub name_set: u16,
pub scale: u16,
}
impl ModfEntry {
pub fn new() -> Self {
Self {
id: 0,
unique_id: 0xFFFFFFFF, position: [0.0; 3],
rotation: [0.0; 3],
lower_bounds: [0.0; 3],
upper_bounds: [0.0; 3],
flags: 0,
doodad_set: 0,
name_set: 0,
scale: 0, }
}
}
impl Default for ModfEntry {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct ModfChunk {
pub entries: Vec<ModfEntry>,
}
impl ModfChunk {
pub fn new() -> Self {
Self {
entries: Vec::new(),
}
}
pub fn add_entry(&mut self, entry: ModfEntry) {
self.entries.push(entry);
}
}
impl Default for ModfChunk {
fn default() -> Self {
Self::new()
}
}
impl Chunk for ModfChunk {
fn magic() -> &'static [u8; 4] {
b"FDOM" }
fn read(reader: &mut impl Read, size: usize) -> Result<Self> {
if !size.is_multiple_of(64) {
return Err(Error::InvalidChunkData {
chunk: "MODF".to_string(),
message: format!("Size {size} is not a multiple of 64"),
});
}
let count = size / 64;
let mut entries = Vec::with_capacity(count);
for _ in 0..count {
let mut buf = [0u8; 4];
reader.read_exact(&mut buf)?;
let id = u32::from_le_bytes(buf);
reader.read_exact(&mut buf)?;
let unique_id = u32::from_le_bytes(buf);
let mut position = [0.0f32; 3];
for item in &mut position {
let mut buf = [0u8; 4];
reader.read_exact(&mut buf)?;
*item = f32::from_le_bytes(buf);
}
let mut rotation = [0.0f32; 3];
for item in &mut rotation {
let mut buf = [0u8; 4];
reader.read_exact(&mut buf)?;
*item = f32::from_le_bytes(buf);
}
let mut lower_bounds = [0.0f32; 3];
for item in &mut lower_bounds {
let mut buf = [0u8; 4];
reader.read_exact(&mut buf)?;
*item = f32::from_le_bytes(buf);
}
let mut upper_bounds = [0.0f32; 3];
for item in &mut upper_bounds {
let mut buf = [0u8; 4];
reader.read_exact(&mut buf)?;
*item = f32::from_le_bytes(buf);
}
let mut buf2 = [0u8; 2];
reader.read_exact(&mut buf2)?;
let flags = u16::from_le_bytes(buf2);
reader.read_exact(&mut buf2)?;
let doodad_set = u16::from_le_bytes(buf2);
reader.read_exact(&mut buf2)?;
let name_set = u16::from_le_bytes(buf2);
reader.read_exact(&mut buf2)?;
let scale = u16::from_le_bytes(buf2);
entries.push(ModfEntry {
id,
unique_id,
position,
rotation,
lower_bounds,
upper_bounds,
flags,
doodad_set,
name_set,
scale,
});
}
Ok(Self { entries })
}
fn write(&self, writer: &mut impl Write) -> Result<()> {
for entry in &self.entries {
writer.write_all(&entry.id.to_le_bytes())?;
writer.write_all(&entry.unique_id.to_le_bytes())?;
for &v in &entry.position {
writer.write_all(&v.to_le_bytes())?;
}
for &v in &entry.rotation {
writer.write_all(&v.to_le_bytes())?;
}
for &v in &entry.lower_bounds {
writer.write_all(&v.to_le_bytes())?;
}
for &v in &entry.upper_bounds {
writer.write_all(&v.to_le_bytes())?;
}
writer.write_all(&entry.flags.to_le_bytes())?;
writer.write_all(&entry.doodad_set.to_le_bytes())?;
writer.write_all(&entry.name_set.to_le_bytes())?;
writer.write_all(&entry.scale.to_le_bytes())?;
}
Ok(())
}
fn size(&self) -> usize {
self.entries.len() * 64
}
}