nbt/
encode.rs

1use crate::tags::{Tag, TagIdent};
2use crate::error::{NBTResult, NBTError, digest_io};
3
4use byteorder::{BigEndian as BE, WriteBytesExt};
5use std::io::Write;
6use std::collections::HashMap;
7
8
9pub(crate) fn write_tag<W: Write>(writer: &mut W, tag: &Tag) -> NBTResult<()>  {
10    match tag {
11        // Writing a Byte (i8)
12        Tag::Byte(byte) => digest_io(writer.write_i8(*byte)),
13
14        // Writing a Short (i16)
15        Tag::Short(short) => digest_io(writer.write_i16::<BE>(*short)),
16
17        // Writing a Int (i32)
18        Tag::Int(int) => digest_io(writer.write_i32::<BE>(*int)),
19
20        // Writing a Long(i64)
21        Tag::Long(long) => digest_io(writer.write_i64::<BE>(*long)),
22
23        // Writing a Float (f32)
24        Tag::Float(float) => digest_io(writer.write_f32::<BE>(*float)),
25
26        // Writing a Double (f64)
27        Tag::Double(double) => digest_io(writer.write_f64::<BE>(*double)),
28
29        // Writing an array of bytes (Vec<i8>)
30        Tag::ByteArray(bytes) => {
31            // Write length as a unsigned int. (4bytes)
32            digest_io(writer.write_u32::<BE>(bytes.len() as u32))?;
33
34            // Write items of array.
35            for byte in bytes {
36                digest_io(writer.write_i8(*byte))?;
37            }
38            Ok(())
39        }
40
41        // Write a string of utf-8 chars
42        Tag::String(string) => write_string(writer, &string),
43
44        Tag::List(list) => {
45            // Check the list is valid (all items are of the same type) and return the type prefix.
46            let list_type = ensure_list_integrity(&list)?;
47
48            // Write type prefix.
49            digest_io(writer.write_u8(list_type as u8))?;
50
51            // Write List length
52            digest_io(writer.write_u32::<BE>(list.len() as u32))?;
53
54            // Write items (without prefix)
55            for item in list {
56                write_tag(writer, &item)?;
57            }
58
59            Ok(())
60        }
61        Tag::Compound(compound) => write_compound(writer, compound),
62        Tag::IntArray(array) => {
63            // Write length as a unsigned int. (4bytes)
64            digest_io(writer.write_u32::<BE>(array.len() as u32))?;
65
66            // Write items of array.
67            for int in array {
68                digest_io(writer.write_i32::<BE>(*int))?;
69            }
70            Ok(())
71        }
72        Tag::LongArray(array) => {
73            // Write length as a unsigned int. (4bytes)
74            digest_io(writer.write_u32::<BE>(array.len() as u32))?;
75
76            // Write items of array.
77            for long in array {
78                digest_io(writer.write_i64::<BE>(*long))?;
79            }
80            Ok(())
81        }
82    }
83}
84
85
86// Function checks through items in a list to check if they are of the same type.
87pub(crate) fn ensure_list_integrity(list: &Vec<Tag>) -> NBTResult<TagIdent> {
88    // If list is empty, then type is TAG_End
89    if list.len() == 0 {
90        return Ok(TagIdent::TAG_End);
91    }
92
93    // Get first type.
94    // Should be safe to unwrap here as we know there will be at least one element in the list.
95    // We have ownership so it will never happen.
96    let tag = list.get(0).unwrap().ident();
97
98    // Loop through items
99    for item in list {
100        // Check
101        if item.ident() != tag {
102            // Error if user is bad at understanding nbt (like-me)
103            return Err(NBTError::InvalidList { found: item.ident(), expecting: tag })
104        }
105    }
106
107    Ok(tag)
108}
109
110// String writer.
111// Strings are written the same way multiple times so this function exists.
112pub(crate) fn write_string<W: Write>(writer: &mut W, string: &str) -> NBTResult<()> {
113    // Get the UTF-8 bytes of the string
114    let bytes = encode_wonky_string(string);
115
116    // Write length of string
117    digest_io(writer.write_u16::<BE>(bytes.len() as u16))?;
118
119    // Write the string.
120    digest_io(writer.write_all(&bytes))
121}
122
123// Function for writing a root compound (implicit compound)
124pub(crate) fn write_root<W: Write>(writer: &mut W, name: &str, elements: &HashMap<String, Tag>) -> NBTResult<()> {
125    // Write implicit compound ident prefix.
126    digest_io(writer.write_u8(TagIdent::TAG_Compound as u8))?;
127
128    // Write root compound name
129    write_string(writer, &name)?;
130
131    // Write elements
132    write_compound(writer, elements)
133}
134
135pub(crate) fn write_compound<W: Write>(writer: &mut W, compound: &HashMap<String, Tag>) -> NBTResult<()> {
136    // Write items of compound
137    for (name, payload) in compound {
138        // Write element tag
139        digest_io(writer.write_u8(payload.ident() as u8))?;
140
141        // Write element name
142        write_string(writer, &name)?;
143
144        // write payload
145        write_tag(writer, payload)?;
146    }
147    digest_io(writer.write_u8(TagIdent::TAG_End as u8))
148}
149
150pub (crate) fn encode_wonky_string(s: &str) -> Vec<u8> {
151    cesu8::to_java_cesu8(&s).to_vec()
152}