use crate::Error; use crate::Tag; use byteorder::{ReadBytesExt, BE}; use flate2::read::{GzDecoder, ZlibDecoder}; use std::collections::HashMap; use std::io::BufRead; use std::io::Read; #[inline] fn read_string(stream: &mut impl Read) -> Result { let length = stream.read_u16::()?; let mut buf = Vec::with_capacity(length as usize); for _ in 0..length { buf.push(stream.read_u8()?); } Ok(String::from_utf8(buf)?) } impl Tag { #[inline] fn read_known(stream: &mut impl Read, id: u8) -> Result { let tag = match id { // Signifies the end of a TAG_Compound. It is only ever used inside // a TAG_Compound, and is not named despite being in a TAG_Compound 0 => Tag::End, // A single signed byte 1 => Tag::Byte(stream.read_i8()?), // A single signed, big endian 16 bit integer 2 => Tag::Short(stream.read_i16::()?), // A single signed, big endian 32 bit integer 3 => Tag::Int(stream.read_i32::()?), // A single signed, big endian 64 bit integer 4 => Tag::Long(stream.read_i64::()?), // A single, big endian IEEE-754 single-precision floating point // number (NaN possible) 5 => Tag::Float(stream.read_f32::()?), // A single, big endian IEEE-754 double-precision floating point // number (NaN possible) 6 => Tag::Double(stream.read_f64::()?), // A length-prefixed array of signed bytes. The prefix is a signed // integer (thus 4 bytes) 7 => { let length = stream.read_i32::()?; let mut bytes = Vec::with_capacity(length as usize); for _ in 0..length { bytes.push(stream.read_i8()?); } Tag::ByteArray(bytes) } // A length-prefixed modified UTF-8 string. The prefix is an // unsigned short (thus 2 bytes) signifying the length of the // string in bytes 8 => Tag::String(read_string(stream)?), // A list of nameless tags, all of the same type. The list is // prefixed with the Type ID of the items it contains (thus 1 // byte), and the length of the list as a signed integer (a further // 4 bytes). If the length of the list is 0 or negative, the type // may be 0 (TAG_End) but otherwise it must be any other type. (The // notchian implementation uses TAG_End in that situation, but // another reference implementation by Mojang uses 1 instead; // parsers should accept any type if the length is <= 0). 9 => { let type_id = stream.read_u8()?; let length = stream.read_i32::()?; let mut list = Vec::with_capacity(length as usize); for _ in 0..length { list.push(Tag::read_known(stream, type_id)?); } Tag::List(list) } // Effectively a list of a named tags. Order is not guaranteed. 10 => { // we default to capacity 4 because it'll probably not be empty let mut map = HashMap::with_capacity(4); loop { let tag_id = stream.read_u8().unwrap_or(0); if tag_id == 0 { break; } let name = read_string(stream)?; let tag = Tag::read_known(stream, tag_id)?; map.insert(name, tag); } Tag::Compound(map) } // A length-prefixed array of signed integers. The prefix is a // signed integer (thus 4 bytes) and indicates the number of 4 byte // integers. 11 => { let length = stream.read_i32::()?; let mut ints = Vec::with_capacity(length as usize); for _ in 0..length { ints.push(stream.read_i32::()?); } Tag::IntArray(ints) } // A length-prefixed array of signed longs. The prefix is a signed // integer (thus 4 bytes) and indicates the number of 8 byte longs. 12 => { let length = stream.read_i32::()?; let mut longs = Vec::with_capacity(length as usize); for _ in 0..length { longs.push(stream.read_i64::()?); } Tag::LongArray(longs) } _ => return Err(Error::InvalidTagType(id)), }; Ok(tag) } pub fn read(stream: &mut impl Read) -> Result { // default to compound tag // the parent compound only ever has one item let tag_id = stream.read_u8().unwrap_or(0); if tag_id == 0 { return Ok(Tag::End); } let name = read_string(stream)?; let tag = Tag::read_known(stream, tag_id)?; let mut map = HashMap::with_capacity(1); map.insert(name, tag); Ok(Tag::Compound(map)) } pub fn read_zlib(stream: &mut impl BufRead) -> Result { let mut gz = ZlibDecoder::new(stream); Tag::read(&mut gz) } pub fn read_gzip(stream: &mut impl Read) -> Result { let mut gz = GzDecoder::new(stream); Tag::read(&mut gz) } } impl McBufReadable for Tag { fn read_into(buf: &mut impl Read) -> Result { match Tag::read(self) { Ok(r) => Ok(r), // Err(e) => Err(e.to_string()), Err(e) => Err(e.to_string()).unwrap(), } } }