use std::io::{Seek, SeekFrom, Write};
use crate::Adt;
use crate::error::Result;
use crate::io_helpers::WriteLittleEndian;
use crate::mcnk_writer;
use crate::version::AdtVersion;
fn write_chunk_header<W: Write>(writer: &mut W, magic: &[u8; 4], size: u32) -> Result<()> {
let mut reversed_magic = *magic;
reversed_magic.reverse();
writer.write_all(&reversed_magic)?;
writer.write_u32_le(size)?;
Ok(())
}
#[derive(Default, Debug)]
struct OffsetTracker {
mver: Option<u32>,
mhdr: Option<u32>,
mcin: Option<u32>,
mtex: Option<u32>,
mmdx: Option<u32>,
mmid: Option<u32>,
mwmo: Option<u32>,
mwid: Option<u32>,
mddf: Option<u32>,
modf: Option<u32>,
mfbo: Option<u32>,
mh2o: Option<u32>,
mtfx: Option<u32>,
mcnk: Vec<u32>,
}
impl Adt {
pub fn write<W: Write + Seek>(&self, writer: &mut W) -> Result<()> {
let mut offset_tracker = OffsetTracker::default();
self.write_mver(writer, &mut offset_tracker)?;
let mhdr_pos = writer.stream_position()?;
offset_tracker.mhdr = Some(mhdr_pos as u32);
let mhdr_size = 36 + if self.version >= AdtVersion::TBC { 4 } else { 0 }
+ if self.version >= AdtVersion::WotLK { 4 } else { 0 }
+ if self.version >= AdtVersion::Cataclysm { 4 } else { 0 };
write_chunk_header(writer, b"MHDR", mhdr_size)?;
writer.write_all(&vec![0; mhdr_size as usize])?;
self.write_mcin(writer, &mut offset_tracker)?;
self.write_mtex(writer, &mut offset_tracker)?;
self.write_mmdx(writer, &mut offset_tracker)?;
self.write_mmid(writer, &mut offset_tracker)?;
self.write_mwmo(writer, &mut offset_tracker)?;
self.write_mwid(writer, &mut offset_tracker)?;
self.write_mddf(writer, &mut offset_tracker)?;
self.write_modf(writer, &mut offset_tracker)?;
if self.version >= AdtVersion::TBC {
self.write_mfbo(writer, &mut offset_tracker)?;
}
if self.version >= AdtVersion::WotLK {
self.write_mh2o(writer, &mut offset_tracker)?;
}
if self.version >= AdtVersion::Cataclysm {
self.write_mtfx(writer, &mut offset_tracker)?;
}
self.write_mcnks(writer, &mut offset_tracker)?;
writer.seek(SeekFrom::Start(mhdr_pos))?;
self.write_mhdr(writer, &offset_tracker)?;
Ok(())
}
fn write_mver<W: Write + Seek>(
&self,
writer: &mut W,
offsets: &mut OffsetTracker,
) -> Result<()> {
let pos = writer.stream_position()? as u32;
offsets.mver = Some(pos);
write_chunk_header(writer, b"MVER", 4)?;
writer.write_u32_le(self.version.to_mver_value())?;
Ok(())
}
fn write_mhdr<W: Write + Seek>(&self, writer: &mut W, offsets: &OffsetTracker) -> Result<()> {
let base_size = 36; let mut size = base_size;
if self.version >= AdtVersion::TBC {
size += 4; }
if self.version >= AdtVersion::WotLK {
size += 4; }
if self.version >= AdtVersion::Cataclysm {
size += 4; }
write_chunk_header(writer, b"MHDR", size)?;
let flags = self.mhdr.as_ref().map_or(0, |h| h.flags);
writer.write_u32_le(flags)?;
writer.write_u32_le(offsets.mcin.unwrap_or(0))?;
writer.write_u32_le(offsets.mtex.unwrap_or(0))?;
writer.write_u32_le(offsets.mmdx.unwrap_or(0))?;
writer.write_u32_le(offsets.mmid.unwrap_or(0))?;
writer.write_u32_le(offsets.mwmo.unwrap_or(0))?;
writer.write_u32_le(offsets.mwid.unwrap_or(0))?;
writer.write_u32_le(offsets.mddf.unwrap_or(0))?;
writer.write_u32_le(offsets.modf.unwrap_or(0))?;
if self.version >= AdtVersion::TBC {
writer.write_u32_le(offsets.mfbo.unwrap_or(0))?;
}
if self.version >= AdtVersion::WotLK {
writer.write_u32_le(offsets.mh2o.unwrap_or(0))?;
}
if self.version >= AdtVersion::Cataclysm {
writer.write_u32_le(offsets.mtfx.unwrap_or(0))?;
}
Ok(())
}
fn write_mcin<W: Write + Seek>(
&self,
writer: &mut W,
offsets: &mut OffsetTracker,
) -> Result<()> {
if let Some(ref mcin) = self.mcin {
let pos = writer.stream_position()? as u32;
offsets.mcin = Some(pos);
write_chunk_header(writer, b"MCIN", 4096)?;
for entry in &mcin.entries {
writer.write_u32_le(entry.offset)?;
writer.write_u32_le(entry.size)?;
writer.write_u32_le(entry.flags)?;
writer.write_u32_le(entry.async_id)?;
}
}
Ok(())
}
fn write_mtex<W: Write + Seek>(
&self,
writer: &mut W,
offsets: &mut OffsetTracker,
) -> Result<()> {
if let Some(ref mtex) = self.mtex {
let pos = writer.stream_position()? as u32;
offsets.mtex = Some(pos);
let mut size = 0;
for filename in &mtex.filenames {
size += filename.len() + 1; }
write_chunk_header(writer, b"MTEX", size as u32)?;
for filename in &mtex.filenames {
writer.write_all(filename.as_bytes())?;
writer.write_u8(0)?; }
}
Ok(())
}
fn write_mmdx<W: Write + Seek>(
&self,
writer: &mut W,
offsets: &mut OffsetTracker,
) -> Result<()> {
if let Some(ref mmdx) = self.mmdx {
let pos = writer.stream_position()? as u32;
offsets.mmdx = Some(pos);
let mut size = 0;
for filename in &mmdx.filenames {
size += filename.len() + 1; }
write_chunk_header(writer, b"MMDX", size as u32)?;
for filename in &mmdx.filenames {
writer.write_all(filename.as_bytes())?;
writer.write_u8(0)?; }
}
Ok(())
}
fn write_mmid<W: Write + Seek>(
&self,
writer: &mut W,
offsets: &mut OffsetTracker,
) -> Result<()> {
if let Some(ref mmid) = self.mmid {
let pos = writer.stream_position()? as u32;
offsets.mmid = Some(pos);
let size = mmid.offsets.len() * 4;
write_chunk_header(writer, b"MMID", size as u32)?;
for offset in &mmid.offsets {
writer.write_u32_le(*offset)?;
}
}
Ok(())
}
fn write_mwmo<W: Write + Seek>(
&self,
writer: &mut W,
offsets: &mut OffsetTracker,
) -> Result<()> {
if let Some(ref mwmo) = self.mwmo {
let pos = writer.stream_position()? as u32;
offsets.mwmo = Some(pos);
let mut size = 0;
for filename in &mwmo.filenames {
size += filename.len() + 1; }
write_chunk_header(writer, b"MWMO", size as u32)?;
for filename in &mwmo.filenames {
writer.write_all(filename.as_bytes())?;
writer.write_u8(0)?; }
}
Ok(())
}
fn write_mwid<W: Write + Seek>(
&self,
writer: &mut W,
offsets: &mut OffsetTracker,
) -> Result<()> {
if let Some(ref mwid) = self.mwid {
let pos = writer.stream_position()? as u32;
offsets.mwid = Some(pos);
let size = mwid.offsets.len() * 4;
write_chunk_header(writer, b"MWID", size as u32)?;
for offset in &mwid.offsets {
writer.write_u32_le(*offset)?;
}
}
Ok(())
}
fn write_mddf<W: Write + Seek>(
&self,
writer: &mut W,
offsets: &mut OffsetTracker,
) -> Result<()> {
if let Some(ref mddf) = self.mddf {
let pos = writer.stream_position()? as u32;
offsets.mddf = Some(pos);
let size = mddf.doodads.len() * 36;
write_chunk_header(writer, b"MDDF", size as u32)?;
for doodad in &mddf.doodads {
writer.write_u32_le(doodad.name_id)?;
writer.write_u32_le(doodad.unique_id)?;
for i in 0..3 {
writer.write_f32_le(doodad.position[i])?;
}
for i in 0..3 {
writer.write_f32_le(doodad.rotation[i])?;
}
let scale_u16 = (doodad.scale * 1024.0) as u16;
writer.write_u16_le(scale_u16)?;
writer.write_u16_le(doodad.flags)?;
}
}
Ok(())
}
fn write_modf<W: Write + Seek>(
&self,
writer: &mut W,
offsets: &mut OffsetTracker,
) -> Result<()> {
if let Some(ref modf) = self.modf {
let pos = writer.stream_position()? as u32;
offsets.modf = Some(pos);
let size = modf.models.len() * 64;
write_chunk_header(writer, b"MODF", size as u32)?;
for model in &modf.models {
writer.write_u32_le(model.name_id)?;
writer.write_u32_le(model.unique_id)?;
for i in 0..3 {
writer.write_f32_le(model.position[i])?;
}
for i in 0..3 {
writer.write_f32_le(model.rotation[i])?;
}
for i in 0..3 {
writer.write_f32_le(model.bounds_min[i])?;
}
for i in 0..3 {
writer.write_f32_le(model.bounds_max[i])?;
}
writer.write_u16_le(model.flags)?;
writer.write_u16_le(model.doodad_set)?;
writer.write_u16_le(model.name_set)?;
writer.write_u16_le(model.padding)?;
}
}
Ok(())
}
fn write_mfbo<W: Write + Seek>(
&self,
writer: &mut W,
offsets: &mut OffsetTracker,
) -> Result<()> {
if let Some(ref mfbo) = self.mfbo {
let pos = writer.stream_position()? as u32;
offsets.mfbo = Some(pos);
write_chunk_header(writer, b"MFBO", 36)?;
for i in 0..9 {
writer.write_i16_le(mfbo.max[i])?;
}
for i in 0..9 {
writer.write_i16_le(mfbo.min[i])?;
}
}
Ok(())
}
fn write_mh2o<W: Write + Seek>(
&self,
writer: &mut W,
offsets: &mut OffsetTracker,
) -> Result<()> {
if let Some(ref _mh2o) = self.mh2o {
let pos = writer.stream_position()? as u32;
offsets.mh2o = Some(pos);
let header_size = 256 * 24;
write_chunk_header(writer, b"MH2O", header_size as u32)?;
for _ in 0..256 {
writer.write_all(&[0; 24])?;
}
}
Ok(())
}
fn write_mtfx<W: Write + Seek>(
&self,
writer: &mut W,
offsets: &mut OffsetTracker,
) -> Result<()> {
if let Some(ref mtfx) = self.mtfx {
let pos = writer.stream_position()? as u32;
offsets.mtfx = Some(pos);
let size = mtfx.effects.len() * 4;
write_chunk_header(writer, b"MTFX", size as u32)?;
for effect in &mtfx.effects {
writer.write_u32_le(effect.raw_flags)?;
}
}
Ok(())
}
fn write_mcnks<W: Write + Seek>(
&self,
writer: &mut W,
offsets: &mut OffsetTracker,
) -> Result<()> {
offsets.mcnk.clear();
let mut mcnk_entries = Vec::new();
for mcnk in &self.mcnk_chunks {
let (pos, size) = mcnk_writer::write_mcnk(writer, mcnk, self.version)?;
offsets.mcnk.push(pos);
mcnk_entries.push((pos, size));
}
if self.mcin.is_some() && offsets.mcin.is_some() {
let mcin_pos = offsets.mcin.unwrap();
let current_pos = writer.stream_position()?;
writer.seek(SeekFrom::Start((mcin_pos + 8) as u64))?;
for (i, (pos, size)) in mcnk_entries.iter().enumerate() {
if i < 256 {
writer.write_u32_le(*pos)?;
writer.write_u32_le(*size)?;
writer.write_u32_le(0)?; writer.write_u32_le(0)?; }
}
writer.seek(SeekFrom::Start(current_pos))?;
}
Ok(())
}
}