nbt/
decode.rs

1use crate::{CompoundTag, Tag};
2use byteorder::{BigEndian, ReadBytesExt};
3use flate2::read::{GzDecoder, ZlibDecoder};
4use linked_hash_map::LinkedHashMap;
5use std::{error::Error, io::Read};
6use std::{fmt::Display, io};
7
8/// Possible types of errors while decoding tag.
9#[derive(Debug)]
10pub enum TagDecodeError {
11    /// Root of tag must be compound tag.
12    RootMustBeCompoundTag {
13        /// Actual tag.
14        actual_tag: Tag,
15    },
16    /// Tag type not recognized.
17    UnknownTagType {
18        /// Tag type id which is not recognized.
19        tag_type_id: u8,
20    },
21    /// I/O Error which happened while were decoding.
22    IOError { io_error: io::Error },
23}
24
25impl From<io::Error> for TagDecodeError {
26    fn from(io_error: io::Error) -> Self {
27        TagDecodeError::IOError { io_error }
28    }
29}
30
31impl Error for TagDecodeError {
32    fn source(&self) -> Option<&(dyn Error + 'static)> {
33        match self {
34            TagDecodeError::IOError { io_error } => Some(io_error),
35            _ => None,
36        }
37    }
38}
39
40impl Display for TagDecodeError {
41    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
42        match self {
43            Self::RootMustBeCompoundTag { actual_tag } => write!(
44                f,
45                "Root must be a TAG_Compound but is a {}",
46                actual_tag.type_name()
47            ),
48            Self::UnknownTagType { tag_type_id } => write!(f, "Unknown tag type: {}", tag_type_id),
49            Self::IOError { .. } => write!(f, "IO Error"),
50        }
51    }
52}
53
54/// Read a compound tag from a reader compressed with gzip.
55pub fn read_gzip_compound_tag<R: Read>(reader: &mut R) -> Result<CompoundTag, TagDecodeError> {
56    read_compound_tag(&mut GzDecoder::new(reader))
57}
58
59/// Read a compound tag from a reader compressed with zlib.
60pub fn read_zlib_compound_tag<R: Read>(reader: &mut R) -> Result<CompoundTag, TagDecodeError> {
61    read_compound_tag(&mut ZlibDecoder::new(reader))
62}
63
64/// Read a compound tag from a reader.
65///
66/// # Example
67/// ```
68/// use nbt::decode::read_compound_tag;
69/// use std::io::Cursor;
70///
71/// let mut cursor = Cursor::new(include_bytes!("../test/binary/servers.dat").to_vec());
72/// let root_tag = read_compound_tag(&mut cursor).unwrap();
73///
74/// let servers = root_tag.get_compound_tag_vec("servers").unwrap();
75/// assert_eq!(servers.len(), 1);
76///
77/// let server = servers[0];
78/// let ip = server.get_str("ip").unwrap();
79/// let name = server.get_str("name").unwrap();
80/// let hide_address = server.get_bool("hideAddress").unwrap();
81///
82/// assert_eq!(ip, "localhost:25565");
83/// assert_eq!(name, "Minecraft Server");
84/// assert!(hide_address);
85/// ```
86pub fn read_compound_tag<'a, R: Read>(reader: &mut R) -> Result<CompoundTag, TagDecodeError> {
87    let tag_id = reader.read_u8()?;
88    let name = read_string(reader)?;
89    let tag = read_tag(tag_id, Some(name.as_str()), reader)?;
90
91    match tag {
92        Tag::Compound(value) => Ok(value),
93        actual_tag => Err(TagDecodeError::RootMustBeCompoundTag { actual_tag }),
94    }
95}
96
97fn read_tag<R: Read>(
98    tag_id: u8,
99    name: Option<&str>,
100    reader: &mut R,
101) -> Result<Tag, TagDecodeError> {
102    match tag_id {
103        1 => {
104            let value = reader.read_i8()?;
105
106            return Ok(Tag::Byte(value));
107        }
108        2 => {
109            let value = reader.read_i16::<BigEndian>()?;
110
111            return Ok(Tag::Short(value));
112        }
113        3 => {
114            let value = reader.read_i32::<BigEndian>()?;
115
116            return Ok(Tag::Int(value));
117        }
118        4 => {
119            let value = reader.read_i64::<BigEndian>()?;
120
121            return Ok(Tag::Long(value));
122        }
123        5 => {
124            let value = reader.read_f32::<BigEndian>()?;
125
126            return Ok(Tag::Float(value));
127        }
128        6 => {
129            let value = reader.read_f64::<BigEndian>()?;
130
131            return Ok(Tag::Double(value));
132        }
133        7 => {
134            let length = reader.read_u32::<BigEndian>()?;
135            let mut value = Vec::new();
136
137            for _ in 0..length {
138                value.push(reader.read_i8()?);
139            }
140
141            return Ok(Tag::ByteArray(value));
142        }
143        8 => {
144            let value = read_string(reader)?;
145
146            return Ok(Tag::String(value));
147        }
148        9 => {
149            let list_tags_id = reader.read_u8()?;
150            let length = reader.read_u32::<BigEndian>()?;
151            let mut value = Vec::new();
152
153            for _ in 0..length {
154                value.push(read_tag(list_tags_id, None, reader)?);
155            }
156
157            return Ok(Tag::List(value));
158        }
159        10 => {
160            let mut tags = LinkedHashMap::new();
161
162            loop {
163                let tag_id = reader.read_u8()?;
164
165                // Compound tag end reached.
166                if tag_id == 0 {
167                    break;
168                }
169
170                let name = read_string(reader)?;
171                let tag = read_tag(tag_id, Some(name.as_str()), reader)?;
172
173                tags.insert(name, tag);
174            }
175
176            let compound_tag = CompoundTag {
177                name: name.map(|s| s.into()),
178                tags,
179            };
180
181            return Ok(Tag::Compound(compound_tag));
182        }
183        11 => {
184            let length = reader.read_u32::<BigEndian>()?;
185            let mut value = Vec::new();
186
187            for _ in 0..length {
188                value.push(reader.read_i32::<BigEndian>()?);
189            }
190
191            return Ok(Tag::IntArray(value));
192        }
193        12 => {
194            let length = reader.read_u32::<BigEndian>()?;
195            let mut value = Vec::new();
196
197            for _ in 0..length {
198                value.push(reader.read_i64::<BigEndian>()?);
199            }
200
201            return Ok(Tag::LongArray(value));
202        }
203        tag_type_id => return Err(TagDecodeError::UnknownTagType { tag_type_id }),
204    }
205}
206
207fn read_string<R: Read>(reader: &mut R) -> Result<String, TagDecodeError> {
208    let length = reader.read_u16::<BigEndian>()?;
209    let mut buf = vec![0; length as usize];
210    reader.read_exact(&mut buf)?;
211
212    Ok(String::from_utf8_lossy(&buf).into_owned())
213}
214
215#[test]
216fn test_hello_world_read() {
217    use std::io::Cursor;
218
219    let mut cursor = Cursor::new(include_bytes!("../test/binary/hello_world.dat").to_vec());
220    let hello_world = read_compound_tag(&mut cursor).unwrap();
221
222    assert_eq!(hello_world.name.as_ref().unwrap(), "hello world");
223    assert_eq!(hello_world.get_str("name").unwrap(), "Bananrama");
224}
225
226#[test]
227fn test_servers_read() {
228    use std::io::Cursor;
229
230    let mut cursor = Cursor::new(include_bytes!("../test/binary/servers.dat").to_vec());
231    let root_tag = read_compound_tag(&mut cursor).unwrap();
232
233    assert!(root_tag.name.as_ref().unwrap().is_empty());
234    let servers = root_tag.get_compound_tag_vec("servers").unwrap();
235    assert_eq!(servers.len(), 1);
236
237    let server = servers[0];
238    let ip = server.get_str("ip").unwrap();
239    let name = server.get_str("name").unwrap();
240    let hide_address = server.get_bool("hideAddress").unwrap();
241
242    assert_eq!(ip, "localhost:25565");
243    assert_eq!(name, "Minecraft Server");
244    assert!(hide_address);
245}
246
247#[test]
248fn test_big_test_read() {
249    use std::io::Cursor;
250
251    let mut cursor = Cursor::new(include_bytes!("../test/binary/bigtest.dat").to_vec());
252    let root_tag = read_gzip_compound_tag(&mut cursor).unwrap();
253
254    assert_eq!(root_tag.name.as_ref().unwrap(), "Level");
255    assert_eq!(root_tag.get_i8("byteTest").unwrap(), i8::max_value());
256    assert_eq!(root_tag.get_i16("shortTest").unwrap(), i16::max_value());
257    assert_eq!(root_tag.get_i32("intTest").unwrap(), i32::max_value());
258    assert_eq!(root_tag.get_i64("longTest").unwrap(), i64::max_value());
259    assert_eq!(root_tag.get_f32("floatTest").unwrap(), 0.4982314705848694);
260    assert_eq!(root_tag.get_f64("doubleTest").unwrap(), 0.4931287132182315);
261    assert_eq!(
262        root_tag.get_str("stringTest").unwrap(),
263        "HELLO WORLD THIS IS A TEST STRING ÅÄÖ!"
264    );
265
266    let byte_array = root_tag.get_i8_vec("byteArrayTest (the first 1000 values of (n*n*255+n*7)%100, starting with n=0 (0, 62, 34, 16, 8, ...))").unwrap();
267
268    for i in 0..1000 {
269        let value = *byte_array.get(i).unwrap();
270        let expected = ((i * i * 255 + i * 7) % 100) as i8;
271
272        assert_eq!(value, expected);
273    }
274
275    let compound_tag_vec = root_tag
276        .get_compound_tag_vec("listTest (compound)")
277        .unwrap();
278
279    assert_eq!(compound_tag_vec.len(), 2);
280
281    let list_compound_tag_1 = compound_tag_vec[0];
282    let list_compound_tag_2 = compound_tag_vec[1];
283
284    assert_eq!(
285        list_compound_tag_1.get_str("name").unwrap(),
286        "Compound tag #0"
287    );
288    assert_eq!(
289        list_compound_tag_1.get_i64("created-on").unwrap(),
290        1264099775885
291    );
292    assert_eq!(
293        list_compound_tag_2.get_str("name").unwrap(),
294        "Compound tag #1"
295    );
296    assert_eq!(
297        list_compound_tag_2.get_i64("created-on").unwrap(),
298        1264099775885
299    );
300
301    let nested_compound_tag = root_tag.get_compound_tag("nested compound test").unwrap();
302    let egg_compound_tag = nested_compound_tag.get_compound_tag("egg").unwrap();
303    let ham_compound_tag = nested_compound_tag.get_compound_tag("ham").unwrap();
304
305    assert_eq!(egg_compound_tag.get_str("name").unwrap(), "Eggbert");
306    assert_eq!(egg_compound_tag.get_f32("value").unwrap(), 0.5);
307    assert_eq!(ham_compound_tag.get_str("name").unwrap(), "Hampus");
308    assert_eq!(ham_compound_tag.get_f32("value").unwrap(), 0.75);
309}