1use std::collections::HashMap;
2use std::io::Read;
3
4#[allow(dead_code)]
5#[derive(Debug, Clone)]
6pub enum Nbt {
7 Byte(i8),
8 Short(i16),
9 Int(i32),
10 Long(i64),
11 Float(f32),
12 Double(f64),
13 ByteArray(Vec<u8>),
14 String(String),
15 List(Vec<Nbt>),
16 Compound(HashMap<String, Nbt>),
17 IntArray(Vec<i32>),
18 LongArray(Vec<i64>),
19}
20
21impl Nbt {
22 pub fn get(&self, key: &str) -> Option<&Nbt> {
23 match self {
24 Nbt::Compound(map) => map.get(key),
25 _ => None,
26 }
27 }
28
29 pub fn as_str(&self) -> Option<&str> {
30 match self {
31 Nbt::String(s) => Some(s),
32 _ => None,
33 }
34 }
35
36 pub fn as_list(&self) -> Option<&[Nbt]> {
37 match self {
38 Nbt::List(items) => Some(items),
39 _ => None,
40 }
41 }
42}
43
44struct Reader<'a> {
45 data: &'a [u8],
46 pos: usize,
47}
48
49impl<'a> Reader<'a> {
50 fn take(&mut self, n: usize) -> Option<&'a [u8]> {
51 let end = self.pos.checked_add(n)?;
52 let slice = self.data.get(self.pos..end)?;
53 self.pos = end;
54 Some(slice)
55 }
56
57 fn u8(&mut self) -> Option<u8> {
58 Some(self.take(1)?[0])
59 }
60
61 fn i16(&mut self) -> Option<i16> {
62 Some(i16::from_be_bytes(self.take(2)?.try_into().ok()?))
63 }
64
65 fn i32(&mut self) -> Option<i32> {
66 Some(i32::from_be_bytes(self.take(4)?.try_into().ok()?))
67 }
68
69 fn i64(&mut self) -> Option<i64> {
70 Some(i64::from_be_bytes(self.take(8)?.try_into().ok()?))
71 }
72
73 fn string(&mut self) -> Option<String> {
74 let len = self.i16()? as usize;
75
76 Some(String::from_utf8_lossy(self.take(len)?).to_string())
77 }
78
79 fn value(&mut self, tag: u8) -> Option<Nbt> {
80 Some(match tag {
81 1 => Nbt::Byte(self.u8()? as i8),
82 2 => Nbt::Short(self.i16()?),
83 3 => Nbt::Int(self.i32()?),
84 4 => Nbt::Long(self.i64()?),
85 5 => Nbt::Float(f32::from_bits(self.i32()? as u32)),
86 6 => Nbt::Double(f64::from_bits(self.i64()? as u64)),
87 7 => {
88 let len = self.i32()?.max(0) as usize;
89 Nbt::ByteArray(self.take(len)?.to_vec())
90 }
91 8 => Nbt::String(self.string()?),
92 9 => {
93 let item_tag = self.u8()?;
94 let len = self.i32()?.max(0) as usize;
95 let mut items = Vec::with_capacity(len.min(1024));
96 for _ in 0..len {
97 items.push(self.value(item_tag)?);
98 }
99 Nbt::List(items)
100 }
101 10 => {
102 let mut map = HashMap::new();
103 loop {
104 let child = self.u8()?;
105 if child == 0 {
106 break;
107 }
108 let name = self.string()?;
109 map.insert(name, self.value(child)?);
110 }
111 Nbt::Compound(map)
112 }
113 11 => {
114 let len = self.i32()?.max(0) as usize;
115 let mut items = Vec::with_capacity(len.min(1024));
116 for _ in 0..len {
117 items.push(self.i32()?);
118 }
119 Nbt::IntArray(items)
120 }
121 12 => {
122 let len = self.i32()?.max(0) as usize;
123 let mut items = Vec::with_capacity(len.min(1024));
124 for _ in 0..len {
125 items.push(self.i64()?);
126 }
127 Nbt::LongArray(items)
128 }
129 _ => return None,
130 })
131 }
132}
133
134pub fn parse(bytes: &[u8]) -> Option<Nbt> {
135 let owned;
136 let data = if bytes.starts_with(&[0x1f, 0x8b]) {
137 let mut out = Vec::new();
138 flate2::read::GzDecoder::new(bytes)
139 .read_to_end(&mut out)
140 .ok()?;
141 owned = out;
142 owned.as_slice()
143 } else {
144 bytes
145 };
146
147 let mut reader = Reader { data, pos: 0 };
148 let tag = reader.u8()?;
149 if tag != 10 {
150 return None;
151 }
152 reader.string()?; reader.value(10)
154}
155
156#[cfg(test)]
157mod tests {
158 use super::*;
159
160 fn sample() -> Vec<u8> {
161 let mut out = vec![10, 0, 0]; out.extend([9]); out.extend((b"servers".len() as i16).to_be_bytes());
164 out.extend(b"servers");
165 out.extend([10]); out.extend(1i32.to_be_bytes());
167
168 for (key, value) in [("name", "Home"), ("ip", "127.0.0.1")] {
169 out.extend([8]); out.extend((key.len() as i16).to_be_bytes());
171 out.extend(key.as_bytes());
172 out.extend((value.len() as i16).to_be_bytes());
173 out.extend(value.as_bytes());
174 }
175 out.extend([0]); out.extend([0]); out
178 }
179
180 #[test]
181 fn reads_server_entries() {
182 let root = parse(&sample()).expect("should parse");
183 let servers = root.get("servers").and_then(Nbt::as_list).expect("servers");
184
185 assert_eq!(servers.len(), 1);
186 assert_eq!(servers[0].get("name").and_then(Nbt::as_str), Some("Home"));
187 assert_eq!(
188 servers[0].get("ip").and_then(Nbt::as_str),
189 Some("127.0.0.1")
190 );
191 }
192
193 #[test]
194 fn broken_data_returns_none() {
195 assert!(parse(b"not nbt at all").is_none());
196 assert!(parse(&sample()[..8]).is_none());
197 }
198}