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#[derive(Debug)]
10pub enum TagDecodeError {
11 RootMustBeCompoundTag {
13 actual_tag: Tag,
15 },
16 UnknownTagType {
18 tag_type_id: u8,
20 },
21 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
54pub fn read_gzip_compound_tag<R: Read>(reader: &mut R) -> Result<CompoundTag, TagDecodeError> {
56 read_compound_tag(&mut GzDecoder::new(reader))
57}
58
59pub fn read_zlib_compound_tag<R: Read>(reader: &mut R) -> Result<CompoundTag, TagDecodeError> {
61 read_compound_tag(&mut ZlibDecoder::new(reader))
62}
63
64pub 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 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}