use crate::data::{Compression, NBTFile, NBT};
use crate::Result;
use std::io::Write;
use byteorder::{BigEndian, WriteBytesExt};
use flate2::write::{GzEncoder, ZlibEncoder};
use anyhow::bail;
macro_rules! compression_level {
() => {
flate2::Compression::default()
};
}
pub fn write_file<W: Write>(w: &mut W, file: &NBTFile) -> Result<()> {
let map = match file.root {
NBT::Compound(ref x) => x,
_ => unreachable!(),
};
match file.compression {
Compression::None => write_compound(w, map, false)?,
Compression::Gzip => {
let mut w = GzEncoder::new(w, compression_level!());
write_compound(&mut w, map, false)?;
let _: &mut W = w.finish()?;
}
Compression::Zlib => {
let mut w = ZlibEncoder::new(w, compression_level!());
write_compound(&mut w, map, false)?;
let _: &mut W = w.finish()?;
}
}
Ok(())
}
fn write_tag<W: Write>(w: &mut W, tag: &NBT) -> Result<()> {
match *tag {
NBT::End => bail!("Unable to write End tag"),
NBT::Byte(x) => write_byte(w, x),
NBT::Short(x) => write_short(w, x),
NBT::Int(x) => write_int(w, x),
NBT::Long(x) => write_long(w, x),
NBT::Float(x) => write_float(w, x),
NBT::Double(x) => write_double(w, x),
NBT::ByteArray(ref x) => write_byte_array(w, x),
NBT::String(ref x) => write_string(w, x),
NBT::List(ref x) => write_list(w, x),
NBT::Compound(ref x) => write_compound(w, x, true),
NBT::IntArray(ref x) => write_int_array(w, x),
NBT::LongArray(ref x) => write_long_array(w, x),
}
}
fn write_byte<W: Write>(w: &mut W, val: i8) -> Result<()> {
w.write_i8(val).map_err(|e| e.into())
}
fn write_short<W: Write>(w: &mut W, val: i16) -> Result<()> {
w.write_i16::<BigEndian>(val).map_err(|e| e.into())
}
fn write_int<W: Write>(w: &mut W, val: i32) -> Result<()> {
w.write_i32::<BigEndian>(val).map_err(|e| e.into())
}
fn write_long<W: Write>(w: &mut W, val: i64) -> Result<()> {
w.write_i64::<BigEndian>(val).map_err(|e| e.into())
}
fn write_float<W: Write>(w: &mut W, val: f32) -> Result<()> {
w.write_f32::<BigEndian>(val).map_err(|e| e.into())
}
fn write_double<W: Write>(w: &mut W, val: f64) -> Result<()> {
w.write_f64::<BigEndian>(val).map_err(|e| e.into())
}
fn write_byte_array<W: Write>(w: &mut W, val: &[i8]) -> Result<()> {
write_int(w, val.len() as i32)?;
for x in val {
write_byte(w, *x)?;
}
Ok(())
}
fn write_string<W: Write>(w: &mut W, val: &[u8]) -> Result<()> {
let bytes = &val;
w.write_u16::<BigEndian>(bytes.len() as u16)?;
w.write_all(bytes).map_err(|e| e.into())
}
fn write_list<W: Write>(w: &mut W, val: &[NBT]) -> Result<()> {
#[rustfmt::skip]
let tag_type = if val.is_empty() {
0
} else {
val[0].type_byte()
};
w.write_all(&[tag_type])?;
write_int(w, val.len() as i32)?;
for tag in val {
write_tag(w, tag)?;
}
Ok(())
}
fn write_compound<W: Write>(w: &mut W, map: &[(Vec<u8>, NBT)], end: bool) -> Result<()> {
for (key, tag) in map {
w.write_all(&[tag.type_byte()])?;
write_string(w, key)?;
write_tag(w, tag)?;
}
if end {
w.write_all(&[0])?;
}
Ok(())
}
fn write_int_array<W: Write>(w: &mut W, val: &[i32]) -> Result<()> {
write_int(w, val.len() as i32)?;
for x in val {
write_int(w, *x)?;
}
Ok(())
}
fn write_long_array<W: Write>(w: &mut W, val: &[i64]) -> Result<()> {
write_int(w, val.len() as i32)?;
for x in val {
write_long(w, *x)?;
}
Ok(())
}