Skip to main content

hypixel/util/
nbt.rs

1use std::io::Read;
2
3use base64::Engine;
4use flate2::read::GzDecoder;
5use serde_json::Value;
6
7/// Errors raised while decoding base64+gzip NBT blobs.
8#[derive(Debug, thiserror::Error)]
9#[non_exhaustive]
10pub enum NbtError {
11    /// The input was not valid base64.
12    #[error("invalid base64: {0}")]
13    Base64(#[from] base64::DecodeError),
14    /// The gzip stream could not be inflated.
15    #[error("gzip decompression failed: {0}")]
16    Gzip(#[source] std::io::Error),
17    /// The decompressed bytes were not valid NBT.
18    #[error("invalid NBT: {0}")]
19    Nbt(#[from] fastnbt::error::Error),
20    /// The expected `data` field was missing from an inventory blob.
21    #[error("no NBT data field present")]
22    MissingData,
23}
24
25/// Decode a base64-encoded, gzip-compressed NBT blob into an [`fastnbt::Value`].
26///
27/// SkyBlock serializes inventories and auction items this way (the `item_bytes`
28/// field on auctions, and the `data` field within member inventory sections).
29pub fn decode_nbt(encoded: &str) -> Result<fastnbt::Value, NbtError> {
30    let compressed = base64::engine::general_purpose::STANDARD.decode(encoded.trim())?;
31    let mut decoder = GzDecoder::new(&compressed[..]);
32    let mut bytes = Vec::new();
33    decoder.read_to_end(&mut bytes).map_err(NbtError::Gzip)?;
34    Ok(fastnbt::from_bytes(&bytes)?)
35}
36
37/// Decode an inventory section such as `{ "type": 0, "data": "<base64>" }`.
38///
39/// Accepts either that object form or a bare base64 string.
40pub fn decode_inventory(section: &Value) -> Result<fastnbt::Value, NbtError> {
41    let encoded = match section {
42        Value::String(s) => s.as_str(),
43        Value::Object(map) => map
44            .get("data")
45            .and_then(Value::as_str)
46            .ok_or(NbtError::MissingData)?,
47        _ => return Err(NbtError::MissingData),
48    };
49    decode_nbt(encoded)
50}