Skip to main content

hexomc_lib/assets/
nbt.rs

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()?; // Root node name, unused
153    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]; // TAG_Compound, empty name
162        out.extend([9]); // TAG_List
163        out.extend((b"servers".len() as i16).to_be_bytes());
164        out.extend(b"servers");
165        out.extend([10]); // Element type TAG_Compound
166        out.extend(1i32.to_be_bytes());
167
168        for (key, value) in [("name", "Home"), ("ip", "127.0.0.1")] {
169            out.extend([8]); // TAG_String
170            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]); // End of element
176        out.extend([0]); // End of root node
177        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}