Skip to main content

basalt_types/nbt/
encode.rs

1use crate::nbt::tag::{NbtCompound, NbtTag, tag_id};
2use crate::{Encode, EncodedSize, Result};
3
4/// Writes an NBT string in the NBT wire format (u16-prefixed modified UTF-8).
5///
6/// NBT strings use a big-endian u16 length prefix, not VarInt like protocol
7/// strings. This is a deliberate difference between the NBT format and the
8/// outer protocol string encoding.
9fn encode_nbt_string(s: &str, buf: &mut Vec<u8>) -> Result<()> {
10    let bytes = s.as_bytes();
11    (bytes.len() as u16).encode(buf)?;
12    buf.extend_from_slice(bytes);
13    Ok(())
14}
15
16/// Computes the wire size of an NBT string (u16 prefix + UTF-8 bytes).
17fn nbt_string_size(s: &str) -> usize {
18    2 + s.len()
19}
20
21/// Encodes only the payload of an NBT tag (without the type byte or name).
22///
23/// This is used internally for list elements (which share a single type
24/// byte for the whole list) and for compound entry values (where the type
25/// byte and name are written separately).
26fn encode_tag_payload(tag: &NbtTag, buf: &mut Vec<u8>) -> Result<()> {
27    match tag {
28        NbtTag::Byte(v) => v.encode(buf),
29        NbtTag::Short(v) => v.encode(buf),
30        NbtTag::Int(v) => v.encode(buf),
31        NbtTag::Long(v) => v.encode(buf),
32        NbtTag::Float(v) => v.encode(buf),
33        NbtTag::Double(v) => v.encode(buf),
34        NbtTag::ByteArray(v) => {
35            (v.len() as i32).encode(buf)?;
36            for &b in v {
37                b.encode(buf)?;
38            }
39            Ok(())
40        }
41        NbtTag::String(v) => encode_nbt_string(v, buf),
42        NbtTag::List(list) => {
43            list.element_type.encode(buf)?;
44            (list.elements.len() as i32).encode(buf)?;
45            for elem in &list.elements {
46                encode_tag_payload(elem, buf)?;
47            }
48            Ok(())
49        }
50        NbtTag::Compound(compound) => {
51            encode_compound_payload(compound, buf)?;
52            Ok(())
53        }
54        NbtTag::IntArray(v) => {
55            (v.len() as i32).encode(buf)?;
56            for &val in v {
57                val.encode(buf)?;
58            }
59            Ok(())
60        }
61        NbtTag::LongArray(v) => {
62            (v.len() as i32).encode(buf)?;
63            for &val in v {
64                val.encode(buf)?;
65            }
66            Ok(())
67        }
68    }
69}
70
71/// Encodes the payload of a compound tag: each named entry followed by an End tag.
72///
73/// Each entry is: tag type byte + u16-prefixed name + payload.
74/// The compound is terminated by a single End tag (0x00).
75fn encode_compound_payload(compound: &NbtCompound, buf: &mut Vec<u8>) -> Result<()> {
76    for (name, tag) in compound.iter() {
77        tag.tag_id().encode(buf)?;
78        encode_nbt_string(name, buf)?;
79        encode_tag_payload(tag, buf)?;
80    }
81    tag_id::END.encode(buf)?;
82    Ok(())
83}
84
85/// Computes the wire size of an NBT tag payload (without type byte or name).
86fn tag_payload_size(tag: &NbtTag) -> usize {
87    match tag {
88        NbtTag::Byte(_) => 1,
89        NbtTag::Short(_) => 2,
90        NbtTag::Int(_) => 4,
91        NbtTag::Long(_) => 8,
92        NbtTag::Float(_) => 4,
93        NbtTag::Double(_) => 8,
94        NbtTag::ByteArray(v) => 4 + v.len(),
95        NbtTag::String(v) => nbt_string_size(v),
96        NbtTag::List(list) => {
97            // element type (1) + count (4) + payloads
98            1 + 4 + list.elements.iter().map(tag_payload_size).sum::<usize>()
99        }
100        NbtTag::Compound(compound) => compound_payload_size(compound),
101        NbtTag::IntArray(v) => 4 + v.len() * 4,
102        NbtTag::LongArray(v) => 4 + v.len() * 8,
103    }
104}
105
106/// Computes the wire size of a compound payload (entries + End tag).
107fn compound_payload_size(compound: &NbtCompound) -> usize {
108    let entries_size: usize = compound
109        .iter()
110        .map(|(name, tag)| {
111            // type byte (1) + name (u16 prefix + bytes) + payload
112            1 + nbt_string_size(name) + tag_payload_size(tag)
113        })
114        .sum();
115    entries_size + 1 // +1 for the End tag
116}
117
118/// Encodes an NbtCompound as a network NBT root compound.
119///
120/// Since Minecraft 1.20.3, network NBT uses a simplified root format:
121/// a compound tag type byte (0x0A) followed directly by the compound
122/// payload (no root tag name). This differs from the traditional NBT
123/// format which includes a root tag name after the type byte.
124///
125/// This encoder produces the network NBT format used in modern protocol
126/// packets (chat components, item stacks, registry data).
127impl Encode for NbtCompound {
128    /// Writes the compound type byte (0x0A) followed by the compound payload.
129    fn encode(&self, buf: &mut Vec<u8>) -> Result<()> {
130        tag_id::COMPOUND.encode(buf)?;
131        encode_compound_payload(self, buf)
132    }
133}
134
135/// Computes the wire size of a network NBT root compound.
136///
137/// Includes the compound type byte (1) plus the compound payload size
138/// (entries + End tag).
139impl EncodedSize for NbtCompound {
140    /// Returns 1 (type byte) + payload size.
141    fn encoded_size(&self) -> usize {
142        1 + compound_payload_size(self)
143    }
144}
145
146/// Encodes a single NbtTag as a network NBT root.
147///
148/// Only `NbtTag::Compound` is valid as a network NBT root. This delegates
149/// to the `NbtCompound` encoder for compound tags. Other tag types are
150/// wrapped in a single-entry compound for compatibility, though in practice
151/// the protocol always uses compound roots.
152impl Encode for NbtTag {
153    /// Writes the tag as a network NBT root compound.
154    fn encode(&self, buf: &mut Vec<u8>) -> Result<()> {
155        match self {
156            NbtTag::Compound(compound) => compound.encode(buf),
157            _ => {
158                // Wrap non-compound tags in a compound with an empty-string key
159                let mut wrapper = NbtCompound::new();
160                wrapper.insert("", self.clone());
161                wrapper.encode(buf)
162            }
163        }
164    }
165}
166
167/// Computes the wire size of an NbtTag as a network NBT root.
168impl EncodedSize for NbtTag {
169    fn encoded_size(&self) -> usize {
170        match self {
171            NbtTag::Compound(compound) => compound.encoded_size(),
172            _ => {
173                let mut wrapper = NbtCompound::new();
174                wrapper.insert("", self.clone());
175                wrapper.encoded_size()
176            }
177        }
178    }
179}